やおよろず

ゆるふわな内容を書いても良い。と言うか何でも良い。そんな自由度の高いブログをめざします。

Slackに”いつものチャンネル”的な要素を持たせるChrome拡張を作った話

この記事は圧倒的令和ッ!!ぴょこりんクラスタ Advent Calendar 2019 - Adventarのために書いたものです。

はじめに

Slackってあるじゃないですか。
人によって馴染みの具合は様々かと思いますが、仕事にプライベートに技術コミュニティに、僕は何かと良く利用しています。
特に職場でのコミュニケーションがslackだと、驚く程色々なチャンネルを行き来したりします。*1

そんな中でたまに気になってくるのが、チャンネルの管理についてです。
お気に入り機能を使えば優先度の高/低2種類のカテゴライズまでは可能なのですが、それ以上の分類はできません。
僕はお気に入りチャンネルに自身の分報*2チャンネルと障害対応用のチャンネルを入れているのですが、
分報チャンネルと障害対応チャンネルがお気に入り枠内部で隣り合わせになっているのは、選択ミスのリスクが高くなります。

と言うか正直に言ってしまえば、先日まさに誤爆をしました。
障害対応チャンネルで思考過程を垂れ流しながら「なんで!?」って騒いでたの、今でも穴があったら入りたくなります。

分報チャンネルに情報流しながら試行錯誤している最中、ふと他チャンネルに移動して気が散っているのが良くないし、
自身の分報チャンネルに遷移する方法が一般のお気に入りチャンネルと変わらないと言うのもよくありません。
分報チャンネルは作業に集中している間に常時表示しているチャンネルなのですから、
お気に入りチャンネル以上の「いつもの」的な取り扱いが欲しいところです。

本題

と言うわけで今回は、以下の条件を満たすchrome拡張を作ってみました。

  • slackのワークスペースごとに一つ「いつものチャンネル」を登録できる
  • いつもとは違う箇所のワンクリックで、いつものチャンネルに遷移できる
  • ついでにそのチャンネル以外の存在を見えないものにする

ソースコードとデモ

ソースコードgithub.com で、デモは

https://github.com/yaoshimax/SlackRestrictor/raw/movie/demo.gif

です。

はい。これだけです。シンプルですね。

デモではチャンネル登録後にslackをreloadしていますが、冷静に考えたらその処理は不要でした。

構造の説明

  • options.htmlとoption.jsで設定。addボタンやdeleteボタンを押すとリストの追加/削除の上、storageに保存される
    • 入力内容のバリデーション? そんなものはない。
  • backrgound.jsでボタン押下時のリスナー登録がされてて、ボタンを押されたらどこかに向けてメッセージを飛ばす
  • slack系ページに仕込まれるcontent.jsに上述のメッセージを受け取て発火するリスナを仕込まれている。
    • リスナはメッセージを受けたらstorageのチャンネルを一通り嘗めつつsidebarに該当物があったらページ遷移&他チャンネル非表示化&ブラウザバック禁止

をしています。

苦労したこと、嵌ったこと、未解決問題

そもそもchrome拡張は●年前にお遊びで一つ作ろうとしたことがある程度で、細かなことは記憶の彼方でした。
当時作ろうとしたものもpopup windowがでれば良い程度の今回以上にシンプルなものだったし、実質初心者。
javascriptを触るのも去年のアドベントカレンダーぶりだし、初心者らしい失敗が今回は多かったです。

以下でそれらを軽く振り返って見ます。

はじめはslack API叩こうとしてたけど断念

初期構想では、右側にサイドバーが出てきて1チャンネル限定で表示するようなミニslack clientを作ろうとしてました。
ただし、これをやろうとするとslack API叩く際に渡すトークンが必要で、

Slack API: Applications | Slack あたりでアプリ登録して
アプリ画面からincoming-webhookの機能登録して必要な権限付与してトークン発行

みたいな手続きが必要となってしまいます。

今回の需要としては各ワークスペースごとに「このURLだけ使えるようにする!」みたいなことができれば良いので、
slack APIをなんとか使わない方向で…と路線変更して生まれたのが今回のchrome拡張です。
その結果、slackのhtml構造を理解して良い感じに改変するスキルが必要にはなりましたが、
API叩くよりはこのくらいでURL登録すればおっけー、くらいが自分に優しくて良いです。

sendMessageには2種類ある

slackの表示を制御するにはページ内に埋め込むスクリプトを(content.js)を何らかの方法で発火させる必要があって、
その方法としてメッセージと言うものが提供されている、と言う所までは良かったのですが、
メッセージを送る実装を書いた所でcontent.jsが全く受信しなくて少し詰まりました。

Message Passing - Google Chrome にちゃんと書いてあると言えば書いてあるのですが

Sending a request from a content script looks like this:
(中略)
Sending a request from the extension to a content script looks very similar, except that you need to specify which tab to send it to.

とあるように、content_script "から”メッセージを送る時のsendMessageはなんも考えずにsendすれば良いが、
content_script"に対して" メッセージを送る時は、どのタブの相手に送るか指定が必要。よってインターフェースも異なるそうな。
これに気付かずにruntime.sendMessageを書いてしまっていたのが今回の敗因。
ちゃんと原典読まずに付け焼き刃のググった内容を見て開発しようとするからこうなる…

発火しないclick

これはもう本当jqueryを少しでも触ってる人だったら当たり前だろって話なんですが、
久しぶりに触ったせいか完全にぼけていた話です。

$("#id-name")[0].click();

と書くべき所を

$("#id-name").click();

と書いててクリック発火しないのなんで???と暫く悩んでました。
id名指定だと要素は一意に定まるはずだから一要素のリストじゃなくてリストそのものが帰って来るような気がしちゃうやつ。

(未解決問題) ブラウザバックをどう防ぐべきか問題

調べていると、 qiita.com

的な解決策がすぐ出てくるのでサクッと書いてはみたのですが、
今回のケースだと別チャンネルからいつものチャンネルに遷移するような場合に、
ブラウザバックで別チャンネルに戻れてしまう問題がありました。
試行錯誤した感じ、click(); イベントの完了(もしくはそれに連動してurlが変わるまで)を待たずして上記処理が実行されてしまうことで、 pushStateをする内容が遷移前ページで固定されてしまうようなのです。

とりあえずは…と言う事で、今は https://github.com/yaoshimax/SlackRestrictor/blob/master/script/content.js#L28のように
sleepを入れて対応をしています。アンチパターン感が半端ない。

url変わるのを短い間隔でチェックしながら変わり次第反映…と言う手もありますが、
絶妙なタイミングで別ページに遷移された場合等など考えるとどういった処理が正解なのかは不明。教えて偉い人。

(未解決問題) 非表示状態のはずのチャンネルが何故か稀に復活してしまう

最後の最後にこれ言うのかって話ですが、致命的欠陥ですね。
月曜日に職場で運用していて発覚しました。そして再現条件が不明です。
ずっと使って居るといつの間にかひょっこり表示されるチャンネルが現れるのですが、
新規未読メッセージが来たチャンネル全てって訳ではないしメンションが来たとかそう言う特異なケースでもない。
どうやって消す?jQueryで要素を非表示にする方法4つ | BeGeek あたりを見て、
hide()/show()を使う方法からcss要素変更する形に変えもしましたが、改善せずです。

なんか特定条件で一瞬要素に!importantかなにかつけてるか、 クラス・roleの着いてない状態での追加→後付けでロールやらクラスやら付与、みたいな形で要素追加しているのか。
それにしたってあまり法則性が見いだせないのがやっかいですね。うーん、こまった。

終りに

とてもとても締まらないオチとなってしまいましたが、
久しぶりにjavascriptと戯れたり、普段とは違う類いの開発をやって見て楽しかったです。

ちなみに自作キーボードで開発してみた使用感として、まあ案の上ですが、記号系を出力するのが鬼門でした。
特に面倒なのが{と}で、片手じゃshiftと[]キーの感覚が広すぎるため、
右手小指で右シフト+左手人差し指で「キー、もしくはその逆、みたいなコマンドを強いられます。
さながらポップンの左右振り譜面…
あとは記号を色々押そうとするとShift押すべきかLower押すべきかRaise押すべきかがよくごちゃつくのも今後の課題ですね。
{[^=" とか、shift/同時押し無し/lower/raise/shift/ 見たいな運指になるのカオス。
これ効率良くなるのか…?と首を傾げてしまうこともありますが、まだ使いたてだし開発も今回が初めて。 徐々にカスタマイズと練習を積み重ねてよりよい方法を模索していきたいですね。

*1:調べて見たところ、職場slackで僕がjoinしているチャンネルの数は3桁いってました。勿論activeでないものも含みますが

*2:分報って何?と思った方はhttps://developer.ntt.com/ja/blog/d55e2be0-255d-4321-9d45-8608ce9d9726 あたりを見てください

自作キーボードを2週間ほど運用していじくってる話

この記事は圧倒的令和ッ!!ぴょこりんクラスタ Advent Calendar 2019 - Adventarのために書いたものです。

はじめに

前回の記事では、自作キーボードを使ってみた話と、それを使った感想を述べました。
ただ、自作キーボードを使ったことのある方にとってはあるあるかも知れないのですけれど、
こういった沼って、やり始めてからすぐの時期が本領な訳ですよ。
要は、運用をして行くにしたがって、もっとこういう風にしたい等の欲がでてくるものなのですよね。

本題

と言うわけで、今回は前記事を書いて以降に自分が見直したこと等を紹介しようかと思います

キーボードの設定の見直し

英字キーボードとして認識させる

前回の記事で公開してすぐ発覚した事なので、現在では打ち消し線表示となっているのですが、
前回記事執筆時点では、 「英字キーボードかと思いきや[Shift+2]が["]なのとかどう見てもJIS配列。でも変な箇所もあり謎」と認識をしていました。

しかし、ふと組み立てガイドを見ると

f:id:yaoshimax:20191212005015p:plain
OS側でJISキーボードと認識されている
メチャクチャがっつり記載されていました。お恥ずかしい。

と言うわけで、OSのキーボード設定からキーボードを英語キーボードに直すことによって、
無事に[Shift+2]が[@]と認識されるようになり、キーキャップとキーの印字内容、keymapの図との不整合が解消されました

f:id:yaoshimax:20191212005636p:plain
windowsのキーボードの設定このへん

半角・全角の切り替え方法の調整

さて、英語で半角入力と全角入力を切り替えたい時、みなさんならどうしますか?
英字キーボード歴が少ないながら、過去の自分の経験によれば、これは[Ctrl]+[Space]のハズでした。

しかし、試しに今回[Ctrl]+[Space]を押して見ると、想定通りに動かない。
改めて調べてみると、MacLinuxでは[Ctrl]+[Space]であるこの変換は、
Windowsでは [Alt]+[`]である、と言う事が分かりました。
確かにこれまでに僕が使っていた英語キーボードMacLinuxかでしかなかったかも知れない。

さすがにそこは使い慣れたショートカットに合わせたいな、と思って調べて見ると、
IMEごとに変更すればこれはなんとなかなることが分かりまして。

Google IMEhttps://ko-log.net/tech-log/archives/3826932.html
MS IMEhttps://ko-log.net/tech-log/archives/2561460.html
Atokは以下画像

f:id:yaoshimax:20191206025352p:plain
Atokの場合変えた場所はここ
みたいな感じで、半角全角の変換をCtrl+Spaceに統一させることができました。

キーキャップの見直し

前回のキーキャップをはめ込んだ図 f:id:yaoshimax:20191129184715j:plain よくよく見て見ると、色々と突っ込みどころがあるんですよ。

  • 右側、[ー]のキーだけ何故か出っ張っている
  • 右側、[”]のキーだけ上下が逆である(出っ張り具合的に)
    • でも表記上はこの向きでただしい
  • 左側、[のキーが盛大に上下逆である
  • 下側、keymap上ではLGUIとRGUIと言われるキーが表示統一されていない(WINと、Menuになっている)
    • 働き的にはWindowsマークのキーがするあれ。ただWINキーキャップ1つしかなかった
    • そもそもRGUIいらないので文字自体邪魔な気がする
  • 下側、MenuとBackspaceのキーの上下が出っ張り具合的に逆。そもそもどちらの向きが正しいのか謎い
    • 上記写真では多数決的にはMenuキーの向きだが、親指で押すことを考えるとBackSpace的な向きが正しい気がする。
    • Lily58 Pro | 遊舎工房 の写真を見ても、手前側が下がってる向きにしてるように見える。

と言うわけで、これらの不備を調整して、今は以下の形で落ち着きました。

f:id:yaoshimax:20191212011918j:plain

ちょっと色彩過多のような気がしたり対象性とか細かく考えるとまだ完璧ではない気はしていますが
先日買ったキーキャップの組み合わせで自分が弄くる範囲としては、このあたりが落としどころかなぁって思ってます。
後はもうあたりしいキーキャップセット買うしか無いですが、優先度は低いかなぁ個人的に。

キーマップ、運指の見直し

keymap、デフォルトは
https://github.com/qmk/qmk_firmware/blob/master/keyboards/lily58/keymaps/default/keymap.c に記載の通りですが、
今は以下のような変更を行っています。
https://github.com/yaoshimax/qmk_firmware/blob/yaoshimax/keymap/keyboards/lily58/keymaps/yaoshimax/keymap.c

大まかな変更点は以下です。

RGUI→DEL

先も記載しましたが、右手最下段一番右と、左手最下段左から二番目のキーは、それぞれRGUIとLGUIにマップされてます。
要はWinキーなんですけども。自分としては一つで良い。Backspace(右手下段右から二番目)を押す時に暴発されると困るし。
その代わりに欲しいものとしては、たまにあったら役に立つ&BackSpaceとの親和性の高さを理由にDELを入れました。

RAISE時左手のFunctionキーのマップを削除

日本語入力で変換範囲を調整する時、Shift + 矢印で範囲調整を自分は良く行うのですが、
矢印キーがRAISE押下時じゃないと出ない一方、Shiftより先にRAISEを押してしまうと左手側の意味も変わってしまい、
良くF1からのヘルプ画面の出現とかをやらかしていました。
と言うか、FunctionキーはLOWERボタンを押しても出てくるようになってるし、配置も数字キーに近い順番なので、
RAISEレイヤをわざわざ使うメリットもあまり無い気がし、一端全部削除する用にしました。

ついでにRAISEを押した時にが重複してマップされて居たため、+-=[]と言う並びを+-=*/に変更。
これは大した意味も無いですが、何となく四則演算でまとめとけば覚えやすいかな、くらいの理由です。

矢印キーの位置を一つずつ左にずらす

デフォルトのキーマップでは、RAISEを押しながらjで左、kで下、kで上、lで右に行くキーマップとなっています。
これ、(エディタの) viで移動するときの配置とキレイに1つずつズレてるんですよね。しかもhにはなにもマップされてないし。
と言うわけでここはviにあわせて、hで左、jで下、kで上、lで右に行くようにしました。

運指について

通常の打ち方で大きく変えたのはmを人差し指で打つようにしたくらいです(mを中指で押す弊害は前記事参照)
いまだにmは状況次第で中指が出しゃばることもありますが、
会社PCなどを触れた時にも人差し指でmを打つ運指もやってやれなくはなさそうだったので、矯正を続けるつもりです。

あとは、RAISEとLOWERと言うレイヤが使えるので、
できる限りそれらを使った方が楽なキーはそちらを使っていくように矯正中。
具体的にはShift+数字で出る記号系(@とか)はLOWERを押しつつホームポジションの行を押すようにしているし、
数字系は最上段に手を伸ばす代わりにRAISEを押しながら上から2段目を使うようにしています。
そうか数字キー無くて良い人の気持ちってこういう気持ちか…と少し思ったり。この方が手の動きが小さくて済むので実際楽。

OLEDのカスタマイズ

qiita.com 神サービスがあるので使うっきゃない。ってことで。 f:id:yaoshimax:20191212033652j:plain とりあえずドラゴン入れときました。

細かすぎて見えるか見えないかギリギリ感ありますね。
絵をアニメーションとかにできたらもっと面白いんだろうなって思うんですが圧倒的にドット絵スキルが足らない。

ホントは左側のOLEDもカスタマイズしようかなって思ってたんですがあまり良い案思い浮びませんでした。
ちなみに遊舎工房の店員さんからの口コミに寄ればここでテトリスできるようにした方がいたとかいないとか。
めっちゃ置きミスしそう。

おわりに

てなわけで、自作キーボードのカスタマイズ一通り12月上旬で遊びつつ自分がやりたいように試行錯誤した話でした。
まだこのキーボードを使ってプログラミングをほとんどしてないので、
プログラム書き始めたらまたキーマップは変わりそうですね。=とかキツそう。
ともあれ、一通りのいじくれるところには触れた気になり、分割キーボードを使う生活も軌道に乗ってきた気がしてきています。
もっとどんどん使っていきたいですね。

10年ぶりに半田ごて持って自作キーボード作ってみた話

この記事は圧倒的令和ッ!!ぴょこりんクラスタ Advent Calendar 2019 - Adventarのために書いたものです。
ちなみに、このブログは一年ぶりに書きました。

はじめに

キーボードってあるじゃないですか。楽器とかの方じゃなくて、パソコンの入力インターフェースとして使われる方。
こんな辺境のブログを読んでいるような方々なら、
人気のキーボードの話とか、Dvorak配列なるものがあるという話とか、青軸赤軸茶軸とか、
様々な奥深さがあることも聞いたことがあるかと思います*1

このあたりの話、個人的には 可能な限り自分専用カスタマイズをしない ことをモットーにしていたので、
あまり深淵の世界に触れることは過去にはありませんでした。

しかし、ここ数年で少し考え方が変わってきて、
有限の時間と体力を有効活用するには、普段の作業環境くらい積極的に改善・カスタマイズしようと思うようになりました。
特に興味をもったのが分割キーボードで、もう少し姿勢良く、無理のない体勢で作業したくなってきたんですよね。

本題

と言うわけで、今回は分割キーボードを自作して使ってみた体験記を書いてみたいと思います。

お買い物フェーズ

まずは末広町にある自作キーボードのお店――遊舎工房さんに向かいました。

f:id:yaoshimax:20191110153336j:plain

店内のテーブルには大量の自作キーボードが転がっていますし、壁際には比較表とかも貼られています(写真はない)。
見るだけならこれだけでもメチャクチャ楽しいんですけれど、つくるとなると分らないことだらけなので、
なけなしのコミュニケーション力で手近な店員さんに話掛けて、片っ端から説明をして貰うことにしました。

取合えず、キーボードを自作しようとすると、基本キットと、キースイッチと、キーキャップを買う必要がありました。
それぞれ以下のように買いました。

基本キット

プリント基板とか、コネクタとか、スイッチとかの電気部品一式のことを指します。*2
分割キーボードで、初心者にもお勧めで、今日から作り始めたい、という需要を伝えた結果、
今回購入したのはLily58 Pro | 遊舎工房になりました。
自作すると言う観点で言うとキーの数は少ない方が作りやすいとは思うんですが、
数字キーは残したかったのと、キースイッチを基板にはめ込む形式でとり換えが簡単という点が選択理由です。

キースイッチ

赤軸青軸茶軸とかよく言われる所というか、押されて凹んで電気回路に信号を送る部分を指します。
遊舎工房さんには色々なキースイッチの刺さっている台(テスター台?これのもう少し大きいヤツ)が置いてあって、
色々と押し比べることによって、今回はHako スイッチのvioletを選択しました。
そこまで反発の強くない、それでいて心地よい軽快な打鍵感が好みなんですよね。

ちなみに、このスイッチを選んだら店員さんがバイオレット・エバー・ガーデンの話を振ってきたので、僕はメイドインアビスの話をしました。

キーキャップ

キースイッチの上にかぶせる、実際に指で押す部分を指します。
遊舎工房さんの店頭に置かれていたサンプル品のキーキャップが結構好みだったのですが、品切れだったので、
そこまで華美になりすぎずそれでいて色の選択肢も色々ありそうな MDA ORTHO VoID | 遊舎工房 を選択。
PBTとABSという素材の違いがあって-と言う話もしてもらえたのですが…ぶっちゃけ色が全てでした*3

組み立てるフェーズ

遊舎工房さんの店舗では、二時間500円で工作スペースが利用可能になるので、そちらで早速組み立てて行くことにしました。 組み立て手順とかはLily58/buildguide_jp.md at master · kata0510/Lily58 · GitHub に記載の通りで写真もあるので、改めて書くことでは無いですが…

買ったものがこれで f:id:yaoshimax:20191104150148j:plain ソケットとダイオードを半田付けしていってf:id:yaoshimax:20191104154335j:plain イヤホンジャック的なパーツ(TRRSジャック)付けて f:id:yaoshimax:20191127170514j:plain LED部分(OLED)つくって付けて f:id:yaoshimax:20191127172836j:plain キースイッチつける板と重ね合わせてねじどめしてf:id:yaoshimax:20191127182320j:plain キースイッチをはめ込んで f:id:yaoshimax:20191129172837j:plain キーマップを書き込んで f:id:yaoshimax:20191129180403j:plain キーキャップはめれば f:id:yaoshimax:20191129184715j:plain 完成です!

作ってみた感想

ね、簡単でしょ?と言いそうなくらい雑な見せ方をしましたし、手慣れてる方には簡単に思えるかも知れませんが、
半田ごてを持つのが10年ぶりという素人としては、結構苦戦することも多かったです。

手順書にある単語がそれぞれどの物体を指すものなのがわからず手順書を凝視していたら店員さんに心配される所から始まり、
半田ごての使い方を軽く教わり*4
予めダイオード等は小皿(?)に出しておくと良い等の手順的なアドバイスを多々いただき、
TRRSジャックをつける面を真逆にしてしまったのを半田吸い取り機で救ってもらい…と、
ただ工作スペースを提供して貰う以上に、満足度の高い時間を提供して貰っていたと思います。
本当にありがとうございます。

ちなみに、一回目の訪問であと少し…という所まで作った気でいたのですが、
その後二回改めて工作室目当てにお店に訪問しました(LED部分つけるのがまだだったのと、リセットスイッチを半田付けしわすれていたことが発覚した)。
二個目をつくるんだったら半田付けセット購入しても良いなと言う気がするんですが、
工作室の雰囲気も結構気に入ってしまったので悩む所です。

使って見た感想

分割キーボードとはいえ数字キーは残してあるしQWERTY配列ならさして苦労することもないだろう、
と思っていましたが、意外と苦戦を強いられています。

苦戦の具合は数値で可視化する方がわかりやすいと思うので数値化すると

f:id:yaoshimax:20191204020130p:plain
普段のパフォーマンス
f:id:yaoshimax:20191204020219p:plain
自作キーボード利用時のパフォーマンス
になる位。

現在進行形でこの記事は自作キーボードを使って居るのですが、慣れるのはまだまだこれからですね。

以下、つらつらと印象深かった部分を書いていきます。

Column Staggeredに慣れが必要

キーの縦列がぴったり揃ってるものをColumn Staggeredと呼ぶようです。今回作ったキーボードはまさにそれ。
大して購入時には心配していなかったですが、これが一番慣れが必要でした。

例えばMUを打つ場合、僕はMを右手中指、Uを右手人差し指で押すようにしていましたが、
一般的なキーボードのようにキー配置が斜めになっていれば可能なこの運指も、
キーボードが縦に揃ってると、右手中指と人差し指を中指を下にして垂直に並べるような格好となり、非常に打ちづらいです。
MUNIを中指人差し指人差し指中指の順で押していくのが地獄。むにむに。
Mを人差し指で叩く運指が最適解な気がするのですが、なかなか直らないので今後どうするかはなやみどころです。

MUに関わらず、UIOの並びが一般的なキーボードよりも右側にあるので、全体的に日本語が結構難しい。
NOを打とうとするとキーの距離が遠く感じるし、他の細かい所でいえば、BやZやPあたりのタイポがなかなか直りません。

…ただまぁ、これらは使っているうちに徐々に慣れてくる範囲だとは思います。
これが自作キーボードじゃなくてテキトーに買ったものなら投げ出していたかも知れない。
そう言う意味では思い切って自作しておいて良かったかもしれません。

意外と配列はJIS配列っぽい。でもなんか書いてあることと違う気もする(大嘘

記事公開してから割とすぐにここは自分の過ちに気がついたので次回のネタにします
自作キーボードのキー配列は英字キーボードベースであることが多いと聞いていたので、
US配列使っていく覚悟を最初からしてはいたのですが、[Shift+2]が["]なのとかどう見てもJIS配列ですね。
US配列で[Shift+2]をした時に出るはずの[@]はGの右側にあったりします。
しかし、手順書に記載されたキーマップによれば、Gの右側は本当は[{]になるはずだったのでは…
もしかしたらキーマップの書き込みに失敗しているのかも知れませんが、
何を押したら何が出るのかいまだにハッキリしていません。
なんにせよ、これはキー配列も自分向けにカスタマイズしろってことなんでしょうね、きっと。

親指で色々なことができるのは慣れると楽しそう

右手親指のホームポジション的な場所にあるキー(水色無印)はエンターで、押すのが気持ち楽になったかもしれない。
後はLOWER,RAISEと言うキーが左右の親指担当箇所にそれぞれあって、
SHIFTとはまた違う、第三第四の使い方をキーに持たせることができるようになってます。
ここは使いこなしたら面白そうですね。

-の位置には可能性を感じる

一般的なキーボードではPの右上にあるーですが、このキーボードではなんとPの右側についています。
少し使って居て分ってきたのが、このキーはエンターキーを押すようなノリで右手小指を使と存外押しやすいです。
右手薬指を伸ばして押していたよりも早く打てそうな気がする。

両手を開いてキーをたたけるのはやはり楽

一度使って見てから元のキーボードにも取ってみると、両手をかなり近づけているなと感じます。
分割キーボードを使った方が胸を開きやすいので、姿勢改善には想像以上の効果が期待できそうです。
職場と自宅でそれぞれ長時間キーボードに触れていることを考えると、将来的には両方分割にした方が良いのかな…
気が向いたら&慣れてきたら二台目はひょっこりつくるかも知れません。 *5

終りに

と言うわけで、念願の?自作キーボードデビューしてみたという話でした。
長々と書いてしまいましたが、一言で言えば、
思ったよりも苦労し、思ったより楽しく、思ったよりすぐ使いこなせない、という感じです。
総じて楽しい。
OLEDやキーマップのカスタマイズは今後の課題です。

f:id:yaoshimax:20191205021635p:plain
練習の成果で今はこのくらい
 

*1:と言う体で話を進めますが、聞いたことがあるかどうかはさして重要ではないです。ついでに言えばいまだに僕は赤青茶軸の違いをサラで説明できません。青がカタカタ五月蠅いヤツだっけ?くらいの雑な認識で生きてます

*2:これを称してキーボードと書かれることもあるようですが、個人的に分りづらいのでこう記載するようにしました(界隈的によく使われる用語ではないです)

*3:キーキャップの素材と形状まとめ - Sansan Builders Box 曰くABSより耐久性があるヤツらしいです

*4:ちなみに二回目に言った時には半田ごての使い方的な補足が工作スペースに掲示されてました。僕のせいだったらごめんなさいと思いつつ、このホスピタリティの高さはとてもありがたかったです

*5:二台目は出来合いのものを買う、と言う選択肢もあるんですが、今の所それよりは作りたい気持ちがあるんですよね。これが沼か、沼なのか。

Educational Codeforces Roundをそんなにやっていけなかった話

この記事は カレーのち ぴょこりんクラスタ Advent Calendar 2018 - Adventar のために書いたものです。

本当は今日までにスマブラでVIP部屋に行きたかったんですが無理でした。精進します。

はじめに

ぴょこりんクラスタアドベントカレンダーってあるじゃないですか。
今もまさにそれ用の記事を書いているわけですけれども。
これまで自分は参加していなかったし、去年や一昨年は、25日分の記事を3人で埋めていたと言うから恐ろしいですよね。

自分は今年になってはじめての参戦となりますが、
一昨日の記事でも触れた夏の大合宿(仮)に参加したあたりで、アドベントカレンダーの記事を書くことが確定していました。

25日を4人で割ってもおよそ6日*1
そんなに沢山ネタを作るのは大変だなぁ、と思ってましたが、
「途中経過でもええんやで」という誰かしらのアドバイスを受けて思いついたのが続き物ネタ。
そのための布石として実行したのが、12/5に投稿した、Educational Codeforces Roundをやっていきたい話 - やおよろずでした。

本題

と言うわけで今回は、実際に12/5の記事を執筆して以降、 自分がどのくらいEducational Codeforces Roundをやっていけたかの話をしたいと思います。

成果

以下が成果となります。ご査収ください。

f:id:yaoshimax:20181222044132p:plain
10問解きました

12/5時点*2ではRound1 A,B*3 しか解いていませんでしたが、
その後にRound1のC、Round2のABCD、Round3のABCDEを解き、いい感じに階段状の図を作る事に成功しました*4
「なんだ10問か…」と思われるかもしれませんが、submission数で言えば29回。
ならせば1ヶ月毎日1submissionしたと言えるので、かなり頑張った方だと思います*5

以下では、Educational Codeforces Roundのいくつかの問題について、解説とか苦労話などを書いておきます。

8回誤答したRound 2 D「Area of Two Circles' Intersection」

問題ページはこちら

問題概要

2つの円があります。円の中心座標と半径はx_1, y_1, r_1 x_2, y_2, r_2として入力で与えられます。
この円の共有部分の面積を求めて出力してください。

解き方とか

数学ゲーですね。

  • 円が交わらないときは当然0
  • 片方の円が完全にもう片方を包含する時は、小さい円の面積
  • 上記以外の時、交点1つと中心座標(x1,y1), (x2,y2)をがなす角度  \theta_1, \theta_2 を用いて、 {r_1}^2 (\theta_1 - \sin \theta_1 \cos \theta_1) + {r_2}^2(\theta_2- \sin \theta_2 \cos \theta_2) が解

で求まります。 \theta_1 とかは余弦定理などを使えば計算できます*6

が、誤差が1e-6以内である必要があるため、適当な計算をして居ると誤差で不正解扱いになります。

f:id:yaoshimax:20181222051309p:plain
誤差と格闘している様子

  • 多倍長整数とか勝手にやってくれるしpythonの方が良いかな?と思ったが、pythonだと誤差が解決できなかった
    • 多分pythonのfloatが53bitなのが悪い*7。この問題、c++でlong doubleを使う必要があるらしい*8
  •  {r_1}^2 \theta_1  + {r_2}^2\theta_2 - {r_1}^2 \sin \theta_1 \cos \theta_1 - {r_2}^2 \sin \theta_2 \cos \theta_2と計算すると r_1, r_2の差が大きい時に誤差で解がずれる

あたりが特にはまりどころでした。
ネット上から他の方の解答を引っ張ってきて途中計算dumpしながらデバッグするくらいにはしんどかったです。

個人的に教育的っぽかったRound 3 D「Gadgets for dollars and pounds」

問題ページはこちら

問題概要(意訳)

 m個の商品がある。これらの価格はドルまたはポンドで与えられている。
あなたはs円までの予算で、m個のうちから k個の商品を購入したい。
ただし、あなたが所持できるのは日本円だけ。
商品購入時に、必要額のドル・ポンドを円と交換する必要がある(予め円をドルに替えて翌日以降持ち越し、とかは不可)。
ちなみにあなたは、ここから先、 i 日後には1ドル a_i円、1ポンド b_i円で変換できる事は知っている( 0 \le i \le n-1)。
最速で何日までに k個の商品を購入する事ができるのかを調べ、何日にどの商品を購入すべきかと合わせて出力せよ

解き方とか
動的計画法で頑張る方針(これだと解けない)

1日ずつ変換レートを見ながら商品の買う・買わないを判断しつつ、
 i 日目までに商品集合  M を買う値段の最小値を dp[  i ][  M ]円として保持する事を考えます
コードに起こそうとすると以下のような感じでしょうか。

for day in [0..n-1] do
  for all M, which is subset of [1, ..., m] do
    for all U, which is subset of M do
       cost = i日目に商品集合Uを買うコスト(円);
       dp[i][M] = min(dp[i][M], dp[i-1][M\U] + cost)
    end
    if Mのサイズがk以上でdp[i][M]がs円以下 do
       //i日目までに商品はk個以上買える。
       print Mの買い方
       return
    end
  end 
end 

この場合、 m個の商品のうちどれを買ったか・買わないかの情報(商品集合  M )を保持しなければなりません。 この状態数は 2mとなりますが、 m は最大で105なので、そんな情報持てません。

ちゃんとした解き方

と言うわけでどうするかと言うと、注目すべき性質としては以下があります。

  • ドル額で  d 円のとある商品を  x 日目までに買おうとするなら、最適な購入費用は  d \times \min_{i\le x} a_i である。
    • 要するに、  x 日目までの一番ドルが安い日に買っておけば良い
  • ポンド額で  p 円のとある商品を  x 日目までに買おうとするなら、最適な購入費用は  p \times \min_{i\le x} b_i である。
  •  x 日目までに  s 円の予算で商品が  k 個以上買える」が真ならば、任意の  x 以上整数 yにおいて「 y までに  s 円の予算で商品が  k 個以上買える」 も真

以上の条件が成り立つ事により、以下のような二分探索ができます。

left = -1
right = n
while(left+1 < n) do
 mid = (left+right)/2
 price = []
 for all 商品 do
  price.append(商品を mid日目までに購入するとした場合の最安値)
 end
 sort(price)
 sum = 0  // mid日目までにk個の商品を買うとしたら最安値でいくらになるか
 for i in [0,...k-1] do
   sum += price[i]
 end
 if( sum <=  s ) do
  right = mid
 else
  left = mid
 end
end

// right日が答えなので、その日迄に買うk個の商品の購入の仕方を出力する

この手によって、計算量を  O (m* \log(m) \log (n)) におとす事ができるので、  m の最大値が 105とかでも許されます。

実装がダルかった、Round 3 E「Minimum spanning tree for each edge」

問題ページはこちら

問題概要

 n 頂点  m 枝の重み付き無向グラフが与えられます。
このグラフは連結であること、多重辺やセルフループがないことは保証されています。
 e_0, e_1, ... , e_{m-1} それぞれについて、 c_i = \min (e_i を含むグラフの全域木のコスト) を求めてください。

解き方
基本方針

最小全域木を計算する代表的なアルゴリズムに、プリムのアルゴリズムがあります*9プリム法 - Wikipedia

平たく言ってしまえば、重みの軽い枝は、ループを作らない限り、片っ端から最小全域木を構成するものと考えて良い。
ってアルゴリズムです。

f:id:yaoshimax:20181222130705p:plain
プリムのアルゴリズムを説明しようとした雑な図

今回は「ある枝  e をつかった全域木の最小コスト」を枝ごとに求めなければなりませんが、
そう言う全域木は、
 e の端点を  u,v とすると「グラフの最小全域木上で  u から vまでのパスを考えたときに、一番コストの高い枝を、枝  eと置き換える」
事によって作る事ができます。

f:id:yaoshimax:20181222135138p:plain
雑に説明を試みる図

全域木上の2頂点間のパス中の最大辺を求める方法

というわけで、最小全域上の2頂点間のパスの最大辺を求める必要が出るんですが。
頂点数が最大で 2* 105 なので、毎回愚直に深さ優先探索とかして求めようとすると時間がかかるし、
全頂点対間のデータをテーブルに保持するのは頂点対が2 * 1010くらいあるので無理です。

これは、木と2頂点  u, v が与えられた時に、一番近しい共通親(Lowest common ancestor)を求めるアルゴリズムを応用することで効率よく計算できます。

まずLowest common ancestorを効率良く計算するにはどうするかと言うと、 前処理で各頂点 n について1番目の親、2番目の親、4番目の親、…2k番目の親をparents[n][k]に保持しておいて*10、 以下のような擬似コードにより、不必要な所をすっ飛ばしながら親を見つける、というものです。

function LCA(int u, int v){
  if(depth[v] < depth[u]) swap(u,v);
  // ここでdepth[v]がdepth[u]に等しくなるまでvの親をたどる(省略)

  while(u!=v && parents[u][0]!=parents[v][0]){
    // 親が一致しないギリギリである2^k番目の祖先までu,vを更新する
    int k =-1;
    while(parents[u][k+1]!=parents[u][k+1]) k++;
    u = parents[u][k];
    v = parents[v][k];
  }
  if(u!=v) return parents[u][0];
  else return u;
}

これがどうして全域木上の2頂点パス中の最大辺を求める事に繋がるか?というと、 各頂点 nについて、1番目の親、2番目の親、…2k番目の親をたどる迄の最大辺をmaxEdge[n][k]と保存しておけば 以下のような擬似コードが使えるのです。

function maxEdgeBetween(int u, int v){
  if(depth[v] < depth[u]) swap(u,v);
  ans = -1;
  // ここでdepth[v]がdepth[u]に等しくなるまでvの親をたどる(省略)

  while(u!=v && parents[u][0]!=parents[v][0]){
    // 親が一致しないギリギリである2^k番目の祖先までu,vを更新する
    int k =-1;
    while(parents[u][k+1]!=parents[u][k+1]) k++;
    // 更新する際に最大辺を更新する
    ans = max(ans, maxEdge[u][k]);
    ans = max(ans, maxEdge[v][k]);
    u = parents[u][k];
    v = parents[v][k];
  }
  if(u!=v){
    ans = max(ans, maxEdge[u][0]);
    ans = max(ans, maxEdge[v][0]);
  }
  return ans
}
解き方まとめ

と言うわけでこの問題については、

  • グラフの最小全域木とそのコスト S 求める
  • 最小全域木をたどりながらparentsとmaxEdgeを作る
  • 各枝  e = (u,v)について、S+weight(e) + maxEdgeBetween(u,v)を出力する

みたいな事をすれば、  O(m \log(n)) の計算量で解く事ができます。

プリムのアルゴリズムの実装に加えてLowest common ancestorを解く実装もする必要があるので糞だるかったです。

EF解けなかったけどGが解けた。Round 55 G「Petya and Graph」

問題ページはこちら

問題概要

 n 頂点  m 枝の無向グラフが与えられます。
各枝には w_i、各頂点には  a_iの重みが与えられています。

頂点の部分集合Uにより定義できる誘導部分グラフについて、
得点を「Uに含まれる枝の重みの総和ーUに含まれる頂点の重みの総和」で定義します。
この最大値を求めてください。

解き方

何となく燃やす埋める問題っぽさがあるなぁと思いながら考えてたらグラフの最小カットに帰着できました。

始点 s,終点 t、 元のグラフの枝 e_1,e_2,e_3... に対応する頂点集合 W
元のグラフの頂点 v_1,v_2,v_3... に対応する頂点集合 V
を持つようなグラフを考えて枝を以下のように張ります。

  •  e_i の端点 u,vから枝に対して、重み無限大の辺
  • 始点 sから各頂点に対して、 a_iの重みの辺
  • 各枝から終点 tに対して、 w_iの重みの辺

f:id:yaoshimax:20181222144946p:plain
雑な説明(作成都合で始点sが右側なのは許して)

こうすると求める値は、このグラフの最小カットCを用いて、  \sum w_i - C と表せます。

なんで?を雰囲気で説明すると、
始点~頂点の位置で辺をカットした頂点の集合を  V_1、辺をカットしなかった頂点の集合を  V_2とすると、
枝~終点の位置では、 V_2に属する点を端点に持つ全ての枝~終点の経路をカットしなければいけないですよね。
これは、 V_1= Uとみなした際に、

  • 前者のカットによる値が、「Uに含まれる頂点の重みの総和が得点から減点される分」
  • 後者のカットによる値が、「Uに含まれない頂点を端点にもつ枝は、そもそもの得点を得られない分」

にそれぞれ対応していると捉える事ができます。
よって、この減点分を最小にするのが得点を最大にする事と等価といえるのです。

ここまで考察できてしまえば、このグラフの最小カットは sから tまでの最大フローに等しい*11 ので、
後は最大フローのライブラリで殴るだけですね。

終わりに

競プロ勢じゃない方にこの記事ってどのくらい伝わるんですかね…分かりづらかったらすみません(ぷよぷよぶり2回目)
ともあれ、それなりに歯ごたえのある問題も解けたし、
このあたりをサクサク解けるようなライブラリ化したいなーと言うモチベにもなったので、個人的には良い機会でした。
これからも競プロやっていきます*12

*1:合宿当時まだ他の方の参戦は決まって居なかったので、4人で計算してます

*2:当時の記事のdemo画面などでご確認ください

*3:本質でないので脚注に書きますが、Round55のABCDGも解いてあります

*4:別に階段が好きな訳ではないです

*5:詭弁です。本当はもっと頑張りたかったと思っています

*6:実際は咄嗟に余弦定理を思い出すことができず、数式ごにょごにょして余弦定理求めるところから始めた

*7:テキトーに調べて53bitってなんで?と思いながらこう書きましたが、倍精度の仮数部が52bitなので妥当な値な気がしてきた

*8:pythonでもnumpy使えばlong doubleできそうですが競プロで使えるか分からないので未確認

*9:あとクラスカルもありますが。今回は使いません

*10:2011-12-05 - (iwi) { 反省します - TopCoder部 の Doublingの図とか見るとわかりやすいかも?

*11:最大フロー最小カット定理 - Wikipedia

*12:でもそろそろ競プロ以外の趣味コーディングもしたいんですよね。UnityかReactあたり使うヤツももうちょっとやりたい

合宿でUnityを触ったきりだった話

この記事は カレーのち ぴょこりんクラスタ Advent Calendar 2018 - Adventar のために書いたものです。

スマブラのアドベンチャーをクリアして全キャラ解禁しました。
VIP部屋は遠いです。やっていきます。

はじめに

脱出ゲームってあるじゃないですか。
フラッシュゲームが元ネタだと思いますが、ここ数年はリアル脱出ゲームという形でもかなり広まってきた感がありますね。
自分は何度かリアル脱出ゲームのお誘いを受けて遊ぶ機会がまた増えてきたのですが、
謎を解きつつアイテムを集めて少しずつゴールに近づいていく感覚はやはり面白いものです。

本題

既にある合宿参加者の記録 - お菓子食べる部などでも言及されている合宿のころから、
自分はUnityを使って脱出ゲームっぽい者をが作れないか?と思っていました。

結果としては、全然作りきれませんでした

途中経過の内容が以下になります。 https://yaoshimax.github.io/MagicRoomEscape/

やれること

  • 十字キーで移動
  • マウスカーソルで丸い水晶とか紙の目元かランタンとかクリックできる
  • ランタンを取るとItemの所に炎マークがつく
  • ランタンを取った状態で暖炉をチェックすると暖炉に火がつく

今回は、合宿前後で自分が行った事について書いていこうと思います。

合宿前:事前準備

Unityのチュートリアルは玉転がし・シューティング・2Dローグライクあたりまで一度やったことがあったのですが、
完全忘却済みだったので、玉転がしのチュートリアルを再び復習しました。
【Unity 入門】【チュートリアル】玉転がしゲームを作る - コガネブログ

後は、とりあえず部屋を作った上で、
プレイヤーが十字キーで移動ながら、プレイヤー視点でカメラが移動していくような状態を作ろうとリハーサル。

しかし、実はこの時点でいきなり暗礁に乗り上げていました。

はじめはプレイヤーとしてただの円筒形オブジェクトを用意し、オブジェクトにカメラをくっつけ、
その円筒形オブジェクトを操作するscriptを書いて…という実装をしていたのですが、
部屋の隅っこに行った際にうまく止まってくれない現象に長らく悩まされていました。

悪戦苦闘の末、CharacterControllerをつかってプレーヤーを動かす - Taka8’s blogにあるような、
こういうときに使うのにうってつけのControllerがあることに気づき、ようやくスタートラインに立てた気分になります。

ぶっちゃけ、これだけで結構な日数を使ったし、これだけで合宿の準備は終了です。 ただ、これすら調べてない状態で合宿に入っていたら、きっと悲惨なことになっていたでしょうね。

合宿当日

室内のレイアウト周りの調整

ひとまずはリハーサルの成果により、部屋の中を人(with カメラ)が動き回る状態を作り、 その後はアセットストアと戯れながら適当に家具を設置するなどしました。

Unityはアセットを使えばそれなりの見栄えのものができるので、ついつい作業できた気になりますよね。

ただ一方で、イメージに100%合致するアセットはなかなか存在しないのが悩みどころです。

例えば自分の場合「なんか、占い師が使いそうな、丸い水晶みたいなヤツ」が欲しいなぁと思ったところ、 アセットストアでよさげな者を見つけることが意外と難しいものでした*1

仕方がないので、水晶はsphereオブジェクトによさげなテクスチャを張り付けて自動生成。
水晶を乗っけるクッション?みたいな者は、座布団のフリーアセットを流用する、などの運用でカバー案を考えながらレイアウトをしていきました。

あと、暖炉のアセットを壁に埋め込ませようとしたら、継ぎ目の部分がどうしても浮いてしまう、と言う問題にもぶち当たっていたのですが、
これは解決方法が分からなくて諦め。こういうのって皆さんどうしてるんだろう。詳しい人教えてください。

アイテムを見つけて、クリックしたらメッセージが表示される仕組みを作る

ここからはひたすらググりながら実装しています。

  • アイテムにオンマウスで色がハイライトされる
  • クリックすると、別途用意されたレイヤーにテキストオブジェクトが表示される

的な実装です。こう言うと簡単そうですが、 実際にはテキストオブジェクト表示中にさらにプレイヤーが動けちゃったりとか、さらにクリックできちゃったりする現象が発生するので、地味に手間取りました。

クリックでアイテムを取得する仕組みを作る

ググりながら色々と考えつつ実装していきます。

基本的には、先述の「クリックしたらメッセージがでる」仕組みの派生で、 常に描画されてるエリアをつくり、そこに特定条件したでオブジェクト表示をするようにしました。

本当は複数アイテムの取得などの対応も考えたかったのですが、合宿中だったし、シンプルに。 地味に取得したアイテムを非表示にするなどに手間取ったりもしました。

オブジェクト表示についても、本当はアイテムの3Dオブジェクトその者を小さく乗せたかったのですが、 これが結構大変で挫折。

アイテム取得時に特定箇所をクリックするとイベントが発生する仕組みを作る

ランプを持った状態で暖炉をクリックすると暖炉が光る(ランプを投げ込むイメージ)
と言う仕組みを作りました。
このあたりまでくると既存の知識の組み合わせではあったので、割と終了間際にざくざく書いた気がします。
大分実装がごちゃごちゃである自覚は、ある。

おおよそ、合宿中に行えた実装範囲はこのくらいだったかとおもいます。

合宿後にやったこと

合宿メンバーは特に分かると思うんですが、正直あまり進展はないです。

というのも、暗証番号入れたら箱の鍵が開く仕組みを作りたかったんですが、
よさげなアセットがない&自力でやるのが難しくてまごついてます。脱出ゲームとしては個人的に必要不可欠だとおもうんですけどね。。。

というわけで、やったこととしては、

  • 細かなバグ修正(暖炉の火をつけた後にまた暖炉が選択できて、『暖炉の火が消えそうだ』とか言われていた
  • 光源の設置と明るさの調整(合宿時は暖炉の火が小さすぎて全然見えなかった)
  • 棚の扉がクリックで開くようにする
    • 実は直前までこれバグってると思ってて非公開にするつもりだったが、WebGLビルドしてみたら動いていた…
  • WebGLでのコンパイルと公開

あたりです。

終わりに

Unityはアセットの組み合わせで簡単に色々作れると言われますが、
実際は組み合わせ方なり、何もないばあいに自分で作る所なり、結構な地力が必要だなぁと思います。
ただ、プログラマたるもの(?)、ゲームを作るのはやっぱり一度はやってみたいところですし、
今後も引き続き開発できると良いなぁ…と思います。

*1:現時点ではまだ有料アセットを使ってません

ぷよぷよe-sportsセルフ反省会

この記事は カレーのち ぴょこりんクラスタ Advent Calendar 2018 - Adventar のために書いたものです。

今日はスマブラを我慢しました。やっていきます。

はじめに

ぷよぷよってあるじゃ無いですか。
自分は幼少期に初代ぷよぷよ*1 をはじめに触り、
しばしブランクがあったものの、15th Anniversaryで復活し、ぷよぷよフィーバー(クラシック)、20th Anniversaryと遊び続け、
ぷよぷよクロニクル・ぷよぷよテトリスあたりで一端離れ… と、 浮き沈みの激しい遊び方をしていました。

ぷよぷよテトリスあたりで熱が冷めていたのが正直な所だったのですが、
今年になってぷよぷよ日本eスポーツ連合のプロライセンス認定タイトルとなり、
ぷよぷよe-sportsという新しいソフトが発売され…と、
個人的に再びぷよぷよが熱いシーズンを迎えつつあります。

とは言っても、ガチ勢といえる程の実力者では無いため、オンライン対戦ではかなり苦戦を強いられているのですが。

プレイ時間としてはかなりの時間を費やしてきた自負はある*2のですが、
やれどもやれども、なかなか勝てない。漫然とやるだけでは駄目なのだなぁ、と痛感します。

何事も上達するには、過去の失敗を見返して反省し、成長しようと努力しないといけませんね。

と言うわけで今回は、いくつかピックアップしてきた自分のぷよぷよのプレイングのセルフ反省会を行いたいと思います。

ケース1:粘れなかったぷよぷよ

youtu.be 左側が僕です。

行動の説明と振り返り

時間は動画上の時間で表記し、画面右下の時間はつかいません。

  • 0:09:相手の盤面を見て、赤発火の三連鎖(ただし赤が1個)の状態なので紫二色で三連鎖速攻を仕掛ける

ただし、相手のネクス*3・ネクネク*4にそれぞれ赤が1個ずつ含まれていたので、
この判断はあまり正しくありません。実際に今回は、0:13ごろに相手が赤を3つ用意して3連鎖を打たれてしまいました。
たまたま4連鎖・5連鎖では無かったので生きながらえましたが、危なかったですね。

…正直、相手のネクスト・ネクネクまで見るのは滅茶苦茶難しいんですが。修行が必要なところです。

  • 0:14~0:28: 連鎖が作れずモタついたら、追撃連鎖がきたので、とりあえず高く積んどいた

すでに大分雲行きがあやしいですね。0:14時点で赤緑を置きミスしていて、しばらくは以降連鎖の形の再構築に苦戦してました。
0:17時点の赤紫は右から2番目の列に赤を下に縦て置いた方がやりやすかったかも知れません。
少なくとも、 紫青紫青と縦に並んでいる一番右の列は滅茶苦茶使いづらいです。

  • 0:28~0:30:お邪魔画布ったら突然見えてきた紫→緑の連鎖。そして痛恨の段差計算ミス

緑紫が連続している事とお邪魔が降った後の形をみて紫→緑の2連鎖を作ろうと考えました。
しかし、その後においた0:30の赤青の置き方が最悪で、ここで青赤逆に置けば紫→緑→赤→青の4連鎖まで可能だったはずです。
もともとその4連鎖は狙っていたのですが、左から3列目の緑の下のお邪魔ぷよが消えるのを考慮漏れしていました。

  • 0:32~0:37:紫がくることを祈っていたら緑青→緑赤→赤赤→赤青→赤青というツモがきて泣く泣く赤と青を1回ずつ消す

正直これはどうやって組めば良いのかわからなかった。今でも分からない。
とりあえず元の位置に赤を再セットできたので、僕の脳内では紫→緑→赤の3連鎖でした。

  • 0:38~0:42:相手の連鎖に反応して紫を消す。3連鎖のつもりだったが2連鎖になる。

0:28~0:30に起こしていた勘違いがここに来て発覚したわけです。
0:38の赤青で赤を左から3列目において置けば紫→緑赤の同時消し2連鎖になってまだ良かったはずですが
僕の脳内では3連鎖だったため、必死に左から4列目に退避させています。切ない。
退避させるにしたって、3列目に青、4列目に赤としておいた方がまだ良かった気がします。
(そうすると連鎖後に青がしたの方にある青とくっつくので)

  • 0:44~1:15:必死に堪えるターン

0:50で残り4スペースしかないのにツモ3つで紫4つをかろうじてつなげられたのがまず第一に神ツモでした。
その後0:54で相手が落としたお邪魔ぷよが左から3列目に降っていたらそれもまた詰んでいましたが、
別の列(見えない所)に降ったためかろうじてまだ緑が消せて生きて行けそうなきになります*5
後は2連鎖でスペースを確保しながらチマチマとやっていました。
相手の残りぷよ数が少なくて、相手の連鎖数が4連鎖以上になら無かったのにも助けられています。

  • 1:15:本当は3連鎖にできた2連鎖

1:14時点でぷよが1列降る事が分かって居たので紫青を2列目に置きつつ紫→青or緑でなんとか、と思って居る所。
咄嗟に1:15時点の赤ゾロをテキトーに左に置きつつ次の緑青で緑をおいて紫赤で紫を消していますが、
1:15時点の赤ゾロを右に置けばこれは3連鎖にできました。

必死すぎて緑青紫しか注目してなかったんですよね。

  • 1:28 :絶望の置きミス

いい感じに相手の攻撃を牽制していたらそれなりにスペースができてきたので、
紫緑を右から3列目に置いて紫→緑→赤とかの3連鎖を構築予定でしたが、紫緑がなぜか右から2列目に置かれてしまい絶望。
相手から連鎖も飛んできたので、 再び高く構えつつ、紫と緑のどっちか消せばいい気持ちになりながら堪えてます。

  • 1:42~:相手のぷよが殆ど無くなったが、こちらも連鎖が思い浮かばず。暫くして青緑紫赤の4連鎖を考えるも間に合わず

このあたりで大分緊張がきれた感じがありました。
とりあえず振り返っていて思ったのは、1:51の赤紫で謎に赤を消しつつ紫で緑に蓋をしたのが最高に悪い形で、
左から3列目に紫を下に置いとけば、その直後の緑紫を右から3列目に緑を下にして置けば緑→紫赤の同時消し2連鎖ができた。
これならその直後に赤→青の2連鎖も打てていただろうし、この2連鎖同時消しが作れなかったのは痛かった。

ケース2:ワンチャン掘れたはずのぷよぷよ

youtu.be 右側が僕です。短いです。

行動の説明と振り返り

-0:22 :相手から3連鎖飛んでくるが、連鎖を発火するための青が来ないので、黄色を高く詰んでおいた。

こういう攻撃を堪えるために一番右の列の黄色とか青とかが使えれば良いのですが。
今回のツモだと黄色ばっかりでどうしようもナサゲですね。
改めて見ると序盤からかなり空いては組みづらそうな形を組んでいましたし、こちらから速攻を仕掛けても良かった気がします。

-0:30:赤緑3連続の捌き方が思い浮かばず、お邪魔ぷよが3段降れば緑→黃→青→(ry)と連鎖できると信じて赤を消す

当然お邪魔ぷよは3段以上降るので負けます。これは甘えです。

と言うか、今回のツモで言えば、0:26で緑黄色が見えた時点で、以下のようにする事で無駄消しなく連鎖を繋ぐ事が可能でした。 ネクネクを見てから手を変える柔軟性が足りてないのかも知れないな、と言う気持ちになりました。

0:26時点1手目2手目3手目

ケース3:ギリギリ逆転できたぷよぷよ

youtu.be 左側が僕です

行動の説明と振り返り

  • 0:04~0:09:GTRの形を狙ったが思った以上に作りづらい形になってしまった

GTRと言うのはリンク先画像のような形で紫→赤→緑と連鎖をつなげる基本形です。
相手側は僕とは異なり緑→赤→紫のGTRを組んでいるので比較しやすいですが、
僕は最初から3番目におく赤緑を今の形においてしまった為、
紫青→赤赤→紫紫→赤赤というツモを0:09の形のように無理矢理おいてしまっています。
同じ時刻の対戦相手さんの盤面は僕のように赤2個が独立してるなども無く綺麗なのに対し、自分の盤面はかなり悪いですね。

基本的に、3番目のツモを見てから置き場所を相手さんのように組んでいくのが一番だと思います。

が、他に何か手は無いだろうか…と思って少し考えた方法としては、
1手目を今の形で2手目から置き方を変えられる場合は、こう おく手もあったかも。
新GTRみたいな形 が目指せれば一番ですが、
今回のツモだとこうなるみたいな形にするとかだろうか。
紫が分散してしまっているし、思ったほど 良い方法ではないかもしれないけれど、赤2個が離れてないだけましかも。

  • 0:10~ 0:14:連鎖の組み替えと空虚になる右半分

紫紫・青緑をみて連鎖のルートを一番左列の赤→紫→青(これからつなげる)→紫(これからつなげる)とする形に組み替え。 0:14時点で一番左列に青紫と置けば赤発火(緑→赤としても良い)できる下地が整い、直後の青緑で青だけ乗っけてます。
ただ、その代償として右側がすっからかんですね。
あまりでこぼこ憎まない方が大連鎖を組む上では良いとされている(気がする)ので、この形はあまり良くないです。
あと、左から二列目に青を乗っけた都合で緑赤緑赤みたいな奇怪な形ができているのもかなり使いづらかったです。

  • 0:22~0:24:連鎖の最後を緑→赤時得るように作ったけれど紫の置き場を失敗した件

これは置きミスだとリアルタイムでも気づいていませんでした。
0:24時点で連鎖の末尾は緑→赤紫同時消しで確定してしまってますね。
0:24付近の青と紫の位置を逆にするか、こうおいた方が連鎖としては繋がりそうでした。
(より一層でこぼこになるので実際そうした方が良いのかはわからないけど。。。

  • 0:26 :おじゃまぷよが降ってきたことでこの後どう組んだら良いかわからなくなる。

お邪魔は全ての計算を狂わせるんですよね。多分元々は右から3列目に青を置くつもりだった気がします。
が、そもそも青がそうそう来ないし、赤赤や緑緑のツモを処理する方法が分からず、結局適当においちゃってます。
後後の意味では英断だったのは、お邪魔が怖くなったのでこのタイミングで一番左上に紫をセットし、
本線を赤1個or緑3個で消せるようにしてたこと、くらいですかね。

  • 0:39 :相手の3連鎖に為す術が無い

赤1個or緑3個で本線が発火できるといったものの、周囲の色を巻き込まないためには結構都合の良い置き方が限られました。
そもそもその形が悪いと言う話はあるんですが、3連鎖をきても何をする事もできないし、
当時は右半分どうしたら連鎖繋がるか考えるのに必死だったので、雰囲気詰んで後は堪えてから考えることに。

  • 0:40: とりあえず紫を消したら意図せず3連鎖になり良い形になったし、相手は本線を発火していた

青が消えるのは正直予想してなかったです。が、どちらかと言えばもっと嬉しかった副作用があり
0:45時点で、発火させづらかった赤が青→赤と消せるルートが開拓されていました。

これがなかったら勝てなかったと思います。

  • 0:45~:気合いでその後連鎖をつなげて発火する

時間的にどのくらい迄かとかは相手の連鎖を見えてないので雰囲気で紫まで挟んでから発火。
結果的には最後の赤紫同時消しが功を奏して火力勝ち。

…と、まぁ、結果的には勝ちを収めていますが

(1) 相手のお邪魔が降らなかったら本線を発火させるのが大分辛い形になっていた
(2) そもそも右半分の形を綺麗に組めていなかった

と言うあたりは滅茶苦茶反省点だな、と思います。

おわりに

言葉でぷよの「こここうすればよかった」説明するの凄いムズいですね。伝わらなかったらすみません*6

ちなみにこの後、撮れ高を求めて遊び続けていたら階級も2段階落ちたしレートも100位下がりました。
復習するのもそうだし、ムキになっってやけくそ連鎖を打たないようなメンタル強化も必要そうですね。。。

*1:相殺なんて無かった。これはこれで意外と戦術があり面白いのでぷよm@sでググってください

*2:ぷよぷよサークルで百本先取とか何回かやってた時期もあります。5時間ぶっ通しでぷよぷよするの大分ハードでした

*3:次に引くぷよのことです

*4:次の次に引くぷよのことです

*5:こう言うの、潔く負けろと言われる人も多いのですが、個人的にはワンチャン拾える可能性があるなら拾うよう努力する派です

*6:ぴょこりんさんはぷよぷよができる人なので伝わっているはずです

減量に一応成功したが最近伸び悩んでいる話

この記事は カレーのち ぴょこりんクラスタ Advent Calendar 2018 - Adventar のために書いたものです。

スマブラしていたら体調を崩しました。やっていきます。

はじめに

体重ってあるじゃないですか。
自分は数年前くらいからじわじわと増加を感じていたのですけれど、
今年は流石にいい加減健康にならなくては、というケツイを胸に抱き、減量のため努力しました。

f:id:yaoshimax:20181211173648p:plain:h300
6~8月で78kgくらいから69kgくらいまでがっつり落ちた

6月の減少の仕方が凄いですよね。自分でもよく頑張ったなぁと思うと共に、
こんなに効果があるものか、と驚いた出来事でもありました。

で、なぜこんなに急激な減量に成功したかと言いますと。
間食や晩酌の数を減らしたと言うのももちろんあるのですが、とりわけ心の支えだったのが…

f:id:yaoshimax:20140310114828j:plain:w300
SUBWAY!!!!
はい、SUBWAYです。
昼食として野菜を上限*1にしたサンドイッチを食べ続けたのが、かなり効果的だったと思います。
トッピング・ドレッシングの変更などもできるので、はじめ数ヶ月は飽きること無くずっとSUBWAYに通い詰めていました。

…とは、言ったものの。そんな状態がずっと続くわけでもなく。

f:id:yaoshimax:20181211174704p:plain:h300
9月以降の中盤の伸びが怪しい
9月あたりからSUBWAYに行くことがなくなり始め、身体も徐々に緩んでくるようになりました。

SUBWAYの手前にコンビニがあるので、つい移動を面倒くさがってミックスサンド*2を買ってしまうようになり、 それが徐々にもの足らなくなり食べる量が増え…と、完全に堕落の一途をたどっている今日この頃です。

健康面などを考慮して、SUBWAYモチベをもう少し高めたい気になりました。

本題

と言うわけで、SUBWAYのメニューからカスタマイズしたい内容を選択したら
その結果のカロリーとお値段がわかるスマホアプリを作りました。

github.com

デモ

https://github.com/yaoshimax/SubwayCalorieCalculator/raw/movie/movie/demo.gif

以下は、作るまでの流れなどを軽く書いておきます。

下準備:カロリーの算出方法

調査

SUBWAYは結構丁寧にカロリー表示してくれています。
栄養情報PDF:https://www.subway.co.jp/menu/pdf/eiyo.pdf

このデータを見ると、

  • サンドイッチ全体(全部お勧め設定)のカロリー
  • パンのカロリー
  • ドレッシングのカロリー

がわかります。 全体のカロリー=パン+具+野菜+ドレッシングなので 野菜+具のカロリーは単純な引き算で計算できそうです。

…で、野菜って結局何カロリーあるんだ?と言う話なんですが。 結論から言うと、ここが良くわかりませんでした。

日本の栄養情報には野菜ごとのカロリーは記載されてないし、
一部メニューはトッピングとして具のカロリーが記載されているのでその値から野菜のカロリーを逆算する手も考えましたが、
いまいち帳尻の合う値になら無くて挫折(多分トッピングorメインで枚数なり分量なり違う)。

英語サイトでは、各メニュー↓のnutrition calculatorを見れば「VEGGIES」を選択できるのですが、
Menu - Black Forest Ham | SUBWAY.com - United States (English)
日本とは野菜の種類が異なり、にんじんの項目が存在しないし、
そもそも同じ名前のサンドイッチでもカロリーが異なる*3し、どのくらい真に受けて良いのかわかりません。

とりあえず参考情報として、英語サイト的には、レタスとトマトだけ10 kcalで他は0 kcalで計上されているようでした。

雑な仮定

結局は何が正しいのかは良くわからないので、

  • レタスとトマトは他の野菜より2倍程度カロリーが多い
  • レタス・トマト・にんじん・ピーマン・レッドオニオンを入れると15kcalくらいになる*4

という思い切った仮定をおいて、レタス・トマト=4kcal、にんじん・ピーマン・レッドオニオンを2kcalとして計算し、野菜以外の具のカロリーを算出することにしました。

f:id:yaoshimax:20181211181555p:plain:w500
雑な計算をするスプレッドシート

ちなみに、全体のカロリーは全てお勧めの設定で行われているので、
お勧めの組み合わせ=写真に掲示されている組み合わせと(これまた)仮定をおき、
使われているパンと野菜を目力で推定して入れています*5
ホワイトとウィートの違いとか全然わからないし、思った以上に人力で頑張る形になりかなり心が折れつつあったんですが、
雰囲気分かれば良いよねと割り切って実装をしていきます。

実装

カロリーの値を決める所まででかなり大変になりましたが、実装です。 react-nativeでガシガシ書きました。

パンと具材は1個しか選びようがないので、pickerを使い、
野菜・トッピング・ドレッシング*6などの複数選択させる方には、 react-native-label-selectを使っています。

実は、複数選択させる方はかなり鬼門でした。
はじめはreact-native-multiple-selectを使うつもりだったのですが、
どうあがいても自環境では文字が表示されなくて、label-selectに突貫工事で書き換えています。

しかも、react-native-label-selectの方もメンテがされて無くて、
issueで報告されているように、react v15.5以降では動きません。
結局、node_modules下のsrcファイルを修正PRと同じように書き換えて動作させています。

おわりに

まだまだ実用にはほど遠いですが、
せっかくだし一回くらいスマホアプリ作りたかった、という欲求を少しだけ満たせて楽しかったです。
以下のTODOを残してしまったのは心残りですが、気が向いたらやっていきたいと思います

  • サンドイッチを選んだときに野菜やパンを全部お勧めのものに上書きする機能
  • 具材の写真とか載せたい(でもPicker/label-selectの仕様的にむずそう)
  • 野菜全部、はオプションでワンクリックで設定させたい
  • 野菜の分量でちょこっとカロリー増減させたい(厳密計算難しい領域だし大して変わらない気がするので優先度下げちゃった)
  • リリースビルドしてスマホ単独で動作できるようにする
  • 季節限定メニューの内容を持ってこれるようにする(写真から目力で推定するところで詰むのでブレイクスルーが必要)

*1:『野菜上限で」でお店の人にも通じます。量には個人差があります

*2:カロリー的にSUBWAYと大差ないあたりを探すとこのあたりに行き着く

*3:例えば日本のターキーブレストレギュラーサイズは264kcalですが、英語サイトの方では280kcal

*4:海外で20kcalなら日本のはもう少し少ないだろう読み

*5:常に野菜全部入れてたからローストビーフにトマト入ってないのにこの段階で気づきました。当時一律計算させようとしてたから割と絶望

*6:ドレッシングもミックスできるので一応複数選択可能にしました