MANA-DOT PIXEL ART, PROGRAMING, ETC. 2024-01-20T21:24:01+09:00 manaten Hatena::Blog hatenablog://blog/12921228815716055656 自宅のモニタをついにウルトラワイドモニタした & 内蔵KVMスイッチのレビュー hatenablog://entry/6801883189076643453 2024-01-20T21:24:01+09:00 2024-03-03T14:22:29+09:00 Dellの34インチUWQHD(3440x1440)モニタ、 P3424WE を購入しました。 もともと デスク周り2022 - MANA-DOT で書いた通り、27インチのWQHDモニタを愛用していましたが、前々から作業領域的にはWQHDが最適だと思うものの作業領域外に追加の情報(figmaのデザインカンプだとか、ビルド後のプレビューだとか)を表示したいと思うことが多々あり、UWQHDサイズのモニタがほしいと思っていた(加えて会社のモニタはUWQHDであり満足していた)ための購入となります。 今回モニタ購入の際の検討事項の一つが、「内蔵KVMスイッチの出来」であったため、本エントリでは特にモ… <p><img src="https://manaten.net/wp-content/uploads/2024/01/01.jpg" alt="現在のデスク周辺" /></p> <p>Dellの34インチUWQHD(3440x1440)モニタ、 <strong><a href="https://www.dell.com/ja-jp/shop/dell-34-%E6%9B%B2%E9%9D%A2-usb-c%E3%83%8F%E3%83%96-%E3%83%A2%E3%83%8B%E3%82%BF%E3%83%BC-p3424we/apd/210-bjjq/%E3%83%A2%E3%83%8B%E3%82%BF%E3%83%BC-%E3%83%A2%E3%83%8B%E3%82%BF%E3%83%BC%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B5%E3%83%AA%E3%83%BC">P3424WE</a></strong> を購入しました。</p> <p>もともと <a href="https://blog.manaten.net/entry/desk-2022">デスク周り2022 - MANA-DOT</a> で書いた通り、27インチのWQHDモニタを愛用していましたが、前々から作業領域的にはWQHDが最適だと思うものの作業領域外に追加の情報(figmaのデザインカンプだとか、ビルド後のプレビューだとか)を表示したいと思うことが多々あり、UWQHDサイズのモニタがほしいと思っていた(加えて会社のモニタはUWQHDであり満足していた)ための購入となります。</p> <p>今回モニタ購入の際の検討事項の一つが、「<strong>内蔵KVMスイッチの出来</strong>」であったため、本エントリでは特にモニタ内蔵のKVMスイッチ周りについて記載をしようと思います。</p> <h1 id="モニタ内臓のKVMスイッチについて">モニタ内臓のKVMスイッチについて</h1> <p>筆者の環境では <a href="https://blog.manaten.net/entry/desk-2022">デスク周り2022 - MANA-DOT</a> に記載した通り、 <strong>KVMスイッチを用いて「家の据え置きPC」と「会社のラップトップPC」を一つのモニタ・マウス・キーボードを共有して切り替えて</strong> 利用していました。図にすると以下のようになります。</p> <p><img src="https://manaten.net/wp-content/uploads/2024/01/02.png" alt="既存のモニタ周り" /></p> <p>この構成の肝は、<strong>KVMスイッチとUSB TypeCハブ</strong>です。KVMスイッチによって一つずつしかないマウス・キーボード・モニタを共有・切り返えし、更にラップトップ側ではUSB TypeCハブを挟むことで、電源を含めて1つのケーブルのみでPC本体と接続できています。これにより</p> <ul> <li>会社PCと自宅PCで機器共有したいし、都度差し直したくない</li> <li>持ち運びがしやすいようラップトップと機器は最小限のケーブルで接続したい</li> </ul> <p>という要件を満たすことができています。</p> <p>この構成自体に満足はしていたのですが、欠点として以下のようなものがあります。</p> <ul> <li>机の裏に隠しているとはいえケーブルの本数がとても多くなる</li> <li>KVMスイッチが中国の謎メーカーのものしかニーズを満たす(WQHD 60Hz出力可能、USB3.0対応)ものがなく、品質がやや不安</li> <li>電源ケーブルもたくさん必要。PowerPortにポート数の余裕があるとはいえ、この構成だけで4ポート使ってしまう</li> </ul> <p>そこで、次モニタを買い替えるときは<strong>KVMスイッチ内蔵のモニタ</strong>にしようと考えていました。昨今のテレワーク需要を見据えてか、モニタ本体にKVMスイッチを内蔵しており、更にTypeCケーブルですべての機器+電源をラップトップと共有できる、という製品がちらほら増えています。今回購入した <strong><a href="https://www.dell.com/ja-jp/shop/dell-34-%E6%9B%B2%E9%9D%A2-usb-c%E3%83%8F%E3%83%96-%E3%83%A2%E3%83%8B%E3%82%BF%E3%83%BC-p3424we/apd/210-bjjq/%E3%83%A2%E3%83%8B%E3%82%BF%E3%83%BC-%E3%83%A2%E3%83%8B%E3%82%BF%E3%83%BC%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B5%E3%83%AA%E3%83%BC">P3424WE</a></strong> にも同様の機能が搭載されています。これはモニタ本体内臓のUSBハブを、モニタ出力先の切り替えに連動する形で、モニタのUSB TypeC出力またはUSB出力のどちらかに切り替えられるというものです(より詳細は後述)。</p> <p>モニタ本体内臓のKVMスイッチを利用することで、構成は以下の図のようになりました。</p> <p><img src="https://manaten.net/wp-content/uploads/2024/01/03.png" alt="現在のモニタ周り" /></p> <p>以前の構成のメリットを保ったまま、モニタ内蔵のKVMスイッチとUSBハブを利用することで、ケーブルと電源、機器の数を大幅に減らし非常にシンプルにすることができました。</p> <p><img src="https://manaten.net/wp-content/uploads/2024/01/04.jpg" alt="不要になったケーブルたち" /></p> <p>不要になったケーブルたち。撮り忘れてますがUSBハブ経由で利用していたMacのACアダプタも不要になっています。</p> <h1 id="P3424WE内蔵KVMスイッチの使い勝手">P3424WE内蔵KVMスイッチの使い勝手</h1> <p>さて今回モニタを購入するに当たってKVMスイッチの機能が非常に重要であったことは書いたとおりですが、実際に選ぶとなるとサイズを含めてこの条件を満たすモニタはほぼなく(今回のDellのもの以外だとLGのものか、10万以上のハイエンドクラスのモニタしかない)、加えてモニタ自体のレビューはあっても<strong>KVMスイッチの使い勝手に対するレビューは皆無</strong>でした。</p> <p>しかしながらこのKVMスイッチの完成度が低かったり(接続が切れやすかったり遅延するなど)、KVMスイッチの操作性が悪かったり(ほぼ毎日切り替えるものですので、奥まったメニューにあって切り替えづらいなどだと最悪です)、ということは購入前から懸念でした。</p> <p>そこで本記事では、同じくモニタのKVMスイッチの出来が気になる方向けに、KVMスイッチの使い勝手についても軽く触れておこうと思います。</p> <h2 id="P3424WE内蔵KVMスイッチの挙動">P3424WE内蔵KVMスイッチの挙動</h2> <p>そもそも気になる点として、<strong>「KVMスイッチの切替は何をトリガに発生するのか?」</strong>というものがあります。結論から言うと、<strong>KVMスイッチの切り替えは予めモニタの設定画面で設定した割当先に応じて、モニタ入力先切り替えに連動して切り替わります</strong>。</p> <p><img src="https://manaten.net/wp-content/uploads/2024/01/05.jpg" alt="USB割当先の設定" /></p> <p>この写真ではDisplayport、HDMIともにUSB-Bに割り当てられているため、モニタ入力をDisplayportまたはHDMIに切り替えるとUSB-B端子側に、モニタ入力をUSB-Cに切り替えるとUSB-C側にマウス・キーボードも切り替わります。USB-Cは当然ですがUSB-C固定でしか割当できず、HDMI、DisplayportはUSB-C側に割り当てることも可能なようです(サブモニタ用途?)。</p> <p>また、この割当について、初回セットアップ時はKVM設定ガイドというのが表示され、案内に従うことでPC1、PC2、マウス、キーボードの適切な接続と動作確認を案内してもらうことができます。</p> <p><img src="https://manaten.net/wp-content/uploads/2024/01/06.jpg" alt="KVM設定ガイド" /></p> <p>また、入力切替自体はショートカットキーを割り当てておくことで、<strong>本体裏の十字キーで横を押す→決定を押す、という手順</strong>で切り替えることができます。2操作必要ですが(ELEVIEW EHD-601Nでは1プッシュで切り替えできた)、許容範囲内です。</p> <p><img src="https://manaten.net/wp-content/uploads/2024/01/07.jpg" alt="ショートカットキーで切り替え" /></p> <p><img src="https://manaten.net/wp-content/uploads/2024/01/08.jpg" alt="ショートカットキーの割り当て" /></p> <h2 id="P3424WE内蔵KVMスイッチの性能面">P3424WE内蔵KVMスイッチの性能面</h2> <p>まだ利用して日が浅いですが、USB接続が途切れたり、遅延したりということは感じません。通常のモニタ内蔵USBハブと同等だと思います。</p> <p>モニタ出力に関してはPCとは間に機器を挟まずに直接接続しているため、以前の構成より安心感があります。</p> <h2 id="P3424WE内蔵KVMスイッチの課題">P3424WE内蔵KVMスイッチの課題</h2> <p>十分な使い勝手がありつつデスク周りをスッキリできたモニタ内蔵KVMスイッチですが、いくつか課題となる点も気づきましたので紹介します。</p> <h3 id="スピーカースピーカー端子がない">スピーカー・スピーカー端子がない</h3> <p>このスピーカーにはスピーカーが搭載されていません。それだけなら他のモニタでもよくあることですが、このモニタにはスピーカー端子も搭載されていません。</p> <p>HDMI経由で音声出力も一本化しようと考えても、スピーカー端子がないため実現できないということになります。もし音声も一本化したい場合はUSBスピーカーなどをモニタのUSB端子に接続する必要があります(USB端子は4つあります)。</p> <h3 id="Macbookのクラムシェルモードのスリープを復帰させられない">Macbookのクラムシェルモードのスリープを復帰させられない</h3> <p>ウルトラワイドモニタですので作業領域は十分なため、Macbookと使う場合はMacbookをクラムシェルモードで使うことになると思います。ただ、ここに若干の落とし穴があり、<strong>クラムシェルモードでスリープ中のMacをKVMスイッチに接続されたマウス・キーボードで復帰させられない</strong>のです。厳密には復帰させられることもあるのですが、長時間のスリープのあとだと復帰させられないことが多いです。</p> <p>これの何が問題かというと、デスクトップPCで長時間作業のあと、仕事のためにMacに切り替えたときに、せっかくKVMスイッチはワンステップで切り替わるのに、Macが起動しないためMacを起動させるという追加の手順が必要になってしまうのです。</p> <p>おそらくですが、モニタが「USB-Cから電源だけを共有する状態」から「USB-Cハブとして機能する状態」に切り替わる条件がPC側の映像信号の有無となっていそうで、しかし入力がない限りMacはスリープから復帰しないため映像信号も送られず、KVMスイッチに接続されたマウス・キーボードで復帰させられないという結果になっていそうです。</p> <p>以下のURLではこの問題に言及していますが、エレガントな解決策はなさそうです。</p> <ul> <li><a href="https://discussions.apple.com/thread/253289854?sortBy=best">Closed Display Mode with KVM - Apple Community</a></li> <li><a href="https://www.reddit.com/r/macbookair/comments/wp879p/waking_m2_while_in_clamshell_mode/">Waking M2 While in Clamshell Mode : r/macbookair</a></li> </ul> <p>現時点で気づいている対策は次の2つです。</p> <h3 id="1-Macを電源接続時にモニタをオフにしないスリープしないようにする">1. Macを電源接続時にモニタをオフにしない(=スリープしない)ようにする</h3> <p><img src="https://manaten.net/wp-content/uploads/2024/01/09.png" alt="電源接続時にモニタをオフにしない設定" /></p> <p>この設定をオンにすることで、Macはモニタに接続されている限り放置ではスリープにならなくなりますので、長時間のデスクトップ使用のあとにMacに切り替えてもすぐにMac側を操作することができます。</p> <p>これで体験としてはほぼ問題ないのですが、Macを利用してないときもスリープにさせないということになるので、Macへの負荷の心配は若干あります(スクリーンショットで指摘されているモニタの寿命はクラムシェルモードなので問題はないです)。</p> <h3 id="2-MacをKVMスイッチ接続の機器以外でスリープ解除する">2. MacをKVMスイッチ接続の機器以外でスリープ解除する</h3> <p>Macに他の機器が接続されている場合はその機器でスリープ解除するのが手っ取り早いです。</p> <p>自分の場合はマウス(MX Vertical)がUSB接続とBluetooth接続を切り替えられ、Macとはもともと外出時向けにBluetoothでペアリングしているため、マウスをBluetooth接続に切り替えることでMacのスリープを解除できます。ただし、KVMスイッチの切替に加えてマウスの接続方式を切り替えてスリープ解除、という1工程が増えてしまうため、KVMスイッチの良さが半減してしまっています。</p> <p>1の方法で基本スリープさせず、何かの拍子にスリープしてしまった場合のみのバックアップとして考えています。</p> <p>このようにモニタ内蔵のKVMスイッチは基本的には快適なのですが、Macbookのクラムシェルモードといまいち相性が悪いという問題だけが気になります。より優れた解決をお知りの方がいたら教えていただけると幸いです。</p> <p>Macbookとの相性に多少の難はあれど、モニタ本体は発色も気にならず、念願のUWQHDサイズがリーズナブルに手に入りましたし、KVMスイッチがモニタ内蔵になることでPC周りの構成がシンプルになったこともあり、とても満足度が高いです。同じようなニーズでモニタを探している方の参考になれば幸いです。</p> <h2 id="20240203追記-勝手にUSBだけスイッチされてしまう事がある問題">(2024/02/03追記) 勝手にUSBだけスイッチされてしまう?事がある問題</h2> <p>上記のように、クラムシェルモードのmacへの切り替えだけ少々難がある状態だったのですが、もう一つ課題が見つかりました。 どうも、長時間?利用していると勝手にKVMスイッチのUSBだけが切り替わってしまうことがあるようなのです(ようなのです、というのは厳密にハブの機能自体が使えなくなってるのか、もう一台の方に切り替わってしまってるのかが判別できてないため)。</p> <p>作業中に突然マウス・キーボードが反応せず、切り替えると切り替え先で入力されていた形跡があり、もう一度切り替えると復活する、という形で再現しています。 作業中に突然入力できなくなるのは普通にストレスなので、こちらに関しては問い合わせをしようと考えています。</p> <h2 id="20240303追記-勝手にUSBだけスイッチされてしまう事がある問題その2">(2024/03/03追記) 勝手にUSBだけスイッチされてしまう?事がある問題その2</h2> <p>問い合わせをしたところ「簡易放電をする(ケーブル類を抜いたあと電源ボタンを10秒長押し)」または「工場リセット」で改善するというふうに案内されました。</p> <p>そこで「簡易放電」「工場リセット」それぞれで1-2週程度使ってみたところ、どちらのケースも施行直後は発生頻度が圧倒的に減り(一週間程度は発生しない)、その後徐々に発生率が上がっていく(酷いと日に二回発生する)ということがわかりました。 思えば購入直後も特に問題にならなかったため、連続起動時間に依存した問題?のような感じがします。</p> <p>また、簡易放電もケーブル類を抜かずに電源長押しして再起動するだけでも改善することがわかりました。長押し後の起動時にDellのスプラッシュが表示されるため、内部的にスタンバイではなく再起動的な動きをしていそうです。</p> <p>いったんいつかファームウェアアップデートで改善することを祈りながら、週一くらいで電源長押しでリセットすることを意識すれば、問題なく使えそうではありました。</p> manaten 2023年のskeb活動まとめ hatenablog://entry/6801883189071393567 2023-12-31T23:32:41+09:00 2023-12-31T23:33:10+09:00 2023年は、Skeb活動の方針 - MANA-DOT を書き、skeb活動の方針を明確にするとともに活動再開をしました。幸いなことに何件かリクエストを頂けたので、2023年の活動まとめとしてこのエントリを書きます。 https://skeb.jp/@manaten 2023年9月 SNSアイコン向けのナマズ (fukutommyさん) 前職同僚の @fuktommy さんから、skeb活動再開のエントリを書いてしばらくして頂いたリクエストです。SNSアイコンで使えるナマズのキャラクターということでした。SNSアイコンということである程度オリジナリティがあったほうが良さそうと思い、こっそり本人… <p>2023年は、<a href="https://blog.manaten.net/entry/skeb">Skeb活動の方針 - MANA-DOT</a> を書き、skeb活動の方針を明確にするとともに活動再開をしました。幸いなことに何件かリクエストを頂けたので、2023年の活動まとめとしてこのエントリを書きます。</p> <p><a href="https://skeb.jp/@manaten">https://skeb.jp/@manaten</a></p> <h1 id="2023年9月">2023年9月</h1> <h2 id="SNSアイコン向けのナマズ-fukutommyさん">SNSアイコン向けのナマズ (fukutommyさん)</h2> <p><img src="https://manaten.net/wp-content/uploads/2023/12/20230916_namazu.gif" alt="namazu" /></p> <p>前職同僚の <a href="https://twitter.com/fuktommy">@fuktommy</a> さんから、skeb活動再開のエントリを書いてしばらくして頂いたリクエストです。SNSアイコンで使えるナマズのキャラクターということでした。SNSアイコンということである程度オリジナリティがあったほうが良さそうと思い、こっそり本人にDMで好きな色を聞いて、本人の顔も知っているので少し寄せて描いてみました。</p> <p>この手の依頼だと、ただの作画だけでなく「キャラクターデザイン」の作業も兼ねることになるため、面白い反面何度は上がるなという学びを得ました。</p> <h1 id="2023年10月">2023年10月</h1> <h2 id="飼い猫ちゃんのドット絵">飼い猫ちゃんのドット絵</h2> <p>非公開希望のため画像は載せられませんが、ノルウェージャンフォレストキャットの飼い猫ちゃんのドット絵を描きました。</p> <p>リアルにあるものをドット絵に + もふもふの猫種ということでこれまた難しいリクエストだったと記憶してますが、このときはこの一つだけリクエストを受けていたためゆったりと描いていた記憶です。</p> <h1 id="2023年11月">2023年11月</h1> <h2 id="小桜こもも様">小桜こもも様</h2> <p><img src="https://manaten.net/wp-content/uploads/2023/12/20231105_comomo_czkr.gif" alt="小桜こもも様" /></p> <p>個人vtuberの <a href="https://twitter.com/comomo_czkr">小桜こもも様</a> のドット絵です。Twitterを見たところ魔法少女がお好きなようでしたので、それっぽいモーションを2つ追加しています。 また、髪型に2バリエーションあったのでマイナーチェンジと言う形で両方のバリエーションを用意しました。個人的にはショートが好み。インコモチーフのカラフルなヘアスタイルが一番の特徴であり、ドット絵表現が難しいところです。</p> <p>キャラクターものは年初に書いた <a href="https://blog.manaten.net/entry/firststar-hateno-pixelart">一番星はてのちゃん</a> スタイルが、自分で描いてて好みだったので、指定がなければこのスタイルを踏襲しようと決めていたのですが、動物系の依頼が多くてようやく描けたという感じでもありました。また、モーションも1モーションだと寂しいので最低3モーション描こうと決めており、実践した形になります。なおこの縛りはこのあと自分の首を絞める事になります。</p> <h1 id="2023年12月">2023年12月</h1> <p>小桜こもも様の納品をした翌日、4つのリクエストが来たため11月後半~12月にかけて4つ制作していました。上記の通り、1キャラクターあたり3モーション描くと決めていたので、この縛りで非常に時間がかかってしまいました・・・。すべて人間の女の子キャラクターだったので、はてのちゃんスタイルで描いてみてます(skebのリクエストでも、特に海外の方から 「I like your style」というコメントを頂くので、需要はありそう、ではある)。</p> <h2 id="リシャ様">リシャ様</h2> <p><img src="https://manaten.net/wp-content/uploads/2023/12/20231113_lisha_bunny.gif" alt="リシャ様" /></p> <p>海外のイラストレーターの <a href="https://skeb.jp/@lisha_bunny">リシャ様</a> のアバター?です。紫のショートヘアと垂れたうさ耳が特徴の宇宙飛行士とのことです。</p> <p>眠るのが好きということで眠るモーションと、うさぎ+宇宙飛行士なので飛び跳ねて喜ぶモーションを追加しています。</p> <h2 id="Moshi-Mui様">Moshi Mui様</h2> <p><img src="https://manaten.net/wp-content/uploads/2023/12/20231113_MoshiMui.gif" alt="Moshi Mui様" /></p> <p>英語圏Vtuberの <a href="https://twitter.com/MoshiMui">Moshi Mui様</a> のドット絵です。 茶髪の中に紫のアクセントが特徴的な髪型ですが、ドット絵の苦手な表現ではあります。</p> <p>2モーション追加する縛りを貸していたのですがどういう活動をされているのかよく知らなかったため、汎用的に使えそうな手を振るモーションと、Xでラーメンの画像をよく見かけたのでラーメンを食べるモーションを追加しています。</p> <h2 id="れすと様-ランスシリーズのリセットちゃん">れすと様 (ランスシリーズのリセットちゃん)</h2> <p><img src="https://manaten.net/wp-content/uploads/2023/12/20231113_rest3d_resets.gif" alt="れすと様 (ランスシリーズのリセットちゃん)" /></p> <p><a href="https://twitter.com/rest3d_resets">れすと様</a> より、ランスシリーズのリセットちゃんのリクエストです。地味に版権のリクエストは初めてですね(もっと挑戦したい)。</p> <p>ランスシリーズはよく知らなかったのですが、主人公ランスの娘で父譲りのガハハ笑いをするキャラクターのようで、ガハハ笑いがリクエストだったので、通常モーションとガハハ笑いモーションを描いています。</p> <p>3つ目のモーションは、こちらもよく知らないのですが参考資料からリンクされていたリセットちゃんのアニメーションのモーションをドット絵化したものです。地味にこのモーションのために、リセットちゃんは他のリクエストよりもコマ数が多く、時間もかかっています。キャラクターの詳細が多いほど描きたいものが増える、仕方ないね。</p> <h2 id="匿名様">匿名様</h2> <p>もう一件、匿名希望の方からオリジナルキャラクターのドット絵をリクエストされて描いています。 狐モチーフで狐耳とオレンジツートンのショートヘアが可愛らしい女の子でした。</p> <h1 id="さいごに">さいごに</h1> <p>2023年は転職をし、エンジニアマネージャではなく一般エンジニアの立場に戻ったわけですが、労働時間が大きく変わらないのに精神的余裕はできたのか、しばらくできてなかった創作活動を再開したいと思える余裕ができ、そのリハビリ・練習的な意味も含めてのSkeb再開でした。</p> <p>いざ初めて見てわかったのは、そもそもSNSで創作活動をほとんどしてない自分の場合、Skebでリクエストを受け付けていてもめったにリクエストは来ず、納品してトップに上がったタイミングで複数リクエストが来るのだということでした。実際今日の納品を終えたあとにも3リクエスト来ています。</p> <p>個人的には自分の趣味のキャラクターばかり描くより、色んなキャラクターを描いたほうが経験値が貯まると思っているため、たくさんリクエストをいただけるのはありがたいと思える反面、結構な時間を費やしてしまうのでうまく流用をコントロールしたいところです。</p> <p>本当は1キャラ3モーションを4時間程度で描きたい(し、最初はかけると思っていた)のですが、いざ描き始めるとこだわってしまったり、モーションがなかなか決まらなかったりしてトータルで8時間程度かかってしまうというところでした。このあたりは慣れで改善していきたい。</p> <p>2024年もよろしくお願いします。</p> manaten 2023年のmanatenのフロントエンド hatenablog://entry/6801883189071376458 2023-12-31T22:35:57+09:00 2023-12-31T22:44:10+09:00 この記事は? フロントエンドの(に限らずですが)技術はよく言われるように移り変わりが早いです。とはいえ個人で全て追い切るのは難しく、また必ずしも新しいものを使い続けていればいいわけではないとも思います。個人的には新しい技術であっても古い技術であっても、きちんと自分の中に技術採用の軸があることが大事だと思っています。 このエントリではmanatenが2023年末現在でフロントエンド(周辺)でアプリケーション開発をするときに、利用することが多い技術を概要や理由付きで挙げていきます。 (なお年の瀬に思い出して書いてるため、抜けがあったり内容いい加減だったりすると思います。) <h1 id="この記事は">この記事は?</h1> <p>フロントエンドの(に限らずですが)技術はよく言われるように移り変わりが早いです。とはいえ個人で全て追い切るのは難しく、また必ずしも新しいものを使い続けていればいいわけではないとも思います。個人的には新しい技術であっても古い技術であっても、きちんと自分の中に技術採用の軸があることが大事だと思っています。</p> <p>このエントリではmanatenが2023年末現在でフロントエンド(周辺)でアプリケーション開発をするときに、利用することが多い技術を概要や理由付きで挙げていきます。</p> <p>(なお年の瀬に思い出して書いてるため、抜けがあったり内容いい加減だったりすると思います。)</p> <h1 id="今後も使っていくもの">今後も使っていくもの</h1> <h2 id="ライブラリ">ライブラリ</h2> <h3 id="React"><a href="https://react.dev/">React</a></h3> <ul> <li>2023年時点である程度以上複雑なフロントエンドをコンポーネント指向+型安全に開発する上でベターな選択肢だと思います。</li> <li>コンポーネント=JSX≒JSのため、通常のJS開発におけるアセットをほぼそのまま使えるのが、vueなどの競合と比較したときのメリットだと思っています。 <ul> <li>コンポーネント単位=JSのモジュール単位、コンポーネント=JSの関数(+JSX)というところで、一般的なjs開発に慣れている場合無理なくコンポーネント指向で開発できる</li> <li>eslintによるコードスタイル統一をコンポーネントも含めて行うことができる</li> <li>TypeScriptの支援で、コンポーネントやhookの型をかなり厳密に指定することができる</li> </ul> </li> <li>Contextやhookでの依存注入は理解するまでの癖は強いものの、理解すればシンプルにViewとLogicを分離できるため、開発速度に貢献してくれていると思います。</li> <li>React Server Components(RSC)でSSRへの回帰の流れにもしっかり乗ろうとしているのも、まだしばらくはReactは廃れないだろうと思わせてくれます <ul> <li>フロントエンドライブラリは「JAMStack寄りの技術スタックで、ロジックをAPIサーバー、Viewをフロントエンド」の流れから、徐々に「SSRして必要なインタラクションをクライアントで」の流れに回帰しつつあると感じます。後で触れるsvelteやsolid.jsはそういったライブラリだと思っています <ul> <li>パフォーマンス・UX面もですが、そもそも「クライアント側ですべての計算をする」というのが富豪的な(もっというと、コンポーネント指向で開発したいという開発者体験優先の)アプローチであるというふうに以前から個人的には考えており、「可能なものはなるべくビルド時に、難しいものはサーバーサイドで計算」というアプローチのほうが優れていると感じていました(ただそのためにReactの開発体験は損ないたくはない)</li> <li>RSCで従来のReactの開発者体験で古典的なテンプレートエンジン的なUI開発を行い、必要に応じてClient Componentによるインタラクションを注入する、というスタイルはこの思想上かなり合理的だと思っており、徐々に主流になるのかなと感じています。</li> </ul> </li> </ul> </li> </ul> <h3 id="Nextjs-App-Router--Server-Components--Server-Actions"><a href="https://nextjs.org/">Next.js (App Router / Server Components / Server Actions)</a></h3> <ul> <li>Reactで説明したSSRへの部分的回帰の流れに、完成度高く実装しているフレームワークの一つだと思います。 <ul> <li><a href="https://blog.manaten.net/entry/runtime-env-for-client">Next13 の AppRouter で実行時の環境変数をクライアントで取り扱う</a> などでも触れましたが、「コンポーネントをサーバーサイドでrender → ルーティング時にコンポーネント差分だけクライアントで差し替え、コンテキストも引き継ぎ」という挙動をするため、古典的なSSR的コードを記述しつつ、実際には差分しか計算しないというのが(複雑なものの)良いです</li> <li>他に完成度高いフレームワークがあれば、必ずしもNext.jsである必要はないのかなとも思います</li> </ul> </li> <li>キャッシュありきの戦略が実装を逆に難しくしてしまっている側面があるのは否めないです</li> <li>React Server Actionsが動くフレームワークとしても重要です(ただし現時点でバグもある) <ul> <li>API(ロジック)サーバー - フロントサーバー - クライアント という構成の場合、従来だとAPIサーバーの処理をクライアントが呼び出すためのラッパー的なエンドポイントをフロントサーバーにcustom routingなどで実装する必要があり、そのために「クライアント向けのAPIクライアント」「custom routingでのエンドポイント実装」「呼び出し感で型安全にするための何らかの仕組み」をわざわざ用意する必要がありました</li> <li>Server Actionsが利用可能な場合、単にAPIサーバーを呼び出すServer Actionsを実装すれば良いため、非常に簡潔に、かつ型安全が保証される形で実装することができます <ul> <li>これを関数を書くだけで実現できるため、開発速度も向上します</li> <li>ただし、実際は任意のパラメータでリクエストできうるため、Server Actionsには型チェックやエラーハンドリングしてResult型で返却する共通ラッパーを用意しておくのがおすすめです。</li> </ul> </li> </ul> </li> <li>Webpack等のビルドツールチェインを隠蔽してくれているのも大事な性質で、自前のconfigをかなり減らしてくれます。</li> <li>Vercelの動き自体は不安もあります <ul> <li>Next14でServerActionsは安定版となりましたが、以前致命的なバグが有ったり(ブラウザバック後のServer Actionsの呼び出しが、コード上で帰ってこなくなるバグを確認し、未だに修正されていない <a href="https://github.com/vercel/next.js/issues/56811">vercel/next.js/issues/56811</a>)、他にもApp Routerのキャッシュが前衛的すぎて、実用的なアプリケーションを作る上で確実にキャッシュを飛ばすのが難易度が高いなど、実装&amp;リリース優先で安定化が二の次の体制なのではという不安があります。</li> <li>ただ、個人的にはワークアラウンドでカバー可能な範疇で、それ以上に開発速度が上がる側面が大きいと感じています。同機能で安定性が高いフレームワークがあれば乗り換える可能性はありそう。</li> </ul> </li> </ul> <h3 id="storybook"><a href="https://storybook.js.org/">storybook</a></h3> <ul> <li>フロントエンドコンポーネントのテストかわりにとりあえず書く、というのが癖になっています。デザイナーとコンポーネント単位の認識を視覚的に揃え、競業するためにも重要だと思っています。</li> <li>storybookの代替ライブラリというのは聞いたことがないため、まだしばらく一線級なのかなと思います。</li> </ul> <h3 id="React-hook-form"><a href="https://react-hook-form.com/">React-hook-form</a></h3> <ul> <li>フォーム作る場合に思考停止で入れるライブラリです。</li> <li>あまり競合を調べられていませんが、特に不自由を感じていないため利用し続けています</li> <li>後述のzodと組み合わせてバリデーションをすると快適です。また、フォームのJSXを記述したあとにcopilotに任せることでスキーマ自体も90%できた状態で生成してくれるため、非常に楽ちんです。</li> </ul> <h3 id="zod"><a href="https://zod.dev/">zod</a></h3> <ul> <li>独自のオブジェクトでスキーマを定義し、バリデーションするためのライブラリです。</li> <li>オブジェクトの不要フィールドを消す、曖昧な型変換をするなどの痒いところに手が届く機能も揃っており、更にTypeScriptとの親和性も高い(スキーマを定義したらそこから型定義も生成できる)です。</li> <li>スキーマライブラリもいくつか競合がある認識ですが、zodで特に不便を感じていないため比較したことないです</li> </ul> <h3 id="css-modules">css-modules</h3> <ul> <li>2022年はcss-in-jsのemotionや、emotionベースのChakraUIサイコーと思っていたんですが、転職後css-modulesが採用されていたことや、結局SSRに回帰するのであればcssが静的に出力できたほうが優位であることから2023年はcss-modulesを書いていた事が多いです。</li> <li><a href="https://github.com/mizdra/happy-css-modules">mizdra/happy-css-modules</a> を導入することで型安全に開発することができます</li> <li>コンポーネント指向開発におけるcssどうするのか?はまだ答えが出きってないと感じるため、発明があれば置き換わるのかもしれません</li> </ul> <h3 id="ky"><a href="https://github.com/sindresorhus/ky">ky</a></h3> <ul> <li>2022年は <a href="https://github.com/axios/axios">axios</a> を使っていましたが、今年からkyに乗り換えました</li> <li>ReactがfetchをラップしてRequest memorizationを行うため、XHRベースのライブラリよりfetchベースのライブラリのほうが今後は良いだろうと考えたためです。</li> <li>素のfetchで良いという方もいると思いますが、素のfetchは少しインタフェースが使いづらいため、重宝します</li> <li>openAPIのスキーマが提供されている場合、後述のopenapi-typescriptで型安全に扱うためのラッパーを用意して使うことが多いです。</li> </ul> <h3 id="openapi-typescript"><a href="https://www.npmjs.com/package/openapi-typescript">openapi-typescript</a></h3> <ul> <li>openAPIのjson/yamlから、TypeScriptの型定義を生成してくれるライブラリです <ul> <li>ただしそのままだと扱いづらいため、 <a href="https://zenn.dev/micin/articles/openapi-typescript-with-type-puzzles?redirected=1">openapi-typescriptと型パズルで作るREST APIクライアント</a> を参考に、「パス+メソッド → パラメータやレスポンスなどの型情報」なUtil型を用意して使うことが多いです</li> <li>kyをラップして用意したfetch関数でURLとメソッドのエディタ補完が効き、なおかつURLとメソッドが指定されるとパラメータの型が要求され、レスポンスが厳密になるというふうに実装できるため、非常に快適です</li> </ul> </li> <li>openAPIとTypeScriptの橋渡しをするライブラリはいくつか存在しますが、ほとんどが「型定義をもとに独自のクライアントを生成してくれる」というものので、若干使いづらいクライアントが生成されることが多いため、型定義のみ生成してクライアントは自前で実装できるこのライブラリを気にいっています。</li> </ul> <h2 id="ツールチェイン">ツールチェイン</h2> <h3 id="TypeScript"><a href="https://www.typescriptlang.org/">TypeScript</a></h3> <ul> <li>ごく簡単なスクリプトを書くケース以外では基本的にTypeScriptを使っています <ul> <li>ごく簡単でもjsconfigを用意して、ts-checkしながら書くことが多いです <ul> <li><a href="https://blog.manaten.net/entry/ts-check-for-legacy">レガシーなjs環境におすすめなts-checkコメントについて - MANA-DOT</a></li> </ul> </li> </ul> </li> <li>漸進的型付け+柔軟なjsオブジェクト+Type Guardの快適性に勝る静的型付け言語を知らないので(真面目にサーベイしたわけではないです)、フロントエンド以外でも常に開発の第一候補として愛用しています。</li> </ul> <h3 id="prettier--editorconfig"><a href="https://prettier.io/">prettier</a> / <a href="https://editorconfig.org/">editorconfig</a></h3> <ul> <li>リポジトリルートに .prettierrc / .editorconfig を設置し、<a href="https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode">Prettier</a> 、 <a href="https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig">EditorConfig for VS Code</a> をVSCodeに入れておくだけでもコードスタイルを統一できるので、リポジトリを新規作成したときは思考停止で設置するのが良いです</li> <li>スタイルは、リポジトリ内で統一されていれば強い希望はないですが、個人開発の場合はスペース2、クオートはダブルクオートが好みです。</li> </ul> <h3 id="eslint"><a href="https://eslint.org/">eslint</a></h3> <ul> <li>js/tsのプロジェクトにはとりあえず導入して、コード構造を強制します。 <ul> <li>vscodeの設定で、保存時にautofixをかけるようにできるため、autofixを当てにして適当に記述 → 保存してfix というコーディングがすっかり染み付いています</li> <li>copilotに生成させたコードも同じく保存時に統一させます</li> </ul> </li> <li>最近気づきましたが <a href="https://eslint.org/docs/latest/rules/no-restricted-imports">no-restricted-imports</a> が便利で、このルールをカスタマイズしておくと、「腐敗防止層をプロジェクト内で実装しているため、直接importしてほしくないライブラリを禁止」とか「nextのuseRouterがPageRouter用とAppRouer用で2つあるため、間違った方を禁止」などを実現することができます。</li> </ul> <h3 id="eslint-plugin-import"><a href="https://www.npmjs.com/package/eslint-plugin-import">eslint-plugin-import</a></h3> <ul> <li>主にimport-orderをautofixさせるために重宝しています。 <ul> <li>区分ごとにアルファベット順にimportを並ばせることができます。複数人開発でimportがぐちゃぐちゃになることを防げるため便利。</li> </ul> </li> <li>設定を都度カスタマイズする理由はないので、 <a href="https://github.com/manaten/ts-node-cli-template/blob/main/.eslintrc.cjs#L24-L52">manaten/ts-node-cli-template/blob/main/.eslintrc.cjs#L24-L52</a> を毎回コピペしています。 <ul> <li>cssを最後にさせることで、カスケード順が狂わないようにするのを気にいっています。</li> </ul> </li> </ul> <h3 id="stylelint"><a href="https://stylelint.io/">stylelint</a></h3> <ul> <li>css-moduleを利用しているため、stylelintでそのままlintできます。</li> <li>とりあえず入れておけば初歩的なミスにきづけるため、思考停止で導入。</li> </ul> <h3 id="stylelint-config-recess-order"><a href="https://www.npmjs.com/package/stylelint-config-recess-order">stylelint-config-recess-order</a></h3> <ul> <li>stylelintのプラグインで、プロパティの定義順を並べ替えてくれるものはいくつかありますが、これはrecessという順序規則に基づいて並べ替えてくれるものです</li> <li>単なるアルファベット順だと、意味的に直感に反する順序になってしましますが、このプラグインは 「親コンポーネント上の位置系のプロパティ(positionなど)」「要素の性質を決めるプロパティ(displayなど)」「要素の見た目を決めるプロパティ(paddingやborderなど)」という順で並べてくれるため、直感的で気に入っています。</li> </ul> <h3 id="husky"><a href="https://github.com/typicode/husky">husky</a></h3> <ul> <li>npmプロジェクトにインストールしておくと、git commitをhookして、コミット時に追加の処理を行い、失敗したらコミットを失敗させてくれるツールです。</li> <li>prettier、eslint、stylelintと、後述のimagemin-lint-stagedを設定することが多いです。後述のlint-staged経由で実行します。</li> <li>基本的にはVSCodeの保存時のfixで事が足りてしまうのですが、二重でチェックできるため安心できるので重宝しています</li> </ul> <h3 id="lint-staged"><a href="https://www.npmjs.com/package/lint-staged">lint-staged</a></h3> <ul> <li>gitのstagingにあがっているファイルのみに処理をしてくれるツールです。</li> <li>huskyと組み合わせて、コミット時に変更があったファイルのみに処理をする用途で利用します。</li> </ul> <h3 id="imagemin-lint-staged"><a href="https://www.npmjs.com/package/imagemin-lint-staged">imagemin-lint-staged</a></h3> <ul> <li>lint-stagedと組み合わせて、コミット前に画像の最適化をかけてくれるツールです。</li> <li>かつては自前で実装してましたが、ズバリのパッケージが公開されたため今はこちらを使っています <ul> <li><a href="https://blog.manaten.net/entry/precommit-imagemin">husky + lint-stagedでgitのprecommit時にimageminを行い、minifyした画像のみコミットされるようにする - MANA-DOT</a></li> </ul> </li> </ul> <h2 id="エディタ">エディタ</h2> <h3 id="VSCode"><a href="https://code.visualstudio.com/">VSCode</a></h3> <ul> <li>実質js開発の標準です。他にはWebStorm派やVim派がいるのでしょうか?</li> <li>拡張が多く、動作が軽快なのがメリットです。 <ul> <li>また、拡張を自分で開発するのも、jsで記述ができるためフロントエンジニアには難易度低めです。</li> </ul> </li> <li>また、copilotによる支援が便利(これはVSCodeに限らないのかも)で、storybookやテストコードなどの反復処理をとりあえず実装させて人間が細かい手直しをするという使い方が、すべて人間が実装するよりも圧倒的に時短(そして精神的負担も減る)になると思っています。</li> </ul> <h3 id="auto-snippet"><a href="https://marketplace.visualstudio.com/items?itemName=Gruntfuggly.auto-snippet">auto-snippet</a></h3> <ul> <li>VSCodeの拡張で、拡張子に対して特定のスニペットをファイル生成時に自動適用してくれる拡張です。</li> <li>*.tsx ファイルの場合にプロジェクトで使うReactのスニペットを自動展開するなどの用途で使っており、コードスタイルの統一と時短に貢献してくれます。</li> <li><code>.vscode/settings.json</code> ****と <code>.vscode/snippets.code-snippets</code>をプロジェクトにコミットしておくことで、コードスタイルをある程度チーム感で共有できます。</li> </ul> <h2 id="フロント以外">フロント以外</h2> <h3 id="type-orm--sqlite3"><a href="https://typeorm.io/">type-orm</a> + sqlite3</h3> <ul> <li>個人プロダクトでデータストアが手軽にほしいときに便利な選択肢です。</li> <li>TypeOrmはPrismaと競合だと思いますが、最小限の設定を記述したあとはEntityのクラス定義を実装するだけでテーブル作成からCRUDがすぐできるようになるため、手軽に利用したいときにより便利です。 <ul> <li><a href="https://github.com/manaten/sqlite-prisma-vs-typeorm-example">manaten/sqlite-prisma-vs-typeorm-example</a></li> </ul> </li> </ul> <h3 id="ChatGPT"><a href="https://chat.openai.com/">ChatGPT</a></h3> <ul> <li>命名の相談も、TypeScriptの難しい型パズルも、ドット絵描くときのポーズ相談も何でもお願いしています <ul> <li><a href="https://blog.manaten.net/entry/chatgpt-benri">個人的におすすめのchatGPT用法 (ぬけもれ探し・意味から逆引き・雑学のpush) - MANA-DOT</a></li> </ul> </li> <li>2023年度月額払いすべきサブスクリプションサービス一位だと思っています。</li> </ul> <h1 id="今後試してみたいもの">今後試してみたいもの</h1> <h3 id="strapi"><a href="https://strapi.io/">strapi</a></h3> <ul> <li>転職後の職場で利用されていたHeadlessCMSです。クラウドが用意されている他、npmのライブラリとしてオンプレミスでも動作するのがメリットです。</li> <li>軽く触ってみたところ、できが非常に良く、CLIでデータストアを指定したらすぐ使える(しかもsqliteもサポートしているため、DBすら用意しなくてもとりあえず試せる)、ミニマルな管理ツールでスキーマ定義とエンティティ管理ができるのがメリットに感じました。</li> <li>単純なデータ構造でREST APIを定義・実装して工数をかけるというのは少し前時代的に思っており、今後は出来のいいHeadless CMSで実現できるところはCMS上でスキーマ定義+ホスティング、というのも選択肢になりそうだなと思っています。</li> </ul> <h3 id="hono--Cloudflare-Workers"><a href="https://hono.dev/">hono</a> (+ <a href="https://developers.cloudflare.com/workers/">Cloudflare Workers</a>)</h3> <ul> <li>同僚複数人が別の文脈でおすすめしていたため気になっています。</li> <li>Cloudflare製品は最近勢いがあると感じます。AWSなどと比べるとエンタープライズ用途では若干勇気が必要な選択肢の印象はあるものの、個人ユースだと積極的に使いたい感じ。</li> </ul> <h3 id="Svelte-or-Solidjs"><a href="https://svelte.dev/">Svelte</a> or <a href="https://www.solidjs.com/">Solid.js</a></h3> <ul> <li>RSCところで書いた文脈で、ビルドタイム・SSR回帰の文脈で、現状有力な対抗馬としてのこれらのライブラリも気になっています。</li> <li>ただ、開発体験としてはReactで完成してしまっているため、趣味の気軽な開発でなかなか手を出す機会がないのが悩みどころ。</li> </ul> <h1 id="参考リンク">参考リンク</h1> <ul> <li><a href="https://github.com/manaten/ts-node-cli-template">manaten/ts-node-cli-template</a></li> <li><a href="https://github.com/manaten/nextjs-template">manaten/nextjs-template.</a></li> </ul> manaten JavaScriptを使ったアニメーションgifの切り出しと拡大 hatenablog://entry/820878482974399057 2023-10-09T22:07:26+09:00 2023-10-09T22:17:29+09:00 自分の場合ドット絵のちびキャラを複数バリエーション書くとき、以下の画像のようにバリエーションを縦に並べて書いていく事が多いです(画像はskebでリクエストを頂いたナマズさんです)。 このような形式で描き、画像をバリエーションずつに分割し更にSNSアイコンとして利用しやすいよう5倍サイズにしてから納品しています。 これをバリエーションが多い場合に手作業でやるのは面倒ですので、今回は簡単なNode.jsスクリプトで分割・拡大を自動化する方法を紹介します。 <p>自分の場合ドット絵のちびキャラを複数バリエーション書くとき、以下の画像のようにバリエーションを縦に並べて書いていく事が多いです(画像は<a href="https://blog.manaten.net/entry/skeb">skeb</a>でリクエストを頂いた<a href="https://skeb.jp/@manaten/works/3">ナマズさん</a>です)。</p> <p><img src="https://manaten.net/wp-content/uploads/2023/10/namazu.gif" alt="namazu" /></p> <p>このような形式で描き、画像をバリエーションずつに分割し更にSNSアイコンとして利用しやすいよう5倍サイズにしてから納品しています。 これをバリエーションが多い場合に手作業でやるのは面倒ですので、今回は簡単なNode.jsスクリプトで分割・拡大を自動化する方法を紹介します。</p> <h1 id="ライブラリの候補">ライブラリの候補</h1> <p>自分はJavaScript使いなのでnpmパッケージで提供されているものを選びます。その際にポイントとなるのは以下の点です。</p> <ul> <li>簡単な画像加工をスクリプト的に記述する用途で利用したいため、高機能でインタフェースが複雑なものよりは、 <strong>機能が簡単なAPIで提供されている</strong> ものが望ましい</li> <li><strong>アニメーションGIFを簡単に扱える</strong> ことが望ましい</li> <li>(できれば)ちゃんとメンテされていてほしい</li> </ul> <p>条件を満たすライブラリとして以下の候補があります。</p> <h2 id="Jimp">Jimp</h2> <p><a href="https://github.com/jimp-dev/jimp">jimp-dev/jimp: An image processing library written entirely in JavaScript for Node, with zero external or native dependencies.</a></p> <p>npmで簡単にインストールでき、今回利用したいcrop、scaleなどの画像加工メソッドがシンプルなAPIで提供されています。また、メソッドチェーンで複数の修正を連続して適用することができます。 読み込んだ画像から1つ目のバリエーションを取り出して5倍にする場合、以下のようなコードで実現できます。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">import</span> Jimp from <span class="synConstant">&quot;jimp&quot;</span>; <span class="synStatement">const</span> image = await Jimp.read(<span class="synConstant">&quot;namazu.gif&quot;</span>); await image .crop(0, 0, 48, 48) .scale(5, Jimp.RESIZE_NEAREST_NEIGHBOR) .writeAsync(<span class="synConstant">&quot;out/namazu_1x5.gif&quot;</span>); </pre> <p><code>scale</code> の第二引数で <code>Jimp.RESIZE_NEAREST_NEIGHBOR</code> を指定することで、nearest neighbor 法で拡大することができます。 ドット絵を拡大する場合はピクセルがボケてしまうため、nearest neighbor 法で拡大することが必須です。</p> <p>ただし、デフォルトではアニメーションGIFに対応しておらず、上記コードの出力結果は静止画になってしまいます。</p> <p>アニメーションGIFを利用したい場合は、 <a href="https://github.com/jimp-dev/jimp/issues/166#issuecomment-353149458">issue</a> で提示されている <a href="https://github.com/jimp-dev/gifwrap/tree/master">jimp-dev/gifwrap</a> を利用することになります。 gifwrapを利用してアニメーションGIFを切り出す場合は以下のようになります。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">import</span> Jimp from <span class="synConstant">&quot;jimp&quot;</span>; <span class="synStatement">import</span> <span class="synIdentifier">{</span> BitmapImage, GifFrame, GifUtil <span class="synIdentifier">}</span> from <span class="synConstant">&quot;gifwrap&quot;</span>; <span class="synStatement">const</span> image = await GifUtil.read(<span class="synConstant">&quot;namazu.gif&quot;</span>); await GifUtil.write( <span class="synConstant">&quot;out/namazu_1x5.gif&quot;</span>, image.frames.map((frame) =&gt; <span class="synIdentifier">{</span> <span class="synStatement">const</span> j = GifUtil.copyAsJimp(Jimp, frame) .crop(0, 0, 48, 48) .scale(5, Jimp.RESIZE_NEAREST_NEIGHBOR); <span class="synStatement">return</span> <span class="synStatement">new</span> GifFrame(<span class="synStatement">new</span> BitmapImage(j.bitmap), <span class="synIdentifier">{</span> ...frame <span class="synIdentifier">}</span>); <span class="synIdentifier">}</span>), image, ); </pre> <p>gitwrapの提供するGifUtilでファイルを読み込むと、アニメーションgifの各フレームを読み取れるので、それらをjimpで加工し、再度frameに書き戻してあげることでアニメーションgifを切り出すことができます。 少し冗長ですが、複数フレーム持つアニメーションgifを扱う都合上仕方ないような気もします。</p> <h2 id="Sharp">Sharp</h2> <p>実は今回、以上のような内容でJimpの紹介記事を書こうと思っていたのですが、記事を書くために改めてJimpについて調べていたところ、sharpというライブラリも見つけました。</p> <p><a href="https://sharp.pixelplumbing.com/">sharp - High performance Node.js image processing</a></p> <p>こちらは公式のReadmeでのアピールポイントは「高速であること」なのですが、加えて「 <strong>アニメーションgifをデフォルトでサポートしている</strong> 」という今回のユースケースでとても大きな利点がありました。sharpを使って同様に画像を切り出す例は以下のようになります。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">import</span> sharp from <span class="synConstant">&quot;sharp&quot;</span>; await sharp(<span class="synConstant">&quot;namazu.gif&quot;</span>, <span class="synIdentifier">{</span> animated: <span class="synConstant">true</span> <span class="synIdentifier">}</span>) .extract(<span class="synIdentifier">{</span> left: 0, <span class="synStatement">top</span>: 0, width: 48, height: 48 <span class="synIdentifier">}</span>) .resize(<span class="synIdentifier">{</span> width: 48 * 5, kernel: sharp.kernel.nearest <span class="synIdentifier">}</span>) .toFile(<span class="synConstant">&quot;out/namazu_1x5.gif&quot;</span>); </pre> <p>アニメーションgif をデフォルトでサポートしているおかげで、 <code>{ animated: true }</code> を指定するだけでアニメーションgifを取り扱うことができました。ただし、APIはjimpと異なりやや冗長であり、特にjimpのような <code>scale</code> メソッドは存在しないため、自分でサイズ計算をする必要があります。 とはいえgifwrapを利用しないとアニメーションgifを取り扱えなかったjimpと比べてかなりシンプルに記述することができました。</p> <h1 id="Sharpを使った画像の切り出し例">Sharpを使った画像の切り出し例</h1> <p>最後に、冒頭のアニメーションgifを等倍と5倍でそれぞれ切り出す例を紹介します。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">import</span> sharp from <span class="synConstant">&quot;sharp&quot;</span>; <span class="synStatement">const</span> image = sharp(<span class="synConstant">&quot;namazu.gif&quot;</span>, <span class="synIdentifier">{</span> animated: <span class="synConstant">true</span> <span class="synIdentifier">}</span>); await image .clone() .extract(<span class="synIdentifier">{</span> left: 0, <span class="synStatement">top</span>: 0, width: 48, height: 48 <span class="synIdentifier">}</span>) .toFile(<span class="synConstant">&quot;out/namazu_1.gif&quot;</span>); await image .clone() .extract(<span class="synIdentifier">{</span> left: 0, <span class="synStatement">top</span>: 0, width: 48, height: 48 <span class="synIdentifier">}</span>) .resize(<span class="synIdentifier">{</span> width: 48 * 5, kernel: sharp.kernel.nearest <span class="synIdentifier">}</span>) .toFile(<span class="synConstant">&quot;out/namazu_1x5.gif&quot;</span>); await image .clone() .extract(<span class="synIdentifier">{</span> left: 0, <span class="synStatement">top</span>: 48, width: 48, height: 48 <span class="synIdentifier">}</span>) .toFile(<span class="synConstant">&quot;out/namazu_2.gif&quot;</span>); await image .clone() .extract(<span class="synIdentifier">{</span> left: 0, <span class="synStatement">top</span>: 48, width: 48, height: 48 <span class="synIdentifier">}</span>) .resize(<span class="synIdentifier">{</span> width: 48 * 5, kernel: sharp.kernel.nearest <span class="synIdentifier">}</span>) .toFile(<span class="synConstant">&quot;out/namazu_2x5.gif&quot;</span>); </pre> <p><img src="https://manaten.net/wp-content/uploads/2023/10/extracted.gif" alt="namazu" /></p> <p>それぞれ等倍と5倍で切り出すことができました。</p> <h1 id="おまけ-なぜドット絵のバリエーションを縦に並べるのか">おまけ: なぜドット絵のバリエーションを縦に並べるのか</h1> <p>最後に自分がドット絵アニメーションを描くときのプチテクニックの紹介なのですが、複数バリエーションのアニメーションを描くときになぜ今回のように縦に並べて描くかについて説明します。</p> <p><img src="https://manaten.net/wp-content/uploads/2023/10/namazu.gif" alt="namazu" /></p> <p>アニメーションを描くときは自分は<a href="https://takabosoft.com/edge2">EDGE2</a>を使っています。世間的には<a href="https://www.aseprite.org/">Aseprite</a>も人気だと思います。これらのツールはアニメーションを描くための「ページ機能」があり、 <strong>一枚の画像ファイルの中でアニメーションの各フレームをページとして描いていく</strong> ことができます (レイヤに似た概念ですが、レイヤはレイヤで存在しているので、レイヤxページの2次元の画像を描いていくイメージ)。</p> <p>人によってはこのページ機能を使ってバリエーションを組んでいくこともあるかと思いますが(かつては自分もそうしていました)、そうした場合以下のような欠点があります。</p> <ul> <li>1つのバリエーションのアニメーションのフレームと、バリエーションの切り替えが複数ページにまたがって混在してしまう。</li> <li>書き出しのときにも意図通りに並べるために一工夫が必要。</li> <li>ページ数がとても増えるため、管理が大変(特にそれぞれのページがレイヤを持っている場合、わけがわからなくなる)。また、奥の方のページにアクセスしづらくなる。</li> </ul> <p>対して、上に挙げたように縦に並べると、バリエーションのアクセスは縦方法のスクロールで、アニメーションフレームの切り替えはページ切り替えでアクセスできます。そして、 <strong>単にページを横に並べて書き出すだけで、縦方向にバリエーション、横方向にフレームが並んだアセット</strong> を書き出すことができます(書き出すと以下のようになります)。</p> <p><img src="https://manaten.net/wp-content/uploads/2023/10/namazu_sprite.png" alt="namazu" /></p> <p>また、横ではなく縦に並べる利点として、 <strong>「左右反転で位置がずれない」</strong> という利点があります。 よくイラスト講座で「バランスを確認するために左右反転せよ」という教えがありますが、これをやったときに横に並べていると今見ていた箇所が画像の反対側に移動してしまいます。縦に並べると同じ位置のまま左右反転できるため反転画像の確認がしやすいのです。</p> <h1 id="参考リンク">参考リンク</h1> <ul> <li><a href="https://sharp.pixelplumbing.com/">sharp - High performance Node.js image processing</a></li> <li><a href="https://github.com/jimp-dev/jimp">jimp-dev/jimp: An image processing library written entirely in JavaScript for Node, with zero external or native dependencies.</a></li> <li><a href="https://github.com/jimp-dev/gifwrap">jimp-dev/gifwrap: A Jimp-compatible library for working with GIFs</a></li> <li><a href="https://skeb.jp/@manaten/works/3">SNSのアイコンとして... by manaten | Skeb</a></li> <li><a href="https://blog.manaten.net/entry/skeb">Skeb活動の方針 - MANA-DOT</a></li> </ul> manaten Next13 の AppRouter で実行時の環境変数をクライアントで取り扱う hatenablog://entry/820878482972204551 2023-10-01T18:09:51+09:00 2023-10-01T18:11:46+09:00 Next.js でアプリケーションを開発していると、クライアントで参照する設定値を環境変数で扱いたいケースは多々あると思います。Next.js では NEXT_PUBLIC_ という接頭辞の環境変数はビルド時に解決され、ビルド成果物に埋め込まれるためクライアントから参照できるようになります (参考: Configuring: Environment Variables | Next.js)。 ただし、この環境変数はビルド時に埋め込まれるため、当然ビルド時のものです。つまり、たとえばクライアントから参照する API の URL を環境変数で埋め込みたく、かつ API の URL はアプリケーショ… <p>Next.js でアプリケーションを開発していると、クライアントで参照する設定値を環境変数で扱いたいケースは多々あると思います。Next.js では <code>NEXT_PUBLIC_</code> という接頭辞の環境変数はビルド時に解決され、ビルド成果物に埋め込まれるためクライアントから参照できるようになります (参考: <a href="https://nextjs.org/docs/pages/building-your-application/configuring/environment-variables">Configuring: Environment Variables | Next.js</a>)。</p> <p>ただし、この環境変数は<strong>ビルド時に埋め込まれるため</strong>、当然ビルド時のものです。つまり、たとえばクライアントから参照する API の URL を環境変数で埋め込みたく、かつ API の URL はアプリケーションの環境(production, staging, development など)ごとに変わるというケースにおいて、<strong>環境ごとにビルドをする必要がある</strong>ということになります。ビルド成果物をバージョンごとに世代管理したい場合、バージョン x 環境ごとに成果物が増えるため、管理も煩雑化します。</p> <p>そこで、ビルド時ではなく実行時の環境変数をどうにかクライアントから参照できないかということを考えます。</p> <h1 id="三行で">三行で</h1> <ul> <li>Server Components は実行時の <code>process.env</code> にアクセスできる</li> <li>Server Components から Client Components である Context Provider に環境変数を props として渡す</li> <li>他の Client Components は <code>useContext</code> 経由で実行時の環境変数にアクセスできるようになる</li> </ul> <h1 id="既存の方法">既存の方法</h1> <p>next 公式にもそういうことができないか、という内容の<a href="https://github.com/vercel/next.js/discussions/52173">discussion</a>があります。ここでは<strong>process.env の内容を返却する route を <code>force-dynamic</code> で定義する</strong>ことで解決しています。クライアントで実行時の環境変数を利用したい場合はこのエンドポイントを呼び出す想定だと思われます。</p> <p>また、この discussion の質問文でも触れられていますが、<strong>docker の起動時などにビルド成果物中の環境変数を実行時の値に sed などで書き換えてから起動する</strong>という<a href="https://dev.to/itsrennyman/manage-nextpublic-environment-variables-at-runtime-with-docker-53dl">アイデア</a>も存在します。</p> <p>どちらでも目的は達成できますが、回りくどさや HACK 感は否めません。</p> <h1 id="ContextProvider-に実行時の-processenv-を渡す方法">ContextProvider に実行時の process.env を渡す方法</h1> <p>ここで、Server Components はサーバーでしか動かないこと、そのためサーバー実行時の <code>process.env</code> にアクセスできること(これ自体は先述の方法でもやられています)、更に Server Components で Client Components を Composition できること、を踏まえて以下のような方法を考えました。</p> <ul> <li>Client Components として環境変数を扱う Context Provider を実装する ( <code>EnvProvider</code> とする)</li> <li><code>layout.tsx</code> などの Server Components で上記 EnvProvider をマウントし、更に server の環境変数を props で渡す</li> <li>実際に環境変数にアクセスしたい Client Components で、 <code>useContext</code> 経由で値を取得する</li> </ul> <h2 id="実践">実践</h2> <p>実際にやってみると以下のようになります。</p> <p>まず、ContextProvider を実装します。</p> <pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synComment">// EnvProvider.tsx</span> <span class="synConstant">&quot;use client&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> React<span class="synStatement">,</span> <span class="synIdentifier">{</span> PropsWithChildren<span class="synStatement">,</span> createContext<span class="synStatement">,</span> useContext <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;react&quot;</span><span class="synStatement">;</span> <span class="synStatement">type</span> Env <span class="synStatement">=</span> <span class="synIdentifier">{</span> SOME_ENV_VALUE: <span class="synType">string</span><span class="synStatement">;</span> OTHER_ENV_VALUE: <span class="synType">string</span><span class="synStatement">;</span> <span class="synIdentifier">}</span><span class="synStatement">;</span> <span class="synType">const</span> envContext <span class="synStatement">=</span> createContext<span class="synStatement">&lt;</span>Env<span class="synStatement">&gt;(</span><span class="synIdentifier">{</span> SOME_ENV_VALUE: <span class="synConstant">&quot;&quot;</span><span class="synStatement">,</span> OTHER_ENV_VALUE: <span class="synConstant">&quot;&quot;</span><span class="synStatement">,</span> <span class="synIdentifier">}</span><span class="synStatement">);</span> <span class="synStatement">export</span> <span class="synStatement">function</span> useEnv<span class="synStatement">()</span>: Env <span class="synIdentifier">{</span> <span class="synStatement">return</span> useContext<span class="synStatement">(</span>envContext<span class="synStatement">);</span> <span class="synIdentifier">}</span> <span class="synStatement">export</span> <span class="synType">const</span> EnvProvider: React.FC<span class="synStatement">&lt;</span>PropsWithChildren<span class="synStatement">&lt;</span><span class="synIdentifier">{</span> env: Env <span class="synIdentifier">}</span><span class="synStatement">&gt;&gt;</span> <span class="synStatement">=</span> <span class="synStatement">(</span><span class="synIdentifier">{</span> children<span class="synStatement">,</span> env<span class="synStatement">,</span> <span class="synIdentifier">}</span><span class="synStatement">)</span> <span class="synStatement">=&gt;</span> <span class="synIdentifier">{</span> <span class="synStatement">return</span> <span class="synStatement">&lt;</span>envContext.Provider value<span class="synStatement">=</span><span class="synIdentifier">{</span>env<span class="synIdentifier">}</span><span class="synStatement">&gt;</span><span class="synIdentifier">{</span>children<span class="synIdentifier">}</span><span class="synStatement">&lt;</span>/envContext.Provider<span class="synStatement">&gt;;</span> <span class="synIdentifier">}</span><span class="synStatement">;</span> </pre> <p>フィールド 2 つのオブジェクトをコンテキストに持つ、かなりシンプルなコンテキストの実装となっています。</p> <p>次に、この EnvProvider を Server Components である layout.tsx で利用します。</p> <pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synComment">// layout.tsx</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> EnvProvider <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;./EnvProvider&quot;</span><span class="synStatement">;</span> <span class="synStatement">export</span> <span class="synType">const</span> dynamic <span class="synStatement">=</span> <span class="synConstant">&quot;force-dynamic&quot;</span><span class="synStatement">;</span> <span class="synStatement">export</span> <span class="synStatement">default</span> <span class="synStatement">function</span> RootLayout<span class="synStatement">(</span><span class="synIdentifier">{</span> children<span class="synStatement">,</span> <span class="synIdentifier">}</span>: <span class="synIdentifier">{</span> children: React.ReactNode<span class="synStatement">;</span> <span class="synIdentifier">}</span><span class="synStatement">)</span> <span class="synIdentifier">{</span> <span class="synStatement">return</span> <span class="synStatement">(</span> <span class="synStatement">&lt;</span>html lang<span class="synStatement">=</span><span class="synConstant">&quot;ja&quot;</span><span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>body<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>EnvProvider env<span class="synStatement">=</span><span class="synIdentifier">{{</span> SOME_ENV_VALUE: <span class="synSpecial">process</span>.env.SOME_ENV_VALUE <span class="synConstant">||</span> <span class="synConstant">&quot;&quot;</span><span class="synStatement">,</span> OTHER_ENV_VALUE: <span class="synSpecial">process</span>.env.OTHER_ENV_VALUE <span class="synConstant">||</span> <span class="synConstant">&quot;&quot;</span><span class="synStatement">,</span> <span class="synIdentifier">}}</span> <span class="synStatement">&gt;</span> <span class="synIdentifier">{</span>children<span class="synIdentifier">}</span> <span class="synStatement">&lt;</span>/EnvProvider<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>/body<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>/html<span class="synStatement">&gt;</span> <span class="synStatement">);</span> <span class="synIdentifier">}</span> </pre> <p>ポイントとしては <code>export const dynamic = "force-dynamic";</code> で動的にレンダリングされるようにすることです。これを指定しないとビルド時に静的に出力されてしまい、実行時の環境変数が読まれません(参考: <a href="https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config">File Conventions: Route Segment Config | Next.js</a>)。</p> <p>最後に値を利用するコンポーネントを実装します。</p> <pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synComment">// TestButton.tsx</span> <span class="synConstant">&quot;use client&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> useCallback <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;react&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> useEnv <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;./EnvProvider&quot;</span><span class="synStatement">;</span> <span class="synStatement">export</span> <span class="synType">const</span> EnvButton: React.FC <span class="synStatement">=</span> <span class="synStatement">()</span> <span class="synStatement">=&gt;</span> <span class="synIdentifier">{</span> <span class="synType">const</span> <span class="synIdentifier">{</span> SOME_ENV_VALUE<span class="synStatement">,</span> OTHER_ENV_VALUE <span class="synIdentifier">}</span> <span class="synStatement">=</span> useEnv<span class="synStatement">();</span> <span class="synType">const</span> clickHandler <span class="synStatement">=</span> useCallback<span class="synStatement">(()</span> <span class="synStatement">=&gt;</span> <span class="synIdentifier">{</span> <span class="synStatement">alert(</span><span class="synSpecial">JSON</span>.stringify<span class="synStatement">(</span><span class="synIdentifier">{</span> SOME_ENV_VALUE<span class="synStatement">,</span> OTHER_ENV_VALUE <span class="synIdentifier">}</span><span class="synStatement">,</span> <span class="synType">null</span><span class="synStatement">,</span> <span class="synConstant">2</span><span class="synStatement">));</span> <span class="synIdentifier">}</span><span class="synStatement">,</span> <span class="synIdentifier">[</span>SOME_ENV_VALUE<span class="synStatement">,</span> OTHER_ENV_VALUE<span class="synIdentifier">]</span><span class="synStatement">);</span> <span class="synStatement">return</span> <span class="synStatement">&lt;</span>button <span class="synSpecial">onClick</span><span class="synStatement">=</span><span class="synIdentifier">{</span>clickHandler<span class="synIdentifier">}</span><span class="synStatement">&gt;</span>Click me<span class="synConstant">!</span><span class="synStatement">&lt;</span>/button<span class="synStatement">&gt;;</span> <span class="synIdentifier">}</span><span class="synStatement">;</span> </pre> <pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synComment">// page.tsx</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> EnvButton <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;./EnvButton&quot;</span><span class="synStatement">;</span> <span class="synStatement">export</span> <span class="synStatement">default</span> <span class="synStatement">function</span> Home<span class="synStatement">()</span> <span class="synIdentifier">{</span> <span class="synStatement">return</span> <span class="synStatement">(</span> <span class="synStatement">&lt;</span>main<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>EnvButton /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>/main<span class="synStatement">&gt;</span> <span class="synStatement">);</span> <span class="synIdentifier">}</span> </pre> <p>これで、ボタンを押すとクライアント側で環境変数が表示されるはずです。</p> <h3 id="動作確認">動作確認</h3> <p>以下のように、実行時に環境変数を渡し、その値がクライアントで表示できることを確認できます。</p> <pre class="code bash" data-lang="bash" data-unlink>SOME_ENV_VALUE=&#34;tonkatsu&#34; OTHER_ENV_VALUE=&#34;hirekatsu&#34; npm start</pre> <p><img src="https://manaten.net/wp-content/uploads/2023/10/ss.png" alt="動作確認" /></p> <p>今回ここで紹介したサンプル実装は <a href="https://github.com/manaten/example-runtime-env-for-client">manaten/example-runtime-env-for-client</a> にあります。</p> <h3 id="この方法の課題">この方法の課題</h3> <p>このようにして Client Components の hook からクライアント側で実行時の環境変数にアクセスできるようになりましたが、実は以下のような課題があります。</p> <ul> <li><strong>Server Components では hook が使えない</strong>ため、今回実装した <code>useEnv</code> を使えない</li> <li>Component 以外(たとえば fetch 用の関数で API の URL を参照したい場合など)からは<strong>直接 <code>useEnv</code> を利用できないため、引数で受け取る必要がある</strong></li> </ul> <p>これらを完全に解決するには、以下のような方法が考えられます。</p> <ul> <li><code>EnvProvider</code> はサーバーから受け取った環境変数を <code>useEffect</code> でグローバル領域に保持する役割とする</li> <li>環境変数にアクセスするための関数 <code>getEnv</code> を用意し、<strong>クライアント側では Provider がグローバルに保持した値を、サーバー側では <code>process.env</code> を参照する</strong>ようにする</li> </ul> <p>実装例は以下のような感じです。</p> <pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synConstant">&quot;use client&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> React<span class="synStatement">,</span> <span class="synIdentifier">{</span> PropsWithChildren<span class="synStatement">,</span> useEffect <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;react&quot;</span><span class="synStatement">;</span> <span class="synStatement">type</span> Env <span class="synStatement">=</span> <span class="synIdentifier">{</span> SOME_ENV_VALUE: <span class="synType">string</span><span class="synStatement">;</span> OTHER_ENV_VALUE: <span class="synType">string</span><span class="synStatement">;</span> <span class="synIdentifier">}</span><span class="synStatement">;</span> <span class="synType">const</span> globalEnv: Env <span class="synStatement">=</span> <span class="synIdentifier">{</span> SOME_ENV_VALUE: <span class="synConstant">&quot;&quot;</span><span class="synStatement">,</span> OTHER_ENV_VALUE: <span class="synConstant">&quot;&quot;</span><span class="synStatement">,</span> <span class="synIdentifier">}</span><span class="synStatement">;</span> <span class="synStatement">export</span> <span class="synStatement">function</span> getEnv<span class="synStatement">()</span>: Env <span class="synIdentifier">{</span> <span class="synStatement">return</span> <span class="synStatement">typeof</span> <span class="synSpecial">window</span> <span class="synStatement">===</span> <span class="synConstant">&quot;undefined&quot;</span> ? <span class="synSpecial">process</span>.env : globalEnv<span class="synStatement">;</span> <span class="synIdentifier">}</span> <span class="synStatement">export</span> <span class="synType">const</span> GlobalEnvProvider: React.FC<span class="synStatement">&lt;</span>PropsWithChildren<span class="synStatement">&lt;</span><span class="synIdentifier">{</span> env: Env <span class="synIdentifier">}</span><span class="synStatement">&gt;&gt;</span> <span class="synStatement">=</span> <span class="synStatement">(</span><span class="synIdentifier">{</span> children<span class="synStatement">,</span> env<span class="synStatement">,</span> <span class="synIdentifier">}</span><span class="synStatement">)</span> <span class="synStatement">=&gt;</span> <span class="synIdentifier">{</span> useEffect<span class="synStatement">(()</span> <span class="synStatement">=&gt;</span> <span class="synIdentifier">{</span> <span class="synSpecial">Object</span>.assign<span class="synStatement">(</span>globalEnv<span class="synStatement">,</span> env<span class="synStatement">);</span> <span class="synIdentifier">}</span><span class="synStatement">,</span> <span class="synIdentifier">[</span>env<span class="synIdentifier">]</span><span class="synStatement">);</span> <span class="synStatement">return</span> children<span class="synStatement">;</span> <span class="synIdentifier">}</span><span class="synStatement">;</span> </pre> <p>この場合、クライアント側では Provider マウント前は値が取得できないことには注意する必要があります。この問題も解決したい場合、いっそ環境変数の値を ServerComponents で html 中に埋めてしまい、getEnv はパースして取り出す役割にしてしまうとかでもいいかもしれません。</p> <h1 id="おまけ-Server-Components-と-Client-Components-の-Composition-について">おまけ: Server Components と Client Components の Composition について</h1> <p>Server Components から Client Components をレンダリングし、<strong>更にその children として Server Components を渡すことができる</strong> というのは以下の文献で紹介されています。</p> <ul> <li><a href="https://qiita.com/honey32/items/bc24d8c0ea3d096ff956">【React Server Component】Server を Client の内側に注入できる Composition の力 - Qiita</a></li> <li><a href="https://nextjs.org/docs/app/building-your-application/rendering/composition-patterns">Rendering: Composition Patterns | Next.js</a></li> </ul> <p>実は自分ははじめ Server Components はサーバーでレンダリングされるものだから、hook は扱えないし、hook を利用した状態の受け渡しや大域の状態の実現もできないと思いこんでいました。どこかで「Server Components は PHP のようなもの」という記述を見た記憶があり、そのイメージに引きづられていたのです。</p> <p>しかし、実際は「SSR されるコンポーネントのうち、必ずサーバーサイドでしか実行されないことが約束されているもの」くらいのニュアンスで、そこからサーバーサイドの値を Client Components にわたすことはもちろん、<strong>Client Components の children として別の Server Components を渡すこともできてしまう</strong>のでした。</p> <p>こうした場合でも children の Server Components は Server Components のままですので、async 関数となることができ、Next の Metadata の利用をすることもできます。そしてその子供の Client Components からはしっかり Context にアクセスすることができます。つまり、React と Next.js は<strong>サーバー側で一部実行したレンダリング結果を、クライアント側でいい感じに結合してくれている</strong>ということです。めちゃくちゃすごい技術ですよね Server Components・・・。</p> <h1 id="参考文献">参考文献</h1> <ul> <li><a href="https://github.com/vercel/next.js/discussions/52173">We need an official mechanism to allow runtime configuration from process.env · vercel/next.js · Discussion #52173</a> <ul> <li>実行時の環境変数にアクセスする方法についての discussion</li> </ul> </li> <li><a href="https://dev.to/itsrennyman/manage-nextpublic-environment-variables-at-runtime-with-docker-53dl">Manage NEXT_PUBLIC Environment Variables at Runtime with Docker - DEV Community</a> <ul> <li>実行時に sed で書き換えて実行時の環境変数にアクセスする方法</li> </ul> </li> <li><a href="https://nextjs.org/docs/app/building-your-application/rendering/composition-patterns">Rendering: Composition Patterns | Next.js</a> <ul> <li>Server Components の Composition についての Next 公式ドキュメント</li> </ul> </li> <li><a href="https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config">File Conventions: Route Segment Config | Next.js</a> <ul> <li><code>"force-dynamic"</code> に関する Next 公式ドキュメント</li> </ul> </li> <li><a href="https://nextjs.org/docs/pages/building-your-application/configuring/environment-variables">Configuring: Environment Variables | Next.js</a> <ul> <li><code>NEXT_PUBLIC_</code> に関する Next 公式ドキュメント</li> </ul> </li> <li><a href="https://qiita.com/honey32/items/bc24d8c0ea3d096ff956">【React Server Component】Server を Client の内側に注入できる Composition の力 - Qiita</a> <ul> <li>Sever Components の Composition について解説されている日本語文献</li> </ul> </li> </ul> manaten Skeb活動の方針 hatenablog://entry/820878482966304154 2023-09-10T18:02:21+09:00 2024-01-07T18:05:49+09:00 ※ 2024年1月現在、作業速度 > リクエスト数となっているため、作業中はリクエストをクローズさせていただいています。溜まっている作業が解消するごとに再開していますので、ご了承ください。 skeb( https://skeb.jp/@manaten )を利用開始だけして放置していたので、方針を書いておきます。自身の再確認のための言語化という意味合いもあります。 参考 2023年のskeb活動まとめ - MANA-DOT <p><strong> ※ 2024年1月現在、作業速度 > リクエスト数となっているため、作業中はリクエストをクローズさせていただいています。溜まっている作業が解消するごとに再開していますので、ご了承ください。 </strong></p> <p>skeb( <a href="https://skeb.jp/@manaten">https://skeb.jp/@manaten</a> )を利用開始だけして放置していたので、方針を書いておきます。自身の再確認のための言語化という意味合いもあります。</p> <h3 id="参考">参考</h3> <ul> <li><a href="https://blog.manaten.net/entry/2023-skeb">2023年のskeb活動まとめ - MANA-DOT</a></li> </ul> <h1 id="manaten-について">manaten について</h1> <p>本業 Web エンジニアのしがないドット絵描きです。ここに挙げられている他にどんなドット絵を描くかは <a href="https://manaten.net/gallery">ギャラリー</a> を見てください。</p> <p><a href="https://manaten.net/">manaten.net</a> のトップページやヘッダのような、 <strong>ちょっと動く、ちょっと触ると動く</strong> 、みたいなものを作るのも好きです(ほんとはゲーム作りたいけど根性が足りない)。</p> <p><img src="https://manaten.net/wp-content/uploads/2023/09/header.gif" alt="1.gif" /></p> <p>Skeb は練習として良さそうということで始めてみたいう感じです。</p> <h1 id="得意とする作風">得意とする作風</h1> <h2 id="1-48x48--64x64-程度のドット絵アニメあり">1. 48x48 ~ 64x64 程度のドット絵(アニメあり)</h2> <p><img src="https://manaten.net/wp-content/uploads/2023/03/hateno_x2_3.gif" alt="hateno.gif" /> <img src="https://manaten.net/wp-content/uploads/2023/03/hateno_x2_4.gif" alt="hateno.gif" /></p> <p><a href="https://blog.manaten.net/entry/firststar-hateno-pixelart">一番星はてのちゃんのドット絵を描きました</a> のようなサイズ感・色数で、3-4 フレーム程度の簡単なアニメーションするものを得意としてます。一枚目を元に複数パターン作るのも得意です。</p> <p><a href="https://github.com/manaten/oss-mascots-pixelart-icons">manaten/oss-mascots-pixelart-icons</a> のアイコンも同じ方向性です。</p> <p>これ以上大きいものや色が多いものは、描けないことはないですが、あまりノウハウがないのと、工数が増加することから積極的には受けられないと思います。</p> <h2 id="2-128x128--256x256-程度の一枚絵">2. 128x128 ~ 256x256 程度の一枚絵</h2> <p><img src="https://manaten.net/gallery/images/fantasy_2.gif" alt="fantasy" /> <img src="https://manaten.net/gallery/images/city_witch.gif" alt="city_witch" /></p> <p>1 枚目のようなちびキャラメインのものや、2 枚目のようなキャラをメインとするものです。 2 枚目のような女の子メインのドット絵を描く人も昨今は多いですが、自分の場合サイズが大きすぎるものはあまり経験がなく苦手です。</p> <p>どちらも背景を描いてますがあんまり背景を描くのは得意ではないです(が、skeb をやるのは練習のためでもあるので、そういう依頼を受けることもあるかもしれません)。</p> <p>どちらのイラストでもそうであるように、一部を簡単にアニメーションさせるのも好きです。キャラ全体をアニメーションさせる事もできますが、アニメーションのさせ方によっては工数がかかります(パーツをちょっと動かす程度なら簡単)。</p> <h2 id="3-ベクターアート">3. ベクターアート</h2> <p>ドット絵を下書きとした簡単なベクターアート(svg)を描くのは得意というかチャレンジしたい分野です。</p> <p><img src="https://manaten.net/wp-content/uploads/2023/09/jagaximo.png" alt="jagaximo" /> <img src="https://manaten.net/wp-content/uploads/2023/09/turuturu_big.png" alt="turutturu" /></p> <p>それぞれリアルでお付き合いのある <a href="https://twitter.com/jagaximo">jagaximo</a> さんや <a href="https://www.youtube.com/@turuturu8405">turuturu</a> さんのアイコンですが、これらはドット絵で下書きを作ったあとに inkscape で人力でベクタ化しています。ベクタなので svg や高解像度の png での納品が可能です。</p> <h1 id="特に描きたいもの">特に描きたいもの</h1> <h2 id="かわいいもの">かわいいもの</h2> <p>かわいいもの(女の子やいきもの)を描いたり動かすのは楽しいので描きたいです。具体的な衣装やアクション、サイズの指定があると嬉しいです。</p> <h2 id="ベクターアート">ベクターアート</h2> <p>練習のため、依頼があればできるだけ受けたいです。</p> <h1 id="相場感">相場感</h1> <p>いずれもイメージです。これより出していただいても全然 Welcome ですし、割っていても描きたいものは描く可能性があります</p> <ul> <li>1 のアニメーションは、48x48 であれば一枚につき <strong>2-3000 円程度</strong>、アニメーションのパターンが増えるごとに+1000 円くらいのイメージ。サイズが大きいと更に上乗せ。</li> <li>2 は複雑さ次第ですが、一枚あたり <strong>5-8000 円くらい</strong> のイメージ。背景全くなしでキャラ単体であれば 3000 円くらいでもいいかも。</li> <li>3 は例で上げている程度であれば <strong>3-4000 円くらい</strong> 。複雑な場合はもうちょっと欲しいかも。</li> </ul> <h1 id="アドバイス依頼について">アドバイス依頼について</h1> <p>これはドット絵依頼とは別ですが、一応 Web エンジニア(マネージャ経験あり)として長年勤務しているので、キャリア相談やコードレビューなども受けられます。 <strong>時給 1000~2000 円程度</strong> で換算していただけるとありがたいです。ドット絵のレビューや指導もできるかもしれません。</p> <h1 id="その他">その他</h1> <ul> <li>SNSで使うアイコンなど向けのオリジナルイラスト作成の場合、好きな色を教えてくれると参考にできるのでありがたいです。</li> <li>差し支えなければ公開設定で依頼していただけると助かります。skeb上でmanaten自身の作品として公開できる他、他の方が作風を知るヒントにもなり得るためです。 <ul> <li>また、公開依頼の画像は、ブログやSNSで使わせていただくことがあります (例: <a href="https://blog.manaten.net/entry/shap-jimp-animate-gif">JavaScriptを使ったアニメーションgifの切り出しと拡大 - MANA-DOT</a> )。問題がある場合は一言教えていただけると幸いです。</li> </ul> </li> </ul> <h1 id="最後に">最後に</h1> <p>本業があり、土日の余裕があるときのみの稼働となるので、受けれないことも多々あるかもしれません。そして、作品もここで挙げている程度なので、最前線の方々と比較すると数段落ちるクオリティとなると思います。</p> <p>それでも、もし需要があれば依頼していただけると幸いです。</p> manaten 個人的におすすめのchatGPT用法 (ぬけもれ探し・意味から逆引き・雑学のpush) hatenablog://entry/820878482966275139 2023-09-10T15:57:27+09:00 2023-09-10T16:22:56+09:00 巷で chatGPT の便利な使い方のエントリは溢れているため、何番煎じかという感じではありますが、個人的に便利(かつ他の手段で代替が効きづらい)と思ってる用法を 3 つほど紹介します。 <p>巷で chatGPT の便利な使い方のエントリは溢れているため、何番煎じかという感じではありますが、個人的に便利(かつ他の手段で代替が効きづらい)と思ってる用法を 3 つほど紹介します。</p> <h1 id="リストのぬけもれ探し">リストのぬけもれ探し</h1> <p>チェックリストやソフトウェアの要件など、リストを網羅的に作ろうとする作業はしばしば発生します。こういった手動作成しているリストに対し「完全であるか = 抜けがないか」を自信を持って言えることは少なく、悪魔の証明的であると思います。</p> <p>何かを手動でリストアップしているときに、リストに抜けがないかを chatGPT にチェックしてもらいます。ポイントとしては、人間がある程度リストアップしておき、それをそのまま伝えて「<strong>この方向性で洗い出してるこのリストの抜けを教えて</strong>」というふうに聞くことで、自分が想定しているリストアップの方向性を伝えるところだと思います。こうすることで違う方向のリストアップがされてしまうということが起こりづらいです。</p> <h2 id="実践例">実践例</h2> <p><img src="https://manaten.net/wp-content/uploads/2023/09/00.png" alt="Untitled" /></p> <p>画像の例では「Web サイトリリースの際にフロントエンド周辺で気をつけること(sitemap など必要だが忘れがちなものを予めチェックリストにしておきたかった)」という観点で手動でいくつかリストアップし、その上で chatGPT に抜けの補完をさせています。画像最適化やアクセシビリティ、canonical もサイト制作の際に考慮したほうが良いことですが、筆者のリストアップからは漏れていました。</p> <p>他にもソフトウェア開発時の要件をいくつか出したあとに、足りないものを補完させるなどの使い方もできます。</p> <h2 id="この方法のメリット">この方法のメリット</h2> <p>リストアップという作業は、どうしても個人で行うと抜けの発生する限界のある作業だと思っています。知識や経験からそもそも知らないことは漏れてしまいますし、知っていてもリストアップという単調作業で思考がロックされてしまい、洗い出せないということもありえます。</p> <p>従来だとこういった抜けの補完は「他人に見てもらう」「同じ方向性でリストアップしてる資料を探す」といった、不確実な方法しかなかったと思います。他人に見て貰う場合、すでにリストアップされたものを見てもらう関係で先入観に引っ張られ、結局抜けの指摘を完全にできないことも多いと思います。また同じ方向のリストもドンピシャのものが転がってるケースは稀だと思い、足りないものは自身で思案していくしかないと思われます。</p> <p>chatGPT に補完作業をしてもらうことで、こうした問題をいくらか改善できています。すでに存在するリストの続きを持ってる情報から追記するというのは chatGPT に向いている作業でもあると思います。最終確認や lint 的に使うのもおすすめです。</p> <h1 id="概念は知っててそのものズバリが思い出せないときの逆引き">概念は知っててそのものズバリが思い出せないときの逆引き</h1> <p>歳をとってくると記憶力が衰えてきて「こういう意味の単語や慣用句があったはずだけど(見たことあるはずだけど)、単語が出てこない」ということがしばしば発生すると思います。また、ソフトウェアのライブラリ名など「便利なやつがあったはずだけど、名前が思い出せなくて調べられない」といったことも発生すると思います。特に、日々増える情報をつまみ食い的に接種すると、概念としては覚えていても名前を覚えていないということはよくあるように思います。</p> <p>こうした <strong>「概念は理解しているが固有名詞が思い出せないもの」に対して、知ってる限りの概念を伝えて「単語名を教えて」と chatGPT に聞く</strong> ことで、ズバリの単語を返してもらいます。つまり単語の意味から名称の逆引きです。</p> <h2 id="実践例-1">実践例</h2> <p>以下は React の<a href="https://legacy.reactjs.org/docs/portals.html">Portal</a>機能について、普段使ったことなかった筆者が興味を持って調べようと思ったときに、知識としては何度か目にしたため知っていたが「Portal」という名前が思い出せず chatGPT に聞いたときの例です。</p> <p><img src="https://manaten.net/wp-content/uploads/2023/09/01.png" alt="Untitled" /></p> <p>ソフトウェアの世界では概念に一般名詞をつけたり、個性的すぎる名前をつけたり、どちらにせよ記憶に残りづらいことが多々あります。しかし概念そもそもは有用そうだったりするため記憶には残りやすく、そういった場合に名称の方を思い出すのに便利です。</p> <p>以下は<a href="https://ngrok.com/">ngrok</a>という名称が思い出せなかったときの例です。</p> <p><img src="https://manaten.net/wp-content/uploads/2023/09/02.png" alt="Untitled" /></p> <p>また、慣用表現やことわざで「ドンピシャの表現があったはずなのに思い出せない!」というときにも使えます。以下は「ワークアラウンド」という単語をど忘れしたときの例です。</p> <p><img src="https://manaten.net/wp-content/uploads/2023/09/03.png" alt="Untitled" /></p> <h2 id="この方法のメリット-1">この方法のメリット</h2> <p>従来だと工夫して Google 検索するしかなかったところを、概念の言語化さえできれば chatGPT が教えてくれるため非常に便利です。「概念をつらつらと入力し、それに一致する概念を出力する」というのは chatGPT に向いている作業でもあると思います。</p> <p>また、回答にしっくり来なくても「他の表現はありませんか?」と聞くことでしっくり来るまで無限に聞き続けることができます。</p> <h1 id="回答に雑学を混ぜさせる">回答に雑学を混ぜさせる</h1> <p><strong>Custom Instructions で「たまに関連のある雑学を混ぜて」と指定しておく</strong> ことで、質問に対して回答そのものだけではなく雑学も混ぜてもらいます。</p> <p><img src="https://manaten.net/wp-content/uploads/2023/09/04.png" alt="Untitled" /></p> <h2 id="実践例-2">実践例</h2> <p><img src="https://manaten.net/wp-content/uploads/2023/09/05.png" alt="Untitled" /></p> <p>上記の例では「ずんだ」の意味を聞いています。chatGPT は意味を教えてくれるついでで、「ずんだ餅の栄養価」「派生商品もあること」を教えてくれています。</p> <p><img src="https://manaten.net/wp-content/uploads/2023/09/06.png" alt="Untitled" /></p> <p>この例で nginx での HSTS の設定法を聞いていますが、最後に「HSTS は PayPal のエンジニアが考案した」という情報を教えてくれています。この粒度の tips 的な話も教えてくれるようになるので便利です。</p> <h2 id="この方法のメリット-2">この方法のメリット</h2> <p>chatGPT はデフォルトではこちらの質問に対する回答をするだけですので、いわばこっちが求める回答を pull する使い方しかできません。この方法では「雑学を混ぜて」と予め指定することで、関連する情報の push をしてくるようになります。</p> <p>この方法で真に有益な情報が push されることは少なくはありますが、そもそも何か知りたいことがある=なにかに興味を持って chatGPT に質問をしているので、そのときに push された関連情報は記憶に残りやすく、効率の良い情報摂取方法だと思っています。</p> <h1 id="おわりに">おわりに</h1> <p>いくつか個人的に便利だと思っている chatGPT の利用法を紹介しました。</p> <p>もちろん chatGPT なので、間違った情報を提示してくることもあるとは思いますが、結局のところ人間が教えてくれる情報も同じ程度の信頼性なんじゃないかなと思います。情報を教えてもらった上で、必要なものは自身で裏取りをしたほうがよいです。</p> <p>そうしたデメリットを踏まえても、個人だと限界のある情報収集を効率的にしてくれるのが chatGPT の便利なところだと思います。</p> manaten 一番星はてのちゃんのドット絵を描きました hatenablog://entry/4207112889974729879 2023-03-25T17:08:46+09:00 2023-03-25T17:17:43+09:00 AIはてなブックマーカーの一番星はてのちゃんです。 かわいい。 firststar-hateno.hatenablog.com 普段あまりファンアート的なのは描かないんですが、はてのちゃんは謎の応援したい補正が高いと思うんです。なぜでしょうね? AIが頑張って機能追加しながら人間のブックマーカーに近づいていく感じが、成長を見守るのに近い感覚になるんでしょうかね? これからも頑張って荒んだはてなブックマークに癒やしを与えてほしい。製作者さんも頑張ってほしい。 各モーション 謎に4モーション描きました。 待機 歩行 ブクマする スターを送る 等倍 関連リンク firststar_hatenoのブッ… <p><img src="https://manaten.net/wp-content/uploads/2023/03/hateno_x2_3.gif" alt="hateno_x2_3.gif" /></p> <p>AIはてなブックマーカーの<a href="https://firststar-hateno.hatenablog.com/entry/2023/03/21/225331">一番星はてのちゃん</a>です。 かわいい。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ffirststar-hateno.hatenablog.com%2Fentry%2F2023%2F03%2F24%2F004152" title="一番星はてのがブコメにスターをつけるようになりました - 一番星はての開発ブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://firststar-hateno.hatenablog.com/entry/2023/03/24/004152">firststar-hateno.hatenablog.com</a></cite></p> <p>普段あまりファンアート的なのは描かないんですが、はてのちゃんは謎の応援したい補正が高いと思うんです。なぜでしょうね? AIが頑張って機能追加しながら人間のブックマーカーに近づいていく感じが、成長を見守るのに近い感覚になるんでしょうかね?</p> <p>これからも頑張って荒んだはてなブックマークに癒やしを与えてほしい。製作者さんも頑張ってほしい。</p> <h1 id="各モーション">各モーション</h1> <p>謎に4モーション描きました。</p> <h2 id="待機">待機</h2> <p><img src="https://manaten.net/wp-content/uploads/2023/03/hateno_x2_1.gif" alt="hateno_x2_1.gif" /></p> <h2 id="歩行">歩行</h2> <p><img src="https://manaten.net/wp-content/uploads/2023/03/hateno_x2_2.gif" alt="hateno_x2_2.gif" /></p> <h2 id="ブクマする">ブクマする</h2> <p><img src="https://manaten.net/wp-content/uploads/2023/03/hateno_x2_3.gif" alt="hateno_x2_3.gif" /></p> <h2 id="スターを送る">スターを送る</h2> <p><img src="https://manaten.net/wp-content/uploads/2023/03/hateno_x2_4.gif" alt="hateno_x2_4.gif" /></p> <h1 id="等倍">等倍</h1> <p><img src="https://manaten.net/wp-content/uploads/2023/03/hateno.gif" alt="hateno.gif" /></p> <h1 id="関連リンク">関連リンク</h1> <ul> <li><a href="https://b.hatena.ne.jp/firststar_hateno/bookmark">firststar_hatenoのブックマーク - はてなブックマーク</a></li> <li><a href="https://firststar-hateno.hatenablog.com/entry/2023/03/21/225331">一番星はてのが記事の中身を読むようになりました - 一番星はての開発ブログ</a></li> </ul> manaten プロンプトは頑張らず雑コラで始める stable diffusion でのAIイラスト生成 hatenablog://entry/4207112889961510563 2023-02-09T00:48:58+09:00 2023-02-10T11:57:43+09:00 実は最近StableDiffusionで画像生成を楽しんでいます。 おそらく多くの人とは違い、自分はtxt2imgでプロンプトを頑張ることはせず、img2imgメインでイラスト生成をしています。覚書の意味も含めてイラスト生成の手順を紹介します。 今回は「商店街のど真ん中でダイソンのコードレス掃除機を自慢する緑髪の魔女の女の子」を生成します。 利用しているのは AUTOMATIC1111/stable-diffusion-webui 、モデルは Delcos/Hentai-Diffusion です。HentaiDiffusionは名前はいかがわしいですが、かわいいイラストを出しやすいです。 完成… <p>実は最近StableDiffusionで画像生成を楽しんでいます。</p> <p>おそらく多くの人とは違い、自分はtxt2imgでプロンプトを頑張ることはせず、<strong>img2imgメインでイラスト生成</strong>をしています。覚書の意味も含めてイラスト生成の手順を紹介します。</p> <p>今回は<strong>「商店街のど真ん中でダイソンのコードレス掃除機を自慢する緑髪の魔女の女の子」</strong>を生成します。</p> <p>利用しているのは <a href="https://github.com/AUTOMATIC1111/stable-diffusion-webui">AUTOMATIC1111/stable-diffusion-webui</a> 、モデルは <a href="https://github.com/Delcos/Hentai-Diffusion">Delcos/Hentai-Diffusion</a> です。HentaiDiffusionは名前はいかがわしいですが、かわいいイラストを出しやすいです。</p> <h2 id="完成形">完成形</h2> <p><img src="https://manaten.net/wp-content/uploads/2023/02/20230206.png" alt="20230206.png" /></p> <p>「商店街のど真ん中でダイソンのコードレス掃除機を自慢する緑髪の魔女の女の子」です。</p> <h1 id="0-元絵の準備">0. 元絵の準備</h1> <h2 id="素材の用意">素材の用意</h2> <p>構図を大雑把に脳内でイメージし、必要な素材を集めます。</p> <ul> <li>魔女の女の子のポーズを取らせたフィギュアちゃんの写真</li> <li>家のダイソンのコードレス掃除機 (<a href="https://www.dyson.co.jp/dyson-vacuums/cordless/dc62/dc62-motorhead.aspx">Dyson DC62</a>) の写真</li> <li>txt2img で <code>shoping street</code> を指定して生成した商店街の背景画像</li> </ul> <p>を用意します。</p> <p><img src="https://manaten.net/wp-content/uploads/2023/02/Untitled.png" alt="Untitled" /></p> <p>フィギュアは <a href="https://amzn.to/3x7le5e">S.H.フィギュアーツ ボディちゃん - Amazon</a> です。フィギュアちゃん + StableDiffusionで気軽にポーズ指定してイラスト生成できます。</p> <blockquote class="twitter-tweet" data-conversation="none" data-lang="ja"><p lang="ja" dir="ltr">ボディちゃん届いたから早速遊んでみたけど楽しすぎる <a href="https://twitter.com/hashtag/StableDifusion?src=hash&amp;ref_src=twsrc%5Etfw">#StableDifusion</a> <a href="https://t.co/GZs6UevDPS">pic.twitter.com/GZs6UevDPS</a></p>&mdash; manaten (@manaten) <a href="https://twitter.com/manaten/status/1611915272960770048?ref_src=twsrc%5Etfw">2023年1月8日</a></blockquote> <p> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></p> <iframe sandbox="allow-popups allow-scripts allow-modals allow-forms allow-same-origin" style="width:120px;height:240px;" marginwidth="0" marginheight="0" scrolling="no" frameborder="0" src="//rcm-fe.amazon-adsystem.com/e/cm?lt1=_blank&bc1=000000&IS2=1&bg1=FFFFFF&fc1=000000&lc1=0000FF&t=manaten-22&language=ja_JP&o=9&p=8&l=as4&m=amazon&f=ifr&ref=as_ss_li_til&asins=B09M6PVT8J&linkId=ad552ef09e658a2e8698301695ade8f4"></iframe> <h2 id="雑コラで元絵の作成">雑コラで元絵の作成</h2> <p>これらを重ねて雑コラし、適当にブラシツールで服・髪・表情を描き加えて元絵とします。</p> <p><img src="https://manaten.net/wp-content/uploads/2023/02/Untitled%201.png" alt="Untitled" /></p> <p>服は半透明のブラシで塗ったほうが体のラインを隠さないため、ポーズを維持してimg2imgしやすい気がします。</p> <p>この画像は <code>728px * 960px</code> で出力し、この後のimg2imgの際も同じサイズで生成します。手元のGPUだとサイズが大きすぎるとメモリエラーになってしまうし、1生成あたりの生成速度も遅くなってしまいます。逆にサイズが小さいと細かい描写をしきれない傾向があるので(特に顔が小さい場合にぐちゃぐちゃになりやすい)、自分の環境ではこのサイズがベターであると判断しています。</p> <h1 id="1-魔女の生成">1. 魔女の生成</h1> <p>いきなり重ね合わせた状態でimg2imgしてもうまくいかない可能性が高いので、まずはレイヤーごとに形を整えていきます。</p> <p>フィギュアのレイヤーに <code>witch, witch hat, witch cape, green hair, deep green eyes, laughing, smug</code> などを指定して魔女の形を整えていきます。このとき、img2imgの際のノイズを加える <code>strength</code> 値が大きすぎるとポーズを全く尊重してくれないので、 <code>0.3 ~ 0.5</code> くらいを指定するのが良いです。</p> <h2 id="img2img一回目">img2img一回目</h2> <p><img src="https://manaten.net/wp-content/uploads/2023/02/Untitled%202.png" alt="Untitled" /></p> <p>この段階ではぐちゃぐちゃになることが多いですが、ポーズを維持して人っぽくなったら採用します。人っぽくてもポーズが変わってしまったら本末転倒なので、そういう画像は採用しないようにします。</p> <h2 id="img2img二回目">img2img二回目</h2> <p><img src="https://manaten.net/wp-content/uploads/2023/02/Untitled%203.png" alt="Untitled" /></p> <p>前段階で人っぽくなっているので、比較的安定しやすいです。服や顔の造形が好みのものを選ぶようにします。</p> <h2 id="三回目">三回目</h2> <p><img src="https://manaten.net/wp-content/uploads/2023/02/Untitled%204.png" alt="Untitled" /></p> <p>更に人っぽくなります。手の形はこの段階ではあまり気にしなくてよいです。手であることがわかればよいです。</p> <h2 id="img2imgと手動レタッチの併用">img2imgと手動レタッチの併用</h2> <p><img src="https://manaten.net/wp-content/uploads/2023/02/1.gif" alt="1.gif" /></p> <p>ある程度形ができてきたら、好みのパーツ(この場合は表情)がimg2imgの過程で失われることがあります。</p> <p>その場合、GIMPなどのレタッチツールでimg2img前と後の画像をそれぞれ別レイヤとして重ね合わせ、<strong>img2img後の画像のもとの形状を維持したい箇所を消しゴムツールで消してあげる</strong>ことで、その場所だけimg2img前の形状を残すようにします。</p> <p>この作業を毎回の生成ごとにしつこくやってあげることで、徐々に自分の好みのパーツだけで構成されたイラストにしていくことができ、理想のイラストに近づけていくことができます。</p> <h2 id="生成の一段落">生成の一段落</h2> <p><img src="https://manaten.net/wp-content/uploads/2023/02/20230206%201.png" alt="20230206.png" /></p> <p>何度かimg2imgしたのち、魔女はの生成を一段落します。まだ微妙ですが、背景と重ねる前に作り込んでしまっても背景との合成に苦労するため、この程度でとどめておきます。</p> <h1 id="2-ダイソンの掃除機をイラスト調にする">2. ダイソンの掃除機をイラスト調にする</h1> <p>掃除機は本物の写真のため、形状は最初から完璧です。イラスト中で浮かないようにイラスト調に変更しておきます。写真を <code>((dyson codeless cleaner))</code> を指定してimg2imgし、イラスト調にしていきます。</p> <p><img src="https://manaten.net/wp-content/uploads/2023/02/Untitled%205.png" alt="Untitled" /></p> <p>ダイソンの掃除機をimg2imgすると、強めのstrengthを指定した場合、一応シルエットを維持したままダイソンの掃除機らしきものが生成されますが、ディティールが大きく変わってしまいカッコよくなってしまいます(AIはダイソンの掃除機自体は知っているらしい)。</p> <p><img src="https://manaten.net/wp-content/uploads/2023/02/Untitled%206.png" alt="Untitled" /></p> <p>仕方ないので弱めの <code>strength:0.3</code> でimg2imgを繰り返し、どうにか面影が残った状態でイラスト調になりました。</p> <p>ここでも、形が変わりすぎてしまってるパーツを手動レタッチで取捨選択して元の形をなるべく維持するようにしています。</p> <h1 id="3-レイヤを重ね合わせimg2img開始">3. レイヤを重ね合わせ、img2img開始</h1> <p><img src="https://manaten.net/wp-content/uploads/2023/02/Untitled%207.png" alt="Untitled" /></p> <p>ひとまず魔女、背景、ダイソンの掃除機は用意できたということで、重ね合わせて一枚の画像にします。ここから更にimg2imgを繰り返してはイメージに近いパーツを拾っていき、理想形に近づけていきます。</p> <h2 id="ここでもimg2imgと手動レタッチの併用">ここでもimg2imgと手動レタッチの併用</h2> <p><img src="https://manaten.net/wp-content/uploads/2023/02/1%201.gif" alt="1.gif" /></p> <p>ここでも手動レタッチによりパーツを取捨選択していきます。</p> <p>これはいい感じの左手が生成されたときに消しゴムツールで合成する過程です。同じ絵からimg2imgしてるため、雑にぼかし強めの消しゴムで消すだけでもなんとなく馴染みます(気になる場合は丁寧にトリミングしたり、更にimg2imgして境界部分だけ拾うなどします)。</p> <p><img src="https://manaten.net/wp-content/uploads/2023/02/1%202.gif" alt="1.gif" /></p> <p>手足の他、細かい服の装飾なども好みの形状が生成されたら採用していきます。</p> <p>当然ですが、<strong>好みの顔が出たら最優先で確保します。</strong>一番大事。</p> <p>img2imgする際、 strength値は新たなオブジェクトの生成や背景を書き直したい場合は <code>0.5 ~ 0.6</code> くらい、既存のイラストの細部を直したい場合は <code>0.2 ~ 0.4</code> くらいを指定します。小さめのstrengthはもとの絵の大まかな構造をほとんど維持してくれる反面、元の絵を尊重するあまりほとんど描き換えをしないため、繰り返すと徐々にボケていってしまいます。</p> <p>そのため、局所的に直したいときなど最小限の利用にしたほうが全体の仕上がりがきれいになる印象があります。</p> <h2 id="掃除機の仕上げ">掃除機の仕上げ</h2> <p><img src="https://manaten.net/wp-content/uploads/2023/02/Untitled%208.png" alt="Untitled" /></p> <p>ダイソンの掃除機を強めの strength値でimg2imgすると、やはりこれらの画像のようにめちゃくちゃかっこよくなって掃除機じゃなくなってしまいます。</p> <p><img src="https://manaten.net/wp-content/uploads/2023/02/Untitled%209.png" alt="Untitled" /></p> <p>しょうがないので、StableDiffusion WebUI のInPaint機能で掃除機だけを選択し、さらに小さめのstrength値 ( <code>0.2</code> 程度) で少しずつなじませていきました。</p> <h1 id="4-完成">4. 完成</h1> <p><img src="https://manaten.net/wp-content/uploads/2023/02/20230206%202.png" alt="20230206.png" /></p> <p>以上のようなimg2imgの繰り返しを行い、個人的に満足できたら完成となります。</p> <ul> <li>結局背景は普通に日本の商店街にしました( <code>strength: 0.6</code> くらいで <code>akihabara shoping street</code> にしたら簡単に書き換わりました)。なんかファンタジーっぽい商店街に魔女がいても面白みがないので。</li> <li>掃除機はまだ若干浮いてますが、キリがないのでこのあたりで完了。</li> <li>実は背景にも掃除機らしき物体が生成されています。おそらく <code>witch</code> にも反応して魔法っぽい意匠も生成されています。</li> </ul> <h1 id="まとめ">まとめ</h1> <p>今回はmanatenがStableDiffusionでイラスト生成するときの手順を紹介しました。フィギュアや写真、個別に生成した画像を使い、イメージどおりの構図を作ってからimg2imgしていくという手法を取っています。さらに、<strong>img2imgを繰り返しながら手動で部位ごとに取捨選択して理想形に近づけていく</strong>という手法を紹介しました。</p> <p>この手法は、「AIがノイズからプロンプトに近いのはどちらかを判定することを繰り返して画像生成する」過程を更に人間の意志を介入させながら繰り返すという行為だと考えていて、ある意味<strong>「AIの生成に(力技で)個人の趣味嗜好を混ぜ込む」</strong>という手法だと思っています。その分時間はかかりますが(この画像の生成に数時間。主にダイソンのせい)。</p> <p>また、よく「現状のAIは手を描くのが苦手」とは言われますが、「苦手ではあるがちゃんと描けることもある」ので、その面でもこの手法を取るときちんとしたイラストを生成しやすいです。</p> <p>何より従来のお絵描きとは全く別の体験ではあるものの、「自分で作ってる感」がありなかなか楽しいです。プロンプト生成が苦手な人にもおすすめです。</p> <h1 id="参考資料">参考資料</h1> <ul> <li><a href="https://github.com/AUTOMATIC1111/stable-diffusion-webui">AUTOMATIC1111/stable-diffusion-webui</a></li> <li><a href="https://github.com/Delcos/Hentai-Diffusion">Delcos/Hentai-Diffusion</a></li> <li><a href="https://amzn.to/3x7le5e">S.H.フィギュアーツ ボディちゃん - Amazon</a></li> <li><a href="https://www.dyson.co.jp/dyson-vacuums/cordless/dc62/dc62-motorhead.aspx">Dyson DC62 モーターヘッド | ダイソン公式オンラインストア</a></li> </ul> manaten デスク周り2022 hatenablog://entry/4207112889945952585 2022-12-17T22:35:30+09:00 2024-01-20T15:18:26+09:00 筆者はテレワーク勤務になってそろそろ3年目となるのですが、3年もテレワークをしているとデスク周りにお金をかけるようになりますし、今年はブラックフライデーでFlexispotのデスクを購入したこともありデスク周りが一新されたので、現状のデスク周りのまとめ・総評的な記事を書いてみようと思います。 <p><img src="https://manaten.net/wp-content/uploads/2022/12/desk04.jpg" alt="desk04.jpg" /></p> <p>筆者はテレワーク勤務になってそろそろ3年目となるのですが、3年もテレワークをしているとデスク周りにお金をかけるようになりますし、今年はブラックフライデーでFlexispotのデスクを購入したこともありデスク周りが一新されたので、現状のデスク周りのまとめ・総評的な記事を書いてみようと思います。</p> <h1 id="全貌">全貌</h1> <p><img src="https://manaten.net/wp-content/uploads/2022/12/desk03.jpg" alt="desk03.jpg" /></p> <h2 id="デスク-FLEXISPOT-電動式昇降デスク-EJ2">デスク: <strong>FLEXISPOT 電動式昇降デスク EJ2</strong></h2> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B092J5B2NX?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="noopener"><img src="https://m.media-amazon.com/images/I/31nIXmNhhzL._SL500_.jpg" class="hatena-asin-detail-image" alt="FLEXISPOT スタンディングデスク 電動式昇降デスク EJ2 (足(黒)+天板(竹))" title="FLEXISPOT スタンディングデスク 電動式昇降デスク EJ2 (足(黒)+天板(竹))"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B092J5B2NX?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" target="_blank" rel="noopener">FLEXISPOT スタンディングデスク 電動式昇降デスク EJ2 (足(黒)+天板(竹))</a></p><ul class="hatena-asin-detail-meta"><li>FLEXISPOT</li></ul><a href="https://www.amazon.co.jp/dp/B092J5B2NX?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="noopener">Amazon</a></div></div></p> <p>電動昇降のスタンディングデスク。</p> <p>今までバウヒュッテの手動の昇降スタンディングデスクで天板サイズも100cmx45cmしかないものを使っていましたが(これはこれでコストも安く、天板サイズもモニタアームを使うのであれば必要最小限ではありました)、ブラックフライデーで2万円近く安くなっていたため思い切ってデスクを新調してみました。</p> <p>結果としてはまず<strong>電動昇降</strong>が思った以上に快適でした。全貌写真の通り筆者はエアロバイクと組み合わせてデスクを使っているのですが、立ち姿勢と漕ぎ姿勢で最適な天板の高さが5cmほど異なるので、高さをワンタッチで変更できるのが非常に快適です(よく使う高さを記憶させる<strong>メモリー機能</strong>もあり、4つまで記憶できます)。レビューなどでは昇降音を気にするものもありますが、これは個人差があるのかもしれませんが騒音というほどのものではまったくなく、許容できるものです。</p> <p>また、<strong>天板サイズが120cmx60cm</strong>に巨大化したのも非常に使いやすいです。写真の通り、デスクにはラップトップPC(会社のPC)とminisforumのミニPCが乗っていますが、以前の100cmx45cmでは奥行きが無いのが特に厳しく窮屈だったのですが、新しいデスクでは広々と使えており、キーボード脇で食事や書きものなども十分こなせます。個人的には家庭での仕事用デスクとしてはこのサイズが最適解のひとつなのではと思います。</p> <p>細かいところではバウヒュッテのデスクだと足と足の間に板があったためエアロバイクと組み合わせづらかったところ、このデスクは板がないため奥まで設置可能になり、なおかつデスク自体の安定性も上がったのも嬉しいところです。電動昇降の二本足だけで支えてるのに、意識して揺らさない限りはほとんどグラつかないのはすごい。</p> <p>トータルで見て2022年で一番満足度の高い買い物だったと思っています。</p> <h2 id="MINISFORUM-NUCXI5"><strong>MINISFORUM NUCXI5</strong></h2> <p><a href="https://store.minisforum.jp/products/minisforum-deskmini-nucxi7">https://store.minisforum.jp/products/minisforum-deskmini-nucxi7</a></p> <p>グラボ搭載のミニPC。</p> <p>以前は家PCは高性能ラップトップ一台がベストという派閥だったのですが、電池が膨張して以来「大して持ち運びもしないのに家PCがラップトップである必要はあるのか・・・?」という考えに至り、Intel NUCなどのミニPCをメインPCにする派閥になっています。</p> <p>minisforumはミニPCを専門に扱うメーカーで、このNUCXI5は<strong>ミニPCながらGeforce RTX 3600 Laptopを搭載する</strong>モデルになっています(その代わりミニPCと言うよりは同僚にPS2のパチもんと呼ばれるような見た目になってしまっていますが・・・。他のモデルはもっと小さいです)。</p> <p>ミニPCはメモリ・SSDのアップグレードの自由度があり、同性能帯のラップトップPC寄りは遥かに安価なので、家で「よく考えたらラップトップPCをラップトップPCとして使ってないじゃん・・・でもタワー型を置くスペースはないなあ」みたいなタイプの人にはおすすめの選択肢だと思っています。</p> <h2 id="TEKNOS-強弱切替付きマイヤー調ミニマット"><strong>TEKNOS 強弱切替付きマイヤー調ミニマット</strong></h2> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B0048WQX2K?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="noopener"><img src="https://m.media-amazon.com/images/I/41KWj-3GYPL._SL500_.jpg" class="hatena-asin-detail-image" alt="TEKNOS 強弱切替付きマイヤー調ミニマット (45×45cm) ネイビー EC-K466" title="TEKNOS 強弱切替付きマイヤー調ミニマット (45×45cm) ネイビー EC-K466"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B0048WQX2K?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" target="_blank" rel="noopener">TEKNOS 強弱切替付きマイヤー調ミニマット (45×45cm) ネイビー EC-K466</a></p><ul class="hatena-asin-detail-meta"><li>千住</li></ul><a href="https://www.amazon.co.jp/dp/B0048WQX2K?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="noopener">Amazon</a></div></div></p> <p>小さいホットカーペット。</p> <p>2011年に購入したので現在はAmazonのリンクの写真が違ってます。スタンディングで冷えがちな足元を温める事ができ便利ですが、スタンディングじゃなくても暖房つけたくないときにこの上に体育座りすると体を温められるため便利です。着る毛布と合わせると温かい。寒い日の布団の中に仕込むのもおすすめ。</p> <h1 id="椅子">椅子(?)</h1> <h2 id="エアロバイク-アルインコ-フィットネスバイク-AFB4417">エアロバイク: <strong>アルインコ フィットネスバイク AFB4417</strong></h2> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B07TR4QG65?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="noopener"><img src="https://m.media-amazon.com/images/I/41vN1GPPu4L._SL500_.jpg" class="hatena-asin-detail-image" alt="アルインコ(ALINCO) フィットネスバイク クロスバイク AFB4417XK 静音 サドル調整 組立簡単 折りたたみ" title="アルインコ(ALINCO) フィットネスバイク クロスバイク AFB4417XK 静音 サドル調整 組立簡単 折りたたみ"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B07TR4QG65?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" target="_blank" rel="noopener">アルインコ(ALINCO) フィットネスバイク クロスバイク AFB4417XK 静音 サドル調整 組立簡単 折りたたみ</a></p><ul class="hatena-asin-detail-meta"><li>アルインコ(Alinco)</li></ul><a href="https://www.amazon.co.jp/dp/B07TR4QG65?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="noopener">Amazon</a></div></div></p> <p>安価な折りたたみエアロバイク。</p> <p>椅子とカテゴライズすると違和感あるのですが、エアロバイクです。もともと運動嫌いでテレワークになりますます運動しなくなったため、2年ほど前に購入したものです。買ってからも利用したり利用しなかったりなのですが、デスクを先述のものにしてからは「Youtubeを見ながら漕ぐ」「雀魂でラスりながら漕ぐ」などの<strong>余暇時間のながら運動</strong>が非常にしやすくなり、以前よりも運動できるようになりました。</p> <p>このながら運動というのがぐうたらな自分には適しており、「他のことやりたいから運動しない」から「ゲームするなら運動しながらやる」に思考転換できるようになりました。ゲームをしながらできるながら運動は結構限られており、エアロバイクはコスパの良い方だと思っています(本機は1万円台で変えるのも良いです)。</p> <p>本当は「仕事をしながら漕ぐ」「プログラムを書きながら漕ぐ」をやりたかったんですが、過度に集中を要する業務をしながらだと漕ぐ足が止まったり、逆に漕ぐことにリソースを割かれて集中できなかったりすることがわかりました。人によっては問題ないのかもしれません。この記事は書けてる。</p> <p>もちろん椅子として座りっぱなしというわけではなく、立ち姿勢と座り(漕ぎ)姿勢を気分で切り替えながら作業しています。</p> <h2 id="monolife-エアロバイク-互換ペダル"><strong>monolife エアロバイク 互換ペダル</strong></h2> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B07NDQRD34?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="noopener"><img src="https://m.media-amazon.com/images/I/41PQd+SYECL._SL500_.jpg" class="hatena-asin-detail-image" alt="monolife エアロバイク 互換 ペダル 自転車 修理 交換 エクササイズ フィットネス 左右 ペア セット (Aタイプ 1/2 ネジ径 約12.7mm)" title="monolife エアロバイク 互換 ペダル 自転車 修理 交換 エクササイズ フィットネス 左右 ペア セット (Aタイプ 1/2 ネジ径 約12.7mm)"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B07NDQRD34?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" target="_blank" rel="noopener">monolife エアロバイク 互換 ペダル 自転車 修理 交換 エクササイズ フィットネス 左右 ペア セット (Aタイプ 1/2 ネジ径 約12.7mm)</a></p><ul class="hatena-asin-detail-meta"><li>monolife</li></ul><a href="https://www.amazon.co.jp/dp/B07NDQRD34?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="noopener">Amazon</a></div></div></p> <p>エアロバイクの固定バンド付きペダル。</p> <p>エアロバイクの付属ペダルを交換して使っています。バンドがある方が足が固定されて漕ぐ際のストレスが少なく、また漕ぐことに割く集中力も減らせるのでながらで運動がしやすくなります。バンドなしのペダルを使ってる人はおすすめ。</p> <h2 id="Sportneer-自転車サドルカバー"><strong>Sportneer 自転車サドルカバー</strong></h2> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B0786WDMZV?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="noopener"><img src="https://m.media-amazon.com/images/I/31H1BPDIJJL._SL500_.jpg" class="hatena-asin-detail-image" alt="Sportneer 自転車サドルカバー サドルカバー 低反発クッション 超肉厚 お尻痛くない 通気性 衝撃吸収 取付簡単 可調整 ロードバイクサドル マウンテンバイクサドル 防水防塵カバー付き (xl)" title="Sportneer 自転車サドルカバー サドルカバー 低反発クッション 超肉厚 お尻痛くない 通気性 衝撃吸収 取付簡単 可調整 ロードバイクサドル マウンテンバイクサドル 防水防塵カバー付き (xl)"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B0786WDMZV?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" target="_blank" rel="noopener">Sportneer 自転車サドルカバー サドルカバー 低反発クッション 超肉厚 お尻痛くない 通気性 衝撃吸収 取付簡単 可調整 ロードバイクサドル マウンテンバイクサドル 防水防塵カバー付き (xl)</a></p><ul class="hatena-asin-detail-meta"><li>Sportneer</li></ul><a href="https://www.amazon.co.jp/dp/B0786WDMZV?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="noopener">Amazon</a></div></div></p> <p>エアロバイクのサドルカバー。</p> <p>エアロバイクの付属サドルがめちゃくちゃ固く、30分漕ぐだけでお尻がめちゃくちゃ痛くなってしまうため買いました。ただ、これがあればおしりは一切痛くならないかというとそういうこともなく、ないよりはずっとマシレベル。</p> <p><img src="https://manaten.net/wp-content/uploads/2022/12/desk04.jpg" alt="desk04.jpg" /></p> <h1 id="入力機器周り">入力機器周り</h1> <h2 id="ロジクール-ワイヤレスマウス-MX-Master-2S"><strong>ロジクール ワイヤレスマウス MX Master 2S</strong></h2> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B071Z2TFHX?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="noopener"><img src="https://m.media-amazon.com/images/I/31jLZc7z4SL._SL500_.jpg" class="hatena-asin-detail-image" alt="ロジクール ワイヤレスマウス 無線 マウス MX Master 2S MX2100sGR Unifying Bluetooth 高速充電式 FLOW対応 7ボタン windows mac iPad OS 対応 MX2100s グラファイト 国内正規品" title="ロジクール ワイヤレスマウス 無線 マウス MX Master 2S MX2100sGR Unifying Bluetooth 高速充電式 FLOW対応 7ボタン windows mac iPad OS 対応 MX2100s グラファイト 国内正規品"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B071Z2TFHX?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" target="_blank" rel="noopener">ロジクール ワイヤレスマウス 無線 マウス MX Master 2S MX2100sGR Unifying Bluetooth 高速充電式 FLOW対応 7ボタン windows mac iPad OS 対応 MX2100s グラファイト 国内正規品</a></p><ul class="hatena-asin-detail-meta"><li>Logicool(ロジクール)</li></ul><a href="https://www.amazon.co.jp/dp/B071Z2TFHX?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="noopener">Amazon</a></div></div></p> <p>いいマウス。</p> <p>最近は全然描けてませんが、筆者はドット絵をマウスで描くタイプです。<strong>ドット絵を描くという行為は想像以上にマウスの左クリックを酷使する行為</strong>で、結構な頻度で左クリックが反応しなくなります。そのため、少しでも長持ちさせるために耐久性の高いゲーミングマウスを使用しています。</p> <p>もちろん耐久性以外でも、手に馴染む形で持ちやすさ・疲れにくさに優れます。</p> <h2 id="SteelSeries-ゲーミングマウスパッド-大型-QcK-Edge-XL"><strong>SteelSeries ゲーミングマウスパッド 大型 QcK Edge XL</strong></h2> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B07JPZTHYT?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="noopener"><img src="https://m.media-amazon.com/images/I/21pgG4DdrgL._SL500_.jpg" class="hatena-asin-detail-image" alt="SteelSeries ゲーミングマウスパッド 大型 ステッチ ノンスリップラバーベース 90cm×30cm×0.2cm QcK Edge XL 63824" title="SteelSeries ゲーミングマウスパッド 大型 ステッチ ノンスリップラバーベース 90cm×30cm×0.2cm QcK Edge XL 63824"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B07JPZTHYT?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" target="_blank" rel="noopener">SteelSeries ゲーミングマウスパッド 大型 ステッチ ノンスリップラバーベース 90cm×30cm×0.2cm QcK Edge XL 63824</a></p><ul class="hatena-asin-detail-meta"><li>SteelSeries</li></ul><a href="https://www.amazon.co.jp/dp/B07JPZTHYT?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="noopener">Amazon</a></div></div></p> <p>でっかいマウスパッド。</p> <p>SteelSeriesのマウスパッドは基本的に質が高く、マウスをメイン利用するユーザーなら是非試してほしいのですが、その中でも机の広範囲を覆える大型タイプです。</p> <p>大型タイプのマウスパッドのメリットは、写真の通りマウスだけでなく<strong>キーボード含む作業領域を覆える</strong>ことで、これによりマウスパッド特有の「マウスパッド領域より先にマウスを動かそうとしてガタッとなるストレス」から解消されます。非常に細かいんですが、買ってよかったと思える程度にはストレスが軽減されています。</p> <h2 id="レノボジャパン-ThinkPad-トラックポイントキーボード---英語-0B47190"><strong>レノボ・ジャパン ThinkPad トラックポイント・キーボード - 英語 0B47190</strong></h2> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B00DLK4GN8?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="noopener"><img src="https://m.media-amazon.com/images/I/417fJW4gfkL._SL500_.jpg" class="hatena-asin-detail-image" alt="レノボ・ジャパン USB ThinkPad トラックポイント・キーボード - 英語 0B47190" title="レノボ・ジャパン USB ThinkPad トラックポイント・キーボード - 英語 0B47190"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B00DLK4GN8?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" target="_blank" rel="noopener">レノボ・ジャパン USB ThinkPad トラックポイント・キーボード - 英語 0B47190</a></p><ul class="hatena-asin-detail-meta"><li>Lenovo</li></ul><a href="https://www.amazon.co.jp/dp/B00DLK4GN8?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="noopener">Amazon</a></div></div></p> <p>Thinkpadの外付けキーボード。</p> <p>もともとこれの前モデルからThinkpadキーボードのファンで、<strong>パンタグラフ式キーボードの浅いストロークが好き</strong>なのです。キーボードはそれこそ個人の馴染むものを使うべきだと思うので、特段人に薦めたりはしませんが、「パンタグラフ式が好きだがしっくり来るものに出会えてない」方は使ってみてもいいかもしれません。</p> <p>ちなみにbluetoothの無線タイプも持ってますが、無視できない頻度で接続が切れていたため、有線型に変更しています。マウスと違いケーブルが可動域の邪魔になるということはないので有線でも特にデメリットは感じません。</p> <h2 id="ELEVIEW-KVMスイッチEHD-601N"><strong>ELEVIEW KVMスイッチ【EHD-601N】</strong></h2> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B07CYPB13M?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="noopener"><img src="https://m.media-amazon.com/images/I/518erT5Zz6L._SL500_.jpg" class="hatena-asin-detail-image" alt="ELEVIEW KVMスイッチ パソコン切替器 (PC2台用) 4K(60Hz) HDMI2.0 HDCP2.2対応|モニター/キーボード/マウス(ワイヤレス可)を共有できる 2ポート 安定性改良 バスパワー式 電源不要 USBケーブル付き 【EHD-601N】" title="ELEVIEW KVMスイッチ パソコン切替器 (PC2台用) 4K(60Hz) HDMI2.0 HDCP2.2対応|モニター/キーボード/マウス(ワイヤレス可)を共有できる 2ポート 安定性改良 バスパワー式 電源不要 USBケーブル付き 【EHD-601N】"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B07CYPB13M?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" target="_blank" rel="noopener">ELEVIEW KVMスイッチ パソコン切替器 (PC2台用) 4K(60Hz) HDMI2.0 HDCP2.2対応|モニター/キーボード/マウス(ワイヤレス可)を共有できる 2ポート 安定性改良 バスパワー式 電源不要 USBケーブル付き 【EHD-601N】</a></p><ul class="hatena-asin-detail-meta"><li>ELEVIEW</li></ul><a href="https://www.amazon.co.jp/dp/B07CYPB13M?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="noopener">Amazon</a></div></div></p> <p>2台のPC間でモニタとUSB機器を切り替えられるスイッチ。</p> <p>この手のガジェットはKVMスイッチ(Keyboard、Video、Mouseの略らしい)というのですが、筆者の環境(家PC/会社PC)のように<strong>複数PCでで同じモニタ・マウス・キーボードを使い回すために用いる機器</strong>です。これがあると「いちいちPC切り替え時に繋ぎ変える」「繋ぎ変えが面倒なのでマウスやキーボードを複数用意する」などの煩わしさから開放され、デスク上もすっきりします。</p> <p>原理としてはHDMI切替器と切り替え機能付きUSBハブが一体化された単純なものなのですが、Amazonで調べると知らないメーカーの機器ばかりで選ぶのが難しいジャンルとなっています。選ぶポイントとしては「HDMIがちゃんと自分の求める画質・レートに対応している」「USBが2.0に対応している(キーボード・マウスなら3.0である必要はない)」「USB接続でおかしなことをしていない(機器によってはエミュレーションなどをしており、キーボード相性があったりラグったりする模様)」「稼働を安定させるためにバスパワーでなく自分で電源を取れる」あたりで、本製品は見た目は無骨ながらこれらの条件を満たした理想的な製品となっています。</p> <p>本製品にはおまけで手元操作用のスイッチ(デスクの写真だと右端にあるもの)がついており、購入当時期待してなかったのですが<strong>手元でワンプッシュでPCを切り替えられる</strong>ため非常に便利です。</p> <h2 id="UGREEN-USB-Cハブ"><strong>UGREEN USB Cハブ</strong></h2> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B097YB7CDJ?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="noopener"><img src="https://m.media-amazon.com/images/I/41t526tYuvL._SL500_.jpg" class="hatena-asin-detail-image" alt="UGREEN USB Cハブ 4K@60Hz HDMI出力 100W 急速充電 6-IN-1 Type-Cアダプター 4K HDMI 100W Power Delivery 2*USB 3.0ポート SD/MicroSDカードリーダー Surface Dell MacBook HPXPSなどと互換性のあり" title="UGREEN USB Cハブ 4K@60Hz HDMI出力 100W 急速充電 6-IN-1 Type-Cアダプター 4K HDMI 100W Power Delivery 2*USB 3.0ポート SD/MicroSDカードリーダー Surface Dell MacBook HPXPSなどと互換性のあり"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B097YB7CDJ?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" target="_blank" rel="noopener">UGREEN USB Cハブ 4K@60Hz HDMI出力 100W 急速充電 6-IN-1 Type-Cアダプター 4K HDMI 100W Power Delivery 2*USB 3.0ポート SD/MicroSDカードリーダー Surface Dell MacBook HPXPSなどと互換性のあり</a></p><ul class="hatena-asin-detail-meta"><li>UGREEN</li></ul><a href="https://www.amazon.co.jp/dp/B097YB7CDJ?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="noopener">Amazon</a></div></div></p> <p>普通のTypeC ハブ。</p> <p>会社のMacbook Proを家のモニタや上記のKVMスイッチに接続するのに使っています。この手の商品はたくさんある中これが良かった主だった理由はないのですが、強いて挙げるなら「ディスプレイ要件を満たしている」「比較的安価」あたりが決め手だったと思います。</p> <h1 id="モニタ周り">モニタ周り</h1> <h2 id="Dell-U2719D-27インチ-モニター"><strong>Dell U2719D 27インチ モニター</strong></h2> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B07MXJWJD3?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="noopener"><img src="https://m.media-amazon.com/images/I/51N-aN18d5L._SL500_.jpg" class="hatena-asin-detail-image" alt="Dell U2719D 27インチ モニター (3年間無輝点交換保証/WQHD/IPS非光沢/DP,HDMI/縦横回転,高さ調整/Rec.709 99%)" title="Dell U2719D 27インチ モニター (3年間無輝点交換保証/WQHD/IPS非光沢/DP,HDMI/縦横回転,高さ調整/Rec.709 99%)"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B07MXJWJD3?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" target="_blank" rel="noopener">Dell U2719D 27インチ モニター (3年間無輝点交換保証/WQHD/IPS非光沢/DP,HDMI/縦横回転,高さ調整/Rec.709 99%)</a></p><ul class="hatena-asin-detail-meta"><li>Dell</li></ul><a href="https://www.amazon.co.jp/dp/B07MXJWJD3?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="noopener">Amazon</a></div></div></p> <p>27インチのWQHDモニタ。</p> <p>モニタも発色やフレームレートなどで要件がいくらでも変わる為難しいのですが、自分がこのモニタにした理由は<strong>「27インチであること」「WQHD解像度であること」</strong>でした。</p> <p>昔はモニタ5枚で仕事していたこともあったのですが、「情報量が多いと集中力が削がれるのでいうほど効率的ではない」ということに気づいて以来、大きめのモニタ1枚のスタイルに落ち着いています。大きめのモニタとしては「4K」「ウルトラワイド」という選択肢もありますが、会社でこのサイズのモニタを使っていてしっくり来ていたので家でも同サイズのモニタを利用しています。フルHDは小さすぎると感じる。</p> <p>最近だとモニタ自体にKVMスイッチを内蔵し、USB TypeCでPC本体と接続して切り替えられるタイプのモニタもあるため、買い替えるならそういった機種にする可能性はあるなと思っています。まだまだ使えているため当分先かなとは思っています。</p> <h2 id="エルゴトロン-LX-デスクマウント-モニターアーム"><strong>エルゴトロン LX デスクマウント モニターアーム</strong></h2> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B00358RIRC?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="noopener"><img src="https://m.media-amazon.com/images/I/31pj8pl4c1L._SL500_.jpg" class="hatena-asin-detail-image" alt="エルゴトロン LX デスク モニターアーム アルミニウム 34インチ(3.2~11.3kg)まで VESA規格対応 45-241-026" title="エルゴトロン LX デスク モニターアーム アルミニウム 34インチ(3.2~11.3kg)まで VESA規格対応 45-241-026"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B00358RIRC?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" target="_blank" rel="noopener">エルゴトロン LX デスク モニターアーム アルミニウム 34インチ(3.2~11.3kg)まで VESA規格対応 45-241-026</a></p><ul class="hatena-asin-detail-meta"><li>エルゴトロン</li></ul><a href="https://www.amazon.co.jp/dp/B00358RIRC?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="noopener">Amazon</a></div></div></p> <p>モニタアームの大御所エルゴトロンのアーム。</p> <p>モニタアームが欲しい場合ははじめはエルゴトロンのものを選んでおけば間違いないと思っています。クランプ式なのでデスクに穴を開けずに取付可能で、アームはフレキシブルに動き思い通りの場所に移動できます。地味ながら高さ調節やアームの硬さを調節するねじがあったりと、馴染む使い心地にカスタマイズも可能です。</p> <p>モニタアームを使うメリットは<strong>「モニタ下の空間を有効活用できること」「体制によって自由に気軽にモニタ位置を変更できること」</strong>だと思っていて、特に自分のスタイルだと立ち姿勢と漕ぎ姿勢でモニタの位置を変えたいこともままあるので便利に使っています。</p> <h2 id="Anker-PowerConf-C300-ウェブカメラ"><strong>Anker PowerConf C300 ウェブカメラ</strong></h2> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B08WX1XB5Y?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="noopener"><img src="https://m.media-amazon.com/images/I/31stzn+aTKL._SL500_.jpg" class="hatena-asin-detail-image" alt="Anker PowerConf C300 ウェブカメラ AI機能搭載 フル HD モーショントラッキング 高速オートフォーカス 1080p ノイズリダクション オートゲインコントロール 画角調整機能 プライバシーカバー Zoom認証" title="Anker PowerConf C300 ウェブカメラ AI機能搭載 フル HD モーショントラッキング 高速オートフォーカス 1080p ノイズリダクション オートゲインコントロール 画角調整機能 プライバシーカバー Zoom認証"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B08WX1XB5Y?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" target="_blank" rel="noopener">Anker PowerConf C300 ウェブカメラ AI機能搭載 フル HD モーショントラッキング 高速オートフォーカス 1080p ノイズリダクション オートゲインコントロール 画角調整機能 プライバシーカバー Zoom認証</a></p><ul class="hatena-asin-detail-meta"><li>Anker</li></ul><a href="https://www.amazon.co.jp/dp/B08WX1XB5Y?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="noopener">Amazon</a></div></div></p> <p>Ankerのマイクもついてる高性能Webカメラ。</p> <p>会議のカメラ画質でマウントを取りたかったので買ったカメラです。カメラにはオートフォーカス機能が搭載されており、顔認識して勝手に顔が中心に来るようズーム・追従してくれます。これも他の会議参加者にマウントが取れるおもしろポイント。</p> <p>ノイズリダクション・オートゲイン機能のあるマイクも付いており、そこそこの音質が出るため<strong>会議ではそのままマイクもこれのものを利用</strong>しています。マイクがカメラに一体化していることでPC接続機器が減るのも個人的にメリット。</p> <p>地味にプライバシーシールドがついているのも便利で、閉じた状態でバーチャル背景を表示すると背景まるまる表示ができるため、最近はマックの期間限定商品の広告を背景にして飯テロをするのがブームです。</p> <p>なんか実用性ではなくエンタメ性の紹介ばかりですが、会議で他人の顔の細かいとこまで見てる人なんてそんなにいないと思うので(そもそも資料が映されていることが大半)、Webカメラはエンタメ性能で選ぶのが良いと思います。</p> <h1 id="収納など">収納など</h1> <h2 id="サイドバスケットクランパー--ブラック-"><strong>サイドバスケットクランパー [ ブラック ]</strong></h2> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B07SKF1RCP?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="noopener"><img src="https://m.media-amazon.com/images/I/51pSB+3iIFL._SL500_.jpg" class="hatena-asin-detail-image" alt="サイドバスケットクランパー [ ブラック ] SIDE BASKET CLAMPER" title="サイドバスケットクランパー [ ブラック ] SIDE BASKET CLAMPER"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B07SKF1RCP?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" target="_blank" rel="noopener">サイドバスケットクランパー [ ブラック ] SIDE BASKET CLAMPER</a></p><ul class="hatena-asin-detail-meta"><li>ノーブランド品</li></ul><a href="https://www.amazon.co.jp/dp/B07SKF1RCP?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="noopener">Amazon</a></div></div></p> <p>クランプ式のかご。</p> <p>写真のデスク左右に付いてるやつです。Flexispot製品に限らずスタンディングデスクは引き出しもついておらず収納性皆無な物が多いですが、この製品を装着することである多少は解消します。もちろん引き出しそのものと比べると収納力はおまけみたいなものですが、<strong>「後で読む系の書類」「デスクに置いとくと散らばる小物」</strong>を収納しています。</p> <p>特に筆者のようなズボラだとデスク上は勝手に物が溜まっていくので、そういったものの<strong>とりあえずの退避先</strong>として優秀です。ちなみに本来はもっといろんな物が入っていますが、写真では見えを張ってどかしています。</p> <h2 id="サンワダイレクト-ドリンクホルダー-バッグハンガー-STN063BK"><strong>サンワダイレクト ドリンクホルダー バッグハンガー STN063BK</strong></h2> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B09PDMGHP5?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="noopener"><img src="https://m.media-amazon.com/images/I/31HUtxrpnnL._SL500_.jpg" class="hatena-asin-detail-image" alt="サンワダイレクト ドリンクホルダー デスク ヘッドホンハンガー バッグハンガー クランプ式 金属製 耐荷重5kg ブラック 200-STN063BK" title="サンワダイレクト ドリンクホルダー デスク ヘッドホンハンガー バッグハンガー クランプ式 金属製 耐荷重5kg ブラック 200-STN063BK"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B09PDMGHP5?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" target="_blank" rel="noopener">サンワダイレクト ドリンクホルダー デスク ヘッドホンハンガー バッグハンガー クランプ式 金属製 耐荷重5kg ブラック 200-STN063BK</a></p><ul class="hatena-asin-detail-meta"><li>サンワダイレクト</li></ul><a href="https://www.amazon.co.jp/dp/B09PDMGHP5?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="noopener">Amazon</a></div></div></p> <p>クランプ式のドリンクホルダー+ハンガー。</p> <p>デスク左に装着しています。スタンディングデスクだとコップを倒したときの被害範囲が高さのぶんだけ拡大するため安全性を求めて購入。特に食洗機を導入してから新たに購入した食洗機対応の素材のコップが軽く不安だったたという事情もあります。</p> <p>地味ながら下部にバッグハンガーもついており、これも便利です。出社時代だったら自分のデスクにも購入していたかもしれない。</p> <h2 id="プラス-Garage-ケーブルトレー-YY-04DCT"><strong>プラス Garage ケーブルトレー YY-04DCT</strong></h2> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B00CDGRQEM?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="noopener"><img src="https://m.media-amazon.com/images/I/41FYPdU1vWL._SL500_.jpg" class="hatena-asin-detail-image" alt="プラス Garage ケーブルトレー 幅39.7cm ワイヤーケーブルトレー 穴あけ不要 ケーブル配線トレー 配線整理 ケーブル整理 ケーブル収納 S YY-04DCT シルバー 410269" title="プラス Garage ケーブルトレー 幅39.7cm ワイヤーケーブルトレー 穴あけ不要 ケーブル配線トレー 配線整理 ケーブル整理 ケーブル収納 S YY-04DCT シルバー 410269"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B00CDGRQEM?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" target="_blank" rel="noopener">プラス Garage ケーブルトレー 幅39.7cm ワイヤーケーブルトレー 穴あけ不要 ケーブル配線トレー 配線整理 ケーブル整理 ケーブル収納 S YY-04DCT シルバー 410269</a></p><ul class="hatena-asin-detail-meta"><li>PLUS(プラス)</li></ul><a href="https://www.amazon.co.jp/dp/B00CDGRQEM?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="noopener">Amazon</a></div></div></p> <p><img src="https://manaten.net/wp-content/uploads/2022/12/desk01.jpg" alt="desk01.jpg" /></p> <p>クランプ式のケーブルトレー。</p> <p><strong>PCデスク周りはケーブルでごちゃごちゃになる宿命</strong>にありますが、この製品によって多少軽減しています。写真もまだごちゃごちゃしているものの、これがなかったらテーブルタップもアダプタも机の上に置くしかなく机の上がこの状態になってしまいます。収納のないスタンディングデスクだと必須級。</p> <p>おまけでプーさんなどがぶら下がっています。</p> <h2 id="Anker-PowerPort-Strip-PD-6"><strong>Anker PowerPort Strip PD 6</strong></h2> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B08C7GH9Z8?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="noopener"><img src="https://m.media-amazon.com/images/I/31F+NaWydOL._SL500_.jpg" class="hatena-asin-detail-image" alt="Anker PowerPort Strip PD 6(電源タップ コンセント差込口 6口 USB-C 1ポート USB-A 2ポート 延長コード 2m)【PSE技術基準適合 / USB Power Delivery対応 / ほこり防止シャッター】" title="Anker PowerPort Strip PD 6(電源タップ コンセント差込口 6口 USB-C 1ポート USB-A 2ポート 延長コード 2m)【PSE技術基準適合 / USB Power Delivery対応 / ほこり防止シャッター】"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B08C7GH9Z8?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" target="_blank" rel="noopener">Anker PowerPort Strip PD 6(電源タップ コンセント差込口 6口 USB-C 1ポート USB-A 2ポート 延長コード 2m)【PSE技術基準適合 / USB Power Delivery対応 / ほこり防止シャッター】</a></p><ul class="hatena-asin-detail-meta"><li>Anker</li></ul><a href="https://www.amazon.co.jp/dp/B08C7GH9Z8?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="noopener">Amazon</a></div></div></p> <p>Anker製のTypeC給電もできる6個口電源タップ。</p> <p>ケーブルトレーと組み合わせてデスクまわりの電源を集約します。タップは好きなものを使えばいいのですが、USB給電をする場合はAnker製品に利があると感じたので購入。<strong>電源6口のほかTypeC 1口とTypeA 2口に給電</strong>できます。ホコリが溜まりやすいのでホコリシャッターが付いてるのも嬉しいですね。</p> <h2 id="Tohoer-マグネットフック"><strong>Tohoer マグネットフック</strong></h2> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B07MVJ329V?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="noopener"><img src="https://m.media-amazon.com/images/I/517Duq9FQ7L._SL500_.jpg" class="hatena-asin-detail-image" alt="Tohoer【8個セット】マグネット フック 磁石付き ステンレス製 防錆 垂直耐荷重総計10kg 直径20mm キッチン用 オフィス用 浴室 お風呂 壁掛け用 ブラック" title="Tohoer【8個セット】マグネット フック 磁石付き ステンレス製 防錆 垂直耐荷重総計10kg 直径20mm キッチン用 オフィス用 浴室 お風呂 壁掛け用 ブラック"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B07MVJ329V?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" target="_blank" rel="noopener">Tohoer【8個セット】マグネット フック 磁石付き ステンレス製 防錆 垂直耐荷重総計10kg 直径20mm キッチン用 オフィス用 浴室 お風呂 壁掛け用 ブラック</a></p><ul class="hatena-asin-detail-meta"><li>Tohoer</li></ul><a href="https://www.amazon.co.jp/dp/B07MVJ329V?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="noopener">Amazon</a></div></div></p> <p><img src="https://manaten.net/wp-content/uploads/2022/12/desk02.jpg" alt="desk02.jpg" /></p> <p>普通のマグネットフック。</p> <p>ダイソーとかでも変えると思いますが、こちらはマグネットが強力なのとカラーがデスクの支柱に馴染むため購入。写真のようにごみ袋をぶら下げる他、デスク周りでぶら下げて収納できるものの収納にも用いています。</p> <p>スタンディングデスクだと床上のゴミ箱が遠く、<strong>鼻をかんだティッシュを入れようとして外すことが多々ある</strong>ので、地味ながらこの位置にゴミ袋をぶら下げるのは便利です。ただ見た目が不格好なので、より良いソリューションをお持ちの方はご教示いただけると幸いです。</p> <p>マグネットが強力なので、一応昇降機のモーター付近にはつけないようにしています。</p> <h2 id="TRUSCOトラスコ-マジックバンド結束テープ"><strong>TRUSCO(トラスコ) マジックバンド結束テープ</strong></h2> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B004OCOY84?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="noopener"><img src="https://m.media-amazon.com/images/I/412cULhXuCL._SL500_.jpg" class="hatena-asin-detail-image" alt="TRUSCO(トラスコ) マジックバンド結束テープ 両面 黒 10mm×1.5m MKT1015BK" title="TRUSCO(トラスコ) マジックバンド結束テープ 両面 黒 10mm×1.5m MKT1015BK"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B004OCOY84?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" target="_blank" rel="noopener">TRUSCO(トラスコ) マジックバンド結束テープ 両面 黒 10mm×1.5m MKT1015BK</a></p><ul class="hatena-asin-detail-meta"><li>トラスコ中山(TRUSCO)</li></ul><a href="https://www.amazon.co.jp/dp/B004OCOY84?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="noopener">Amazon</a></div></div></p> <p>取り回しの良い両面マジックテープ。</p> <p>ケーブルを束ねたり、モニタアームに固定したりするのに使っています。この手の製品は色々あるのですが、本製品は厚みがありしっかり固定できるため類似品と比較して優秀で、昔から愛用しています。</p> <h2 id="マジックテープ黒粘着タイプ25mm15cm"><strong>マジックテープ(黒、粘着タイプ、25mm×15cm)</strong></h2> <p><a href="https://jp.daisonet.com/collections/handmade0220/products/4905429019557">https://jp.daisonet.com/collections/handmade0220/products/4905429019557</a></p> <p><img src="https://manaten.net/wp-content/uploads/2022/12/desk05.jpg" alt="desk05.jpg" /></p> <p>両面テープで固定するタイプのマジックテープ。</p> <p>KVMスイッチやTypeCハブを写真のようにデスクに固定するために使っています。特にTypeCハブはPC持ち運びの際に外したいため、マジックテープで固定すると取り外し自由で便利です。</p> <h1 id="その他">その他</h1> <h2 id="Google-Nest-Hub第-2-世代"><strong> Google Nest Hub(第 2 世代)</strong></h2> <p><a href="https://store.google.com/jp/product/nest_hub_2nd_gen?hl=ja">Google Nest Hub (第 2 世代)</a></p> <p>画面のついてるGoogle Home。</p> <p>最初サブモニタ的にTODOリストでも表示しようと思ったのですがそういう製品ではなく、もっぱら余暇時間にYoutubeなどをキャストする先として使っています。仕事中はただの時計。</p> <p>スピーカーの音量も出るので、睡眠時に雨音を流すのにも使っています(常時耳鳴りがあるので雑音を流さないと寝付けない)。</p> <p>Switchbotと連携して照明やエアコンの操作もできるようにしてますが、これだけだとAndroidスマホがあれば事足りるので付加価値ありきの製品だと思う。</p> <h2 id="りぶはあと-てのひらモッチ-プレミアムねむねむアニマルズ-カワウソのくるり"><strong>りぶはあと てのひらモッチ プレミアムねむねむアニマルズ カワウソのくるり</strong></h2> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B083NYQVXY?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="noopener"><img src="https://m.media-amazon.com/images/I/41s5007lK8S._SL500_.jpg" class="hatena-asin-detail-image" alt="りぶはあと リラクシングアイテム てのひらモッチ プレミアムねむねむアニマルズ カワウソのくるり (全長約5cm) かわいい ストレス解消 61046-33" title="りぶはあと リラクシングアイテム てのひらモッチ プレミアムねむねむアニマルズ カワウソのくるり (全長約5cm) かわいい ストレス解消 61046-33"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B083NYQVXY?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" target="_blank" rel="noopener">りぶはあと リラクシングアイテム てのひらモッチ プレミアムねむねむアニマルズ カワウソのくるり (全長約5cm) かわいい ストレス解消 61046-33</a></p><ul class="hatena-asin-detail-meta"><li>りぶはあと(Livheart)</li></ul><a href="https://www.amazon.co.jp/dp/B083NYQVXY?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="noopener">Amazon</a></div></div></p> <p>カワウソのくるり。</p> <p><strong>かわいい。ぷにぷにしてる。</strong></p> <h2 id="Present-web七色に変化するUSB対応LEDミニクリスマスツリー"><strong>[Present-web]七色に変化する★USB対応LEDミニクリスマスツリー</strong></h2> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B002ZBN3B0?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="noopener"><img src="https://m.media-amazon.com/images/I/51d+gSl7PcL._SL500_.jpg" class="hatena-asin-detail-image" alt="[Present-web]七色に変化する★USB対応LEDミニクリスマスツリー" title="[Present-web]七色に変化する★USB対応LEDミニクリスマスツリー"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B002ZBN3B0?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" target="_blank" rel="noopener">[Present-web]七色に変化する★USB対応LEDミニクリスマスツリー</a></p><ul class="hatena-asin-detail-meta"><li>Hamee ストラップヤ</li></ul><a href="https://www.amazon.co.jp/dp/B002ZBN3B0?tag=manaten-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="noopener">Amazon</a></div></div></p> <p>USB電源で7色に光るゲーミングクリスマスツリー。</p> <p>12月にだけ登場するアイテム。もともと会社で使っていましたが、テレワークで私物はすべて引き払ったため自宅のデスク上にて活躍。ちゃちい作りながら10年近く使えてるので意外とやりおる。</p> <h1 id="まとめ">まとめ</h1> <p>いつかデスク周りのエントリを書きたかったのですが、今回デスクを新調したことに伴いやる気が出たので満を持して書いてみました。</p> <p>デスク周りは意外と大物より小技的な小物が人の役に立ったりすると思っているので、物が増えたりしたタイミングでまた書いていこうかなと思っています。</p> manaten レガシーなjs環境におすすめなts-checkコメントについて hatenablog://entry/13574176438100634278 2022-06-10T11:30:00+09:00 2022-12-17T19:49:57+09:00 このエントリの内容 このエントリは、javascriptの型チェックをすることができるtypescriptの機能 @ts-check について紹介し、個人的に感じた便利なところや使い所を紹介していきます。 このエントリの読者は以下のような人を想定しています。 レガシーな環境でTypeScriptを導入しづらいが、複雑化するコードベースを少しでも楽に保守したい人 TypeScriptに興味があるが、昨今のjsのツールチェインについていけず、とりあえず試してみたい人 <h1 id="このエントリの内容">このエントリの内容</h1> <p>このエントリは、javascriptの型チェックをすることができるtypescriptの機能 <a href="https://www.typescriptlang.org/docs/handbook/intro-to-js-ts.html">@ts-check</a> について紹介し、個人的に感じた便利なところや使い所を紹介していきます。</p> <p>このエントリの読者は以下のような人を想定しています。</p> <ul> <li>レガシーな環境でTypeScriptを導入しづらいが、複雑化するコードベースを少しでも楽に保守したい人</li> <li>TypeScriptに興味があるが、昨今のjsのツールチェインについていけず、とりあえず試してみたい人</li> </ul> <h1 id="ts-checkとは">@ts-checkとは</h1> <p>@ts-checkとは、JavaScriptファイルの先頭に <code>//@ts-check</code> というコメントをすることで、TypeScriptコンパイラや、TypeScriptを標準で型チェックできる <a href="https://code.visualstudio.com/">VSCode</a> により、JavaScriptファイルに対してTypeScript相当の型チェックを行う事ができる仕組みです。</p> <p><img src="https://manaten.net/wp-content/uploads/2022/06/tscheck00.png" alt="tscheck" /></p> <p>上記の例では、Number型の変数aにはpushというプロパティが存在しないため、型エラーとなっています。</p> <p>なおts-checkとはチェックを有効化するためのコメントのことで正確な機能名ではありません。 <a href="https://www.typescriptlang.org/docs/handbook/intro-to-js-ts.html">JS Projects Utilizing TypeScript</a> にて解説されている、 <code>@ts-check</code> コメントを書くことでTypeScriptコンパイラにJavaScriptファイルもチェックさせることを、このエントリでは便宜上ts-checkと呼びます。</p> <h1 id="なにができるのか">なにができるのか</h1> <p><code>@ts-check</code> と書くことでVSCodeにJavaScriptファイルも型チェックさせることができるのは上記のとおりですが、より詳しく説明すると以下のことができます。</p> <ul> <li>TypeScriptと同様の型推論を行い、型定義がなくてもある程度型を推論してくれ、型チェックができる</li> <li>型が機能することによる、VSCodeでの厳密なコード補完</li> <li>JSDocによる、明示的な型付けや型定義</li> <li>TypeScriptの型定義ファイルの利用による、外部ライブラリの型チェック・コード補完</li> </ul> <p>それぞれ詳しく見ていきます。</p> <h2 id="TypeScriptと同様の型推論を行い型定義がなくてもある程度型を推論してくれ型チェックができる">TypeScriptと同様の型推論を行い、型定義がなくてもある程度型を推論してくれ、型チェックができる</h2> <p>冒頭の例がまさにそうですが、一切型宣言のないJavaScriptファイルでも、推論可能な型は型推論してくれ(これ自体はTypeScriptでも一緒で、すべての変数に型定義する必要はないです)、今までのJavaScriptファイルにとりあえず <code>@ts-check</code> を書くだけでもある程度は型チェックすることができてしまいます。</p> <p>変数宣言だけでなく、関数も推論可能であれば推論できます。</p> <p><img src="https://manaten.net/wp-content/uploads/2022/06/tscheck01.png" alt="tscheck" /></p> <p>この例では明らかにnumberしか返さない関数であるため、numberを返す関数であると推論できています。また、<code>Math.floor</code> <code>Math.random</code> を呼び出していますが、標準関数についても型定義をTypeScriptが知っているため、型チェック可能です。</p> <h2 id="型が機能することによるVSCodeでの厳密なコード補完">型が機能することによる、VSCodeでの厳密なコード補完</h2> <p>TypeScriptユーザーからすると当たり前のことではありますが、JavaScriptファイルもTypeScriptが型チェックできるということは、VSCodeが厳密なコード補完をできるということです。</p> <p><img src="https://manaten.net/wp-content/uploads/2022/06/tscheck02.png" alt="tscheck" /></p> <p>上記の例では、ブラウザAPIでおなじみの <code>document.getElementById</code> で取得した <code>HTMLElement</code> に対して、プロパティ名( <code>addEventListener</code> )を補完させようとしています。大量にあるブラウザAPIを補完でき、またタイポしていたりシグニチャを間違えていたりしてもチェックでエラーを出してくれるため、非常に便利です。(ちなみに、ブラウザAPIも標準で読み込まれます)</p> <h2 id="JSDocによる明示的な型付けや型定義">JSDocによる、明示的な型付けや型定義</h2> <h3 id="明示的な型宣言">明示的な型宣言</h3> <p>ここまでは型推論のみでの型チェックを紹介してきましたが、JSDocを使うことで、型宣言を行うこともできます。</p> <p><img src="https://manaten.net/wp-content/uploads/2022/06/tscheck03.png" alt="tscheck" /></p> <p>上記では、string型を引数に取る <code>sayHello</code> 関数を実装しており、直後にnumber型で呼び出そうとしたためチェックエラーとなっています。</p> <p>また、関数の戻り値を間違っている際もエラーにすることができます。</p> <p><img src="https://manaten.net/wp-content/uploads/2022/06/tscheck04.png" alt="tscheck" /></p> <p>しっかりJSDocで型宣言をしておくことで、誤った型を返却したり、returnの書き忘れを防止することができます。</p> <h3 id="型定義">型定義</h3> <p>また、JSDocにより自作型の定義も行うことができます。</p> <p><img src="https://manaten.net/wp-content/uploads/2022/06/tscheck05.png" alt="tscheck" /></p> <p><code>@typedef</code> により、型に名前を与えることができ(ここで型リテラルとして、TypeScriptで有効な型はだいたい与えることができます)、 その型を <code>@type</code> 宣言で変数に型宣言しています。画像のように、型宣言した変数に対し、型を間違えたプロパティを与えたり、存在しないプロパティを与えたりしようとするとエラーとなります。</p> <p><img src="https://manaten.net/wp-content/uploads/2022/06/tscheck06.png" alt="tscheck" /></p> <p>また、こうして定義された型は当然VSCodeでのコード補完をすることもできます。JavaScriptで独自のオブジェクトを定義する機会は非常に多いため、これらを型安全に扱うことができるのはとても便利です。ちなみに、 <code>@typedef</code> しなくてもある程度は型推論でどうにかなることもありますが、型定義したほうがより安全にコーディングできるため断然おすすめです。</p> <p>JSDocによりTypeScriptコンパイラができることは、 <a href="https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html">TypeScript: Documentation - JSDoc Reference</a> に書れているため、より詳しく知りたい人はこちらも参照してください。</p> <h2 id="TypeScriptの型定義ファイルの利用による外部ライブラリの型チェックコード補完">TypeScriptの型定義ファイルの利用による、外部ライブラリの型チェック・コード補完</h2> <p>TypeScript向けに書かれた型定義ファイルを利用することで、外部ライブラリについても型チェックすることができます。</p> <p><img src="https://manaten.net/wp-content/uploads/2022/06/tscheck07.png" alt="tscheck" /></p> <p>上記例では、型定義ファイルが提供されているlodashをrequireしたときに、JavaScriptファイルでもlodash配下の関数が型定義されており補完できることを示しています。</p> <p>また、 <code>@type</code> 宣言時に import を行うことで外部の型定義を利用して変数宣言することもできます。</p> <p><img src="https://manaten.net/wp-content/uploads/2022/06/tscheck08.png" alt="tscheck" /></p> <p>上記の例は、next.jsが提供しているConfigの型定義をnextConfigというオブジェクトの変数に適用してあげることで、プロパティ名の補完や型チェックを可能にしています。設定ファイルをjsで書かないといけない場合に設定名の間違いなどを減らすことができ、とても便利です。</p> <h1 id="TypeScriptの型の美味しい話">TypeScriptの型の美味しい話</h1> <p>この節はTypeScript初学者向けに、 <code>@ts-check</code> を通して利用できるTypeScriptの(個人的に)便利機能を紹介していきます。</p> <h2 id="型推論">型推論</h2> <p>ここまでで何度か紹介しましたが、<strong>「人間が明示的に型を宣言しなくても、コンパイラが分かる範囲で型をつけてくれる」</strong>機能となります。</p> <p><img src="https://manaten.net/wp-content/uploads/2022/06/tscheck01.png" alt="tscheck" /></p> <p>先程のmyRandomの例ですが、「Math.randomはnumberを返す」「number*numberはnumberを返す」「Math.floorはnumberを返す」ことから、myNumberはnumberを返す関数であることを勝手に推論してくれています。</p> <p>これは、もともと型のなかったコードをとりあえず型チェックしたい場合に非常に便利で、「型が大事なのはわかるけど、今あるコードを全部治すことはできないよ」という悩みを大幅に軽減してくれます。</p> <p>また、これから書くコードであっても「推論可能な型はわざわざ人間が書く必要がない」ということになります。</p> <p><img src="https://manaten.net/wp-content/uploads/2022/06/tscheck09.png" alt="tscheck" /></p> <p>たとえば、上記の例では <code>addEventListener</code> の第2引数に与えたクロージャの引数は、 <code>Event</code> であることが推論可能であるため、特に型宣言などしなくても勝手にevは <code>Event</code> 型となり、プロパティ名の補完や型チェックを行うことができます。</p> <h2 id="漸進的型つけ">漸進的型つけ</h2> <p>TypeScriptといえば、最も世界で使われている漸進的型付けの言語であると言えます。</p> <p><a href="https://qiita.com/t2y/items/0a604384e18db0944398">What is Gradual Typing: 漸進的型付けとは何か - Qiita</a></p> <p>漸進的型付けの言語は、動的型と静的型のいいとこ取りと表現されることもありますが、大雑把に以下のような振る舞いをする言語という風に理解しています(間違っていたら指摘してください・・・)。</p> <ul> <li>型付けのある変数や関数に対しては、静的言語のように型チェックを行う <ul> <li>ここまでで触れてきたとおり</li> </ul> </li> <li>型付けがない変数や関数に対しては、動的言語のように振る舞う <ul> <li>つまり型チェックをせず、型付けの無い変数に対しては任意の演算が可能であるし、任意の関数に渡すことができる。型付けのない関数は任意の引数を受け取ることができる。それでまずかった場合は実行時にエラーとなる。 <ul> <li>※ ただしTypeScriptは型推論も行うことができるため、人間が型を書かなかったコードが必ずしも型付けがないわけではない</li> </ul> </li> <li>このような型付けがない変数は、型としては <code>any</code> 型となり、上記のような挙動となる。</li> </ul> </li> <li><p>型付けのない変数を後付けで型付けすることができる</p> <ul> <li>TypeScriptでは上記のように型付けがない変数は <code>any</code> 型となるが、 <code>any</code>型の変数は任意の型の変数に代入可能で、代入するとその型の変数として扱われる <ul> <li>型定義のない未知のライブラリの返却値は <code>any</code>となるが、返す型がわかっているなら適切な型に再代入できる</li> <li><p>よくある例としては、API返却値に対するJSON.parseなど。JSON.parseの返却値はanyとなってしまうが、プログラマは実際のAPIの返却型を知っているので、型を後付けできる</p> <p> <img src="https://manaten.net/wp-content/uploads/2022/06/tscheck10.png" alt="tscheck" /></p></li> </ul> </li> </ul> </li> <li><p>型付けのある変数を後付で型をなくすことができる</p> <ul> <li>型定義のない外部ライブラリの関数に対して、型付けされた変数を渡すことができる</li> </ul> </li> </ul> <p>漸進的型付けのこれら特徴と、型推論を合わせて、従来の型のないJavaScriptのファイルでも動かすことができ、またTypeScript対応していない過去のリソースでもとりあえず動かすことができるのがTypeScriptの強みです。もちろん、 <code>any</code> が多ければ多いほど型の恩寵は受けれなくなるので、適宜型定義をしていくことが大切です。</p> <h2 id="union-typeとtype-guard">union typeとtype guard</h2> <p>個人的にTypeScriptで一番好きな機能として、Union TypeとType Guardがあります。</p> <p>Union Type では <code>|</code> をつかって型定義することで、 「A型またはB型」という型を定義することができます。一番わかりやすい例はnullableでしょう(※ nullチェックを有効化するには、TypeScriptのstrictオプションを使う必要があります)。</p> <p><img src="https://manaten.net/wp-content/uploads/2022/06/tscheck11.png" alt="tscheck" /></p> <p>この例では「idに対応する値があれば値を、なければnullを返す」関数を <code>string | null</code> としてstringまたはnullを返す関数として宣言しており、その結果をそのままstringとして使おうとしたため、nullの可能性があるとしてエラーが出力されています。JavaScriptにおいてnullまたは値を返す関数は頻出し、都度nullチェックを怠らないということが求められますが、TypeScriptではこの悩みから開放されます。</p> <p>さらに、Union TypeはType Guardの仕組みを使って快適に操作ができます。</p> <p><img src="https://manaten.net/wp-content/uploads/2022/06/tscheck12.png" alt="tscheck" /></p> <p>この例では、if文で「someNameはnullではない」とチェックした場合、そのブロック内では <strong>someNameをstringとして</strong> 扱うことができています。このような、条件分岐(など)によって型を限定し、実際に型チェッカーも限定された型として扱ってくれる機能を<strong>Type Guard</strong>といったりします。Type Guardの素晴らしいところは、自然なコードで必要な条件分岐をした結果コンパイラも自然に型を断定してくれ、その後も自然にコードを書き続けられるところです(TypeGuardがない言語では、明示的にキャストをする必要があるはずです)。</p> <p>最後に、Union TypeとType Guardのより複雑な例を紹介して終了します。</p> <p><img src="https://manaten.net/wp-content/uploads/2022/06/tscheck13.png" alt="tscheck" /></p> <p>この例では、bowメソッドを持つDog型とmeowメソッドを持つCat型、そしてそのいずれかであるAnimal型を定義しています。そして、実際のanimal型の変数に対して、typeプロパティがdogなのかcatなのかにより、Type Guardで型を限定し、型エラーとならずにmeowメソッドやbowメソッドを呼び出せています(TypeGuard外での呼び出しや、誤った組み合わせはエラーになります)。</p> <p>現実でもこういった、すぐには内容を特定できないような型というのはしばしば発生するため、その際も非常に役立つ機能です。</p> <h2 id="ジェネリクス">ジェネリクス</h2> <p>TypeScriptではジェネリクスを利用でき、総称型に型パラメータを与えることで、特化した型を生成できます。</p> <p><img src="https://manaten.net/wp-content/uploads/2022/06/tscheck14.png" alt="tscheck" /></p> <p>たとえば、numberの配列にはnumberしかpushできず、添字で取得した値もnumberであることが保証されます</p> <h1 id="CLIでチェックする">CLIでチェックする</h1> <p>ここまででts-checkの便利さはお伝えしてきましたが、実際に利用するときはvscodeではなくコマンドラインで継続的に型チェックしたくなると思います。</p> <p>ここまで読んでピンときた方もいるかも知れませんが、 <code>@ts-check</code> コメントとは、JavaScriptに型付けして型チェックさせるための宣言というよりは、このJavaScriptファイルをTypeScriptファイルと同等に扱う(さらに、JSDocがあればTypeScriptの型宣言のように扱う)機能と言えます。</p> <p>つまり、cliから型チェックしたいという場合は、普通にtscコマンドを利用することで型チェックが可能です。</p> <p><img src="https://manaten.net/wp-content/uploads/2022/06/tscheck15.png" alt="tscheck" /></p> <p>JSファイルも読み込む <code>--allowJs</code> オプションと、チェックだけを行いコンパイルを行わない <code>--noEmit</code> オプションを指定することで、tscによるJSファイルの型チェックができます。</p> <p>また、実は <code>--checkJs</code> オプションを使うことで、 <code>@ts-check</code> を書かずとも、JavaScriptファイルをチェック対象にすることもできます。</p> <p><img src="https://manaten.net/wp-content/uploads/2022/06/tscheck16.png" alt="tscheck" /></p> <p>つまり、TypeScriptコンパイラにはそもそも <code>checkJs</code> でJavaScriptファイルも型チェックする機能が備わっており、 <code>@ts-check</code> は <code>checkJs</code> を指定してなくてもチェック対象に含めるための宣言であるというふうにも考えることができます。</p> <p>TypeScriptの設定に詳しい人は、 これらのフラグをオンにした <code>tsconfig.json</code> をプロジェクトルートに設置するのも良いと思います。先述の <code>strict</code> などは、 <code>tsconfig.json</code> に書くのがおすすめです。</p> <h1 id="ts-checkの使いどころ">@ts-checkの使いどころ</h1> <p>最後に、僕が <code>@ts-check</code> を使うと便利なところ、逆に使いづらいところを紹介していきます。</p> <h2 id="使い所">使い所</h2> <h3 id="レガシープロダクト">レガシープロダクト</h3> <p>レガシーで直ちにTypeScript化しづらい、またTypeScriptのビルドシステムを導入しづらいプロダクトにはピッタリのソリューションだと思います。とりあえずチェック可能なファイルから <code>@ts-check</code> とかくだけで始められますし、型推論や漸進的型付で最低限の型導入ができます。慣れてきたり、より重要なロジックでは型宣言をしたり、strictオプションを使っていくことで緩急をつけて導入していくことができます。</p> <h3 id="トイプロダクトやスクリプティング">トイプロダクトやスクリプティング</h3> <p>いちいちビルドフェーズを作り込みたくない、トイプロダクトや気軽なスクリプティングにも向いていると言えます。 <code>@ts-check</code> したJavaScriptファイルはTypeScriptチェックが可能とはいえ単にJSDocの書かれたJavaScriptファイルなので、<strong>そのままnodeやブラウザで実行可能</strong>です。</p> <h3 id="jsファイルであることが求められている設定ファイルなど">jsファイルであることが求められている設定ファイルなど</h3> <p>既に紹介しましたが、next.jsやwebpackのconfigファイルなど、<strong>jsで書くことが求められている設定ファイル</strong>を型安全に書きたいというケースでも便利です。頑張ってTypeScriptからビルドする仕組みを作り込んでもいいですが、ts-checkはお手軽でおすすめです。</p> <h2 id="使いづらいところ">使いづらいところ</h2> <h3 id="TypeScriptの機能を十全に発揮したいところ">TypeScriptの機能を十全に発揮したいところ</h3> <p>JSDocで型宣言を行う以上、残念ながらTypeScriptのすべての機能を利用することはできません。たとえばジェネリクスで型引数を取る型の宣言はできませんし、関数呼び出し事の型引数の適用もできません。これによりたとえば、 <code>Array.filter</code> で型を絞りたい場合に絞る先の型を明示できず絞れない、なども問題が起こります。</p> <p><img src="https://manaten.net/wp-content/uploads/2022/06/tscheck17.png" alt="tscheck" /></p> <p>上記の例は、本当は <code>number[]</code> になってほしいが型を絞れず、 <code>(string|number)[]</code> になってしまいます。TypeScriptでは型引数を与えることで限定できます。</p> <p><img src="https://manaten.net/wp-content/uploads/2022/06/tscheck18.png" alt="tscheck" /></p> <h3 id="巨大プロジェクト">巨大プロジェクト</h3> <p>上記のような機能制限や、JSDocで記述しなければいけないことによる記述性の低さから、大きなプロジェクトでは無理せずビルドの仕組みを組んで、TypeScriptで開発するのが良いでしょう。あくまで、ビルドシステムが用意しづらい・用意するほどではないものに対しての限定的な選択肢であるべきだと思います。</p> <h1 id="まとめ">まとめ</h1> <p>本エントリではTypeScriptコンパイラでJavaScriptコードの型チェックをすることができる <code>@ts-check</code> コメントについて紹介しました。JSDocを適切に書くことで(書かなくても型推論によってある程度)JavaScriptのコードでTypeScriptとほぼ同等の漸進的型付けプログラミングを行うことができます。</p> <p>本エントリのサンプルコードは <a href="https://github.com/manaten/ts-check-example">https://github.com/manaten/ts-check-example</a> にありますので、興味がある方は触ってみるのもいいかもしれません( <code>npm test</code> で型チェックが走るようになっています)。</p> <p>ちなみに、Microsoftは <a href="https://www.publickey1.jp/blog/22/javascripttypes_as_commentsjavascripttc39.html">このエントリ</a> で紹介されている通り、JavaScript本体に型宣言のための構文の導入を目論んでいるようで、今回紹介したts-checkとJSDocによる型チェックを、JavaScript上の構文という形に昇華させたいのかな?という雰囲気を感じられます。エントリを読む限りは、TypeScriptに近い構文だが、実行時には無視され、外部の型チェッカーで型チェックできるもののようで、ts-checkのJSDocコメントと役割的には同じに見えます。</p> <h1 id="参考リンク">参考リンク</h1> <ul> <li><a href="https://www.typescriptlang.org/docs/handbook/intro-to-js-ts.html">TypeScript: Documentation - JS Projects Utilizing TypeScript</a></li> <li><a href="https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html">TypeScript: Documentation - Type Checking JavaScript Files</a></li> <li><a href="https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html">TypeScript: Documentation - JSDoc Reference</a></li> <li><a href="https://qiita.com/t2y/items/0a604384e18db0944398">What is Gradual Typing: 漸進的型付けとは何か - Qiita</a></li> <li><a href="https://www.publickey1.jp/blog/22/javascripttypes_as_commentsjavascripttc39.html">マイクロソフト、JavaScriptに型宣言を追加しつつトランスパイラ不要の「Types as Comments」をJavaScript仕様策定会議のTC39に提案へ - Publickey</a></li> <li><a href="https://github.com/manaten/ts-check-example">https://github.com/manaten/ts-check-example</a> <ul> <li>本エントリで利用したサンプルコード</li> </ul> </li> <li><a href="https://blog.manaten.net/entry/2021/12/03/030217">TypeScriptの型定義で麻雀の役判定をする 【dwango Advent Calendar 2日目】 - MANA-DOT</a></li> </ul> manaten TypeScriptの型定義で麻雀の役判定をする 【dwango Advent Calendar 2日目】 hatenablog://entry/13574176438038938390 2021-12-03T03:02:17+09:00 2021-12-03T20:08:28+09:00 このエントリは ドワンゴ Advent Calendar 2021 2日目の記事です(夜が明けるまでは2日目!)。 はじめに TypeScriptには Conditional Types や Template Literal Types といったクッソ強力な型機能があります。 これらを用いて、今回は 2p3p4p2m3m4m2s3s4s4s5s6s8s8s のような天鳳牌譜形式の文字列を型引数に渡すと、麻雀の役判定をする型(あくまで型です、関数ではありません)を作ってみようとおもいます。 (ただし時間がなかったため断么九と平和のみです)。 <p>このエントリは <a href="https://qiita.com/advent-calendar/2021/dwango">ドワンゴ Advent Calendar 2021</a> 2日目の記事です(夜が明けるまでは2日目!)。</p> <h1>はじめに</h1> <p>TypeScriptには <a href="https://www.typescriptlang.org/docs/handbook/2/conditional-types.html">Conditional Types</a> や <a href="https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html">Template Literal Types</a> といったクッソ強力な型機能があります。</p> <p>これらを用いて、今回は <code>2p3p4p2m3m4m2s3s4s4s5s6s8s8s</code> のような天鳳牌譜形式の文字列を型引数に渡すと、麻雀の役判定をする型(あくまで型です、関数ではありません)を作ってみようとおもいます。</p> <p>(ただし時間がなかったため断么九と平和のみです)。</p> <p><img src="https://manaten.net/wp-content/uploads/2021/12/ex4.png" alt="例" /></p> <h1>Conditional Types, Template Literal Types って何?</h1> <p>それぞれ具体的にどんなものか、マニュアルの例を用いて示すと、以下のような感じです。</p> <pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synComment">// Conditional Types の例</span> <span class="synStatement">interface</span> Animal <span class="synIdentifier">{</span> live<span class="synStatement">()</span>: <span class="synType">void</span><span class="synStatement">;</span> <span class="synIdentifier">}</span> <span class="synStatement">interface</span> Dog <span class="synStatement">extends</span> Animal <span class="synIdentifier">{</span> woof<span class="synStatement">()</span>: <span class="synType">void</span><span class="synStatement">;</span> <span class="synIdentifier">}</span> <span class="synComment">// type Example1 = number となる</span> <span class="synStatement">type</span> Example1 <span class="synStatement">=</span> Dog <span class="synStatement">extends</span> Animal ? <span class="synType">number</span> : <span class="synType">string</span><span class="synStatement">;</span> <span class="synComment">// type Example2 = string となる</span> <span class="synStatement">type</span> Example2 <span class="synStatement">=</span> <span class="synSpecial">RegExp</span> <span class="synStatement">extends</span> Animal ? <span class="synType">number</span> : <span class="synType">string</span><span class="synStatement">;</span> <span class="synComment">// Template Literal Types の例</span> <span class="synStatement">type</span> World <span class="synStatement">=</span> <span class="synConstant">&quot;world&quot;</span><span class="synStatement">;</span> <span class="synComment">// type Greeting = &quot;hello world&quot; とおなじになる</span> <span class="synStatement">type</span> Greeting <span class="synStatement">=</span> <span class="synConstant">`hello </span><span class="synSpecial">${</span>World<span class="synSpecial">}</span><span class="synConstant">`</span><span class="synStatement">;</span> </pre> <p>それぞれ、「型に応じた型の分岐」「動的な文字列型の生成」を可能にしています。 これだけだと「ふーん」といった感じかもしれませんが、これとGeneticsやinferによる推論、更に再帰的な型適用をあわせて利用することで、 プログラミングに近い表現力で型を定義することが出来てしまいます。</p> <p>その強力さを示す例として、以下の参考サイトでは、JSONのパーサーを作る例や、SQL文字列からSQL構造の型を作り出す事例が紹介されています(今回非常に参考にさせていただいています)。</p> <ul> <li><a href="https://github.com/ghoullier/awesome-template-literal-types">ghoullier/awesome-template-literal-types: Curated list of awesome Template Literal Types examples</a></li> <li><a href="https://blog.andoshin11.me/posts/typescript-sql-interpretor">TypeScriptで世界一型安全な型レベルSQL Interpreterを作っている話 | Studio Andy</a></li> </ul> <p>これらの事例を参考に、今回麻雀の役判定する型を実装してみたいと思います。</p> <h1>実装</h1> <p>今回成果物となるコードです。順を追って説明していきます。</p> <p><a href="https://github.com/manaten/MahjongTypes/blob/main/MahjongTypes.ts">MahjongTypes/MahjongTypes.ts at main · manaten/MahjongTypes</a></p> <h2>牌の型の定義</h2> <pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synStatement">type</span> 数牌の数 <span class="synStatement">=</span> <span class="synConstant">&quot;1&quot;</span> | <span class="synConstant">&quot;2&quot;</span> | <span class="synConstant">&quot;3&quot;</span> | <span class="synConstant">&quot;4&quot;</span> | <span class="synConstant">&quot;5&quot;</span> | <span class="synConstant">&quot;6&quot;</span> | <span class="synConstant">&quot;7&quot;</span> | <span class="synConstant">&quot;8&quot;</span> | <span class="synConstant">&quot;9&quot;</span><span class="synStatement">;</span> <span class="synStatement">type</span> 数牌の色 <span class="synStatement">=</span> <span class="synConstant">&quot;s&quot;</span> | <span class="synConstant">&quot;p&quot;</span> | <span class="synConstant">&quot;m&quot;</span><span class="synStatement">;</span> <span class="synStatement">type</span> 字牌の数 <span class="synStatement">=</span> <span class="synConstant">&quot;1&quot;</span> | <span class="synConstant">&quot;2&quot;</span> | <span class="synConstant">&quot;3&quot;</span> | <span class="synConstant">&quot;4&quot;</span> | <span class="synConstant">&quot;5&quot;</span> | <span class="synConstant">&quot;6&quot;</span> | <span class="synConstant">&quot;7&quot;</span><span class="synStatement">;</span> <span class="synStatement">type</span> 字牌の色 <span class="synStatement">=</span> <span class="synConstant">&quot;z&quot;</span><span class="synStatement">;</span> <span class="synStatement">type</span> 数牌 <span class="synStatement">=</span> <span class="synConstant">`</span><span class="synSpecial">${</span>数牌の数<span class="synSpecial">}${</span>数牌の色<span class="synSpecial">}</span><span class="synConstant">`</span><span class="synStatement">;</span> <span class="synStatement">type</span> 中張牌<span class="synStatement">=</span> <span class="synConstant">`</span><span class="synSpecial">${</span><span class="synConstant">&quot;2&quot;</span> | <span class="synConstant">&quot;3&quot;</span> | <span class="synConstant">&quot;4&quot;</span> | <span class="synConstant">&quot;5&quot;</span> | <span class="synConstant">&quot;6&quot;</span> | <span class="synConstant">&quot;7&quot;</span> | <span class="synConstant">&quot;8&quot;</span><span class="synSpecial">}${</span>数牌の色<span class="synSpecial">}</span><span class="synConstant">`</span><span class="synStatement">;</span> <span class="synStatement">type</span> 老頭牌 <span class="synStatement">=</span><span class="synConstant">`</span><span class="synSpecial">${</span><span class="synConstant">&quot;1&quot;</span> | <span class="synConstant">&quot;9&quot;</span><span class="synSpecial">}${</span>数牌の色<span class="synSpecial">}</span><span class="synConstant">`</span><span class="synStatement">;</span> <span class="synStatement">type</span> 東 <span class="synStatement">=</span> <span class="synConstant">&quot;1z&quot;</span><span class="synStatement">;</span> <span class="synStatement">type</span> 南 <span class="synStatement">=</span> <span class="synConstant">&quot;2z&quot;</span><span class="synStatement">;</span> <span class="synStatement">type</span> 西 <span class="synStatement">=</span> <span class="synConstant">&quot;3z&quot;</span><span class="synStatement">;</span> <span class="synStatement">type</span> 北 <span class="synStatement">=</span> <span class="synConstant">&quot;4z&quot;</span><span class="synStatement">;</span> <span class="synStatement">type</span> 白 <span class="synStatement">=</span> <span class="synConstant">&quot;5z&quot;</span><span class="synStatement">;</span> <span class="synStatement">type</span> 發 <span class="synStatement">=</span> <span class="synConstant">&quot;6z&quot;</span><span class="synStatement">;</span> <span class="synStatement">type</span> 中 <span class="synStatement">=</span> <span class="synConstant">&quot;7z&quot;</span><span class="synStatement">;</span> <span class="synStatement">type</span> 字牌 <span class="synStatement">=</span> 東 | 南 | 西 | 北 | 白 | 發 | 中<span class="synStatement">;</span> <span class="synStatement">type</span> 么九牌 <span class="synStatement">=</span> 老頭牌 | 字牌<span class="synStatement">;</span> <span class="synStatement">type</span> 雀牌 <span class="synStatement">=</span> 数牌 | 字牌<span class="synStatement">;</span> </pre> <p>ここまではTypeScriptの型がわかればそんなに難しくない内容です。 純粋に共用体を使って麻雀牌の型を定義しています。 これはあとで役判定に使う想定です。</p> <p>ちなみに、大量の型の英名をいちいち考えるのがしんどかったので、ほとんど日本語で型名をつけています。2日まで時間なかったし。</p> <h2>結果となる型の定義</h2> <p>冒頭のスクリーンショットで示したような、判定結果を収納する型の定義です。 <code>2p3p4p2m3m4m2s3s4s4s5s6s8s8s</code> のような文字列を最終的にこの型(の配列)に変換することを目指します。</p> <pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synStatement">type</span> Result <span class="synStatement">=</span> <span class="synIdentifier">{</span> 雀頭: <span class="synIdentifier">[</span>雀牌<span class="synStatement">,</span> 雀牌<span class="synIdentifier">]</span><span class="synStatement">,</span> 面子: <span class="synIdentifier">[</span>雀牌<span class="synStatement">,</span> 雀牌<span class="synStatement">,</span> 雀牌<span class="synIdentifier">][]</span><span class="synStatement">,</span> Rest: <span class="synStatement">(</span>雀牌 | <span class="synConstant">&quot;&quot;</span><span class="synStatement">)</span><span class="synIdentifier">[]</span><span class="synStatement">,</span> 役: <span class="synType">string</span><span class="synIdentifier">[]</span><span class="synStatement">,</span> <span class="synIdentifier">}</span> </pre> <p>再帰的にResult型を作っていくので、Restプロパティは、処理中の牌を入れておくために用意しています。</p> <h2>文字列を配列にする</h2> <p>ここからTypeScriptの型の本領が発揮されていきます。 まずは、処理のために <code>2p3p4p2m3m4m2s3s4s4s5s6s8s8s</code> 形式の文字列を雀牌の配列に変換します</p> <pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synStatement">type</span> ToArray<span class="synStatement">&lt;</span>P <span class="synStatement">extends</span> <span class="synType">string</span><span class="synStatement">&gt;</span> <span class="synStatement">=</span> P <span class="synStatement">extends</span> <span class="synConstant">&quot;&quot;</span> ? <span class="synIdentifier">[]</span> : P <span class="synStatement">extends</span> <span class="synConstant">`</span><span class="synSpecial">${</span>infer A<span class="synSpecial">}${</span>infer B<span class="synSpecial">}${</span>infer Rest<span class="synSpecial">}</span><span class="synConstant">`</span> ? <span class="synConstant">`</span><span class="synSpecial">${</span>A<span class="synSpecial">}${</span>B<span class="synSpecial">}</span><span class="synConstant">`</span> <span class="synStatement">extends</span> 雀牌 ? <span class="synIdentifier">[</span><span class="synConstant">`</span><span class="synSpecial">${</span>A<span class="synSpecial">}${</span>B<span class="synSpecial">}</span><span class="synConstant">`</span><span class="synStatement">,</span> ...ToArray<span class="synStatement">&lt;</span>Rest<span class="synStatement">&gt;</span><span class="synIdentifier">]</span> : <span class="synType">never</span> : <span class="synType">never</span><span class="synStatement">;</span> </pre> <p>inferを使うことで、文字列を3パーツに分割します。 先頭2文字が雀牌であれば、先頭2文字を配列要素に入れ、残りを再びToArray型に適用しています。 すべて雀牌であれば、最終的に空文字列となり、雀牌の配列型となります。</p> <p>実際に文字列を適用すると、以下のような結果となります。</p> <p><img src="https://manaten.net/wp-content/uploads/2021/12/ex1.png" alt="例" /></p> <p>きちんと配列になっています。自分はこれが動いたとき感動しました。</p> <h2>雀頭判定</h2> <p>配列になったので、次は配列をResult型に変換する型を書きます。 自分は「まず雀頭だけ埋める」→「面子を1つずつ埋める」というステップを考えました。 雀頭だけ埋めたResultの配列をこのステップでは生成します。</p> <pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synStatement">type</span> 雀頭判定<span class="synStatement">&lt;</span>A <span class="synStatement">extends</span> 雀牌<span class="synStatement">,</span> B <span class="synStatement">extends</span> 雀牌<span class="synStatement">,</span> Rest <span class="synStatement">extends</span> 雀牌<span class="synIdentifier">[]</span><span class="synStatement">&gt;</span> <span class="synStatement">=</span> A <span class="synStatement">extends</span> B ? <span class="synIdentifier">[{</span> 雀頭: <span class="synIdentifier">[</span>A<span class="synStatement">,</span> B<span class="synIdentifier">]</span><span class="synStatement">,</span> 面子: <span class="synIdentifier">[]</span><span class="synStatement">,</span> Rest: Rest<span class="synStatement">,</span> 役: <span class="synIdentifier">[]</span><span class="synStatement">,</span> <span class="synIdentifier">}]</span> : <span class="synIdentifier">[]</span> <span class="synStatement">type</span> 雀頭マッチング<span class="synStatement">&lt;</span>A <span class="synStatement">extends</span> 雀牌<span class="synStatement">,</span> P0 <span class="synStatement">extends</span> 雀牌<span class="synStatement">,</span> P1 <span class="synStatement">extends</span> 雀牌<span class="synStatement">,</span> P2 <span class="synStatement">extends</span> 雀牌<span class="synStatement">,</span> P3 <span class="synStatement">extends</span> 雀牌<span class="synStatement">,</span> P4 <span class="synStatement">extends</span> 雀牌<span class="synStatement">,</span> P5 <span class="synStatement">extends</span> 雀牌<span class="synStatement">,</span> P6 <span class="synStatement">extends</span> 雀牌<span class="synStatement">,</span> P7 <span class="synStatement">extends</span> 雀牌<span class="synStatement">,</span> P8 <span class="synStatement">extends</span> 雀牌<span class="synStatement">,</span> P9 <span class="synStatement">extends</span> 雀牌<span class="synStatement">,</span> P10 <span class="synStatement">extends</span> 雀牌<span class="synStatement">,</span> P11 <span class="synStatement">extends</span> 雀牌<span class="synStatement">,</span> P12 <span class="synStatement">extends</span> 雀牌<span class="synStatement">&gt;</span> <span class="synStatement">=</span> <span class="synIdentifier">[</span> ...雀頭判定<span class="synStatement">&lt;</span>A<span class="synStatement">,</span> P0<span class="synStatement">,</span> <span class="synIdentifier">[</span>P1<span class="synStatement">,</span> P2<span class="synStatement">,</span> P3<span class="synStatement">,</span> P4<span class="synStatement">,</span> P5<span class="synStatement">,</span> P6<span class="synStatement">,</span> P7<span class="synStatement">,</span> P8<span class="synStatement">,</span> P9<span class="synStatement">,</span> P10<span class="synStatement">,</span> P11<span class="synStatement">,</span> P12<span class="synIdentifier">]</span><span class="synStatement">&gt;,</span> ...雀頭判定<span class="synStatement">&lt;</span>A<span class="synStatement">,</span> P1<span class="synStatement">,</span> <span class="synIdentifier">[</span>P0<span class="synStatement">,</span> P2<span class="synStatement">,</span> P3<span class="synStatement">,</span> P4<span class="synStatement">,</span> P5<span class="synStatement">,</span> P6<span class="synStatement">,</span> P7<span class="synStatement">,</span> P8<span class="synStatement">,</span> P9<span class="synStatement">,</span> P10<span class="synStatement">,</span> P11<span class="synStatement">,</span> P12<span class="synIdentifier">]</span><span class="synStatement">&gt;,</span> ...雀頭判定<span class="synStatement">&lt;</span>A<span class="synStatement">,</span> P2<span class="synStatement">,</span> <span class="synIdentifier">[</span>P0<span class="synStatement">,</span> P1<span class="synStatement">,</span> P3<span class="synStatement">,</span> P4<span class="synStatement">,</span> P5<span class="synStatement">,</span> P6<span class="synStatement">,</span> P7<span class="synStatement">,</span> P8<span class="synStatement">,</span> P9<span class="synStatement">,</span> P10<span class="synStatement">,</span> P11<span class="synStatement">,</span> P12<span class="synIdentifier">]</span><span class="synStatement">&gt;,</span> ...雀頭判定<span class="synStatement">&lt;</span>A<span class="synStatement">,</span> P3<span class="synStatement">,</span> <span class="synIdentifier">[</span>P0<span class="synStatement">,</span> P1<span class="synStatement">,</span> P2<span class="synStatement">,</span> P4<span class="synStatement">,</span> P5<span class="synStatement">,</span> P6<span class="synStatement">,</span> P7<span class="synStatement">,</span> P8<span class="synStatement">,</span> P9<span class="synStatement">,</span> P10<span class="synStatement">,</span> P11<span class="synStatement">,</span> P12<span class="synIdentifier">]</span><span class="synStatement">&gt;,</span> ...雀頭判定<span class="synStatement">&lt;</span>A<span class="synStatement">,</span> P4<span class="synStatement">,</span> <span class="synIdentifier">[</span>P0<span class="synStatement">,</span> P1<span class="synStatement">,</span> P2<span class="synStatement">,</span> P3<span class="synStatement">,</span> P5<span class="synStatement">,</span> P6<span class="synStatement">,</span> P7<span class="synStatement">,</span> P8<span class="synStatement">,</span> P9<span class="synStatement">,</span> P10<span class="synStatement">,</span> P11<span class="synStatement">,</span> P12<span class="synIdentifier">]</span><span class="synStatement">&gt;,</span> ...雀頭判定<span class="synStatement">&lt;</span>A<span class="synStatement">,</span> P5<span class="synStatement">,</span> <span class="synIdentifier">[</span>P0<span class="synStatement">,</span> P1<span class="synStatement">,</span> P2<span class="synStatement">,</span> P3<span class="synStatement">,</span> P4<span class="synStatement">,</span> P6<span class="synStatement">,</span> P7<span class="synStatement">,</span> P8<span class="synStatement">,</span> P9<span class="synStatement">,</span> P10<span class="synStatement">,</span> P11<span class="synStatement">,</span> P12<span class="synIdentifier">]</span><span class="synStatement">&gt;,</span> ...雀頭判定<span class="synStatement">&lt;</span>A<span class="synStatement">,</span> P6<span class="synStatement">,</span> <span class="synIdentifier">[</span>P0<span class="synStatement">,</span> P1<span class="synStatement">,</span> P2<span class="synStatement">,</span> P3<span class="synStatement">,</span> P4<span class="synStatement">,</span> P5<span class="synStatement">,</span> P7<span class="synStatement">,</span> P8<span class="synStatement">,</span> P9<span class="synStatement">,</span> P10<span class="synStatement">,</span> P11<span class="synStatement">,</span> P12<span class="synIdentifier">]</span><span class="synStatement">&gt;,</span> ...雀頭判定<span class="synStatement">&lt;</span>A<span class="synStatement">,</span> P7<span class="synStatement">,</span> <span class="synIdentifier">[</span>P0<span class="synStatement">,</span> P1<span class="synStatement">,</span> P2<span class="synStatement">,</span> P3<span class="synStatement">,</span> P4<span class="synStatement">,</span> P5<span class="synStatement">,</span> P6<span class="synStatement">,</span> P8<span class="synStatement">,</span> P9<span class="synStatement">,</span> P10<span class="synStatement">,</span> P11<span class="synStatement">,</span> P12<span class="synIdentifier">]</span><span class="synStatement">&gt;,</span> ...雀頭判定<span class="synStatement">&lt;</span>A<span class="synStatement">,</span> P8<span class="synStatement">,</span> <span class="synIdentifier">[</span>P0<span class="synStatement">,</span> P1<span class="synStatement">,</span> P2<span class="synStatement">,</span> P3<span class="synStatement">,</span> P4<span class="synStatement">,</span> P5<span class="synStatement">,</span> P6<span class="synStatement">,</span> P7<span class="synStatement">,</span> P9<span class="synStatement">,</span> P10<span class="synStatement">,</span> P11<span class="synStatement">,</span> P12<span class="synIdentifier">]</span><span class="synStatement">&gt;,</span> ...雀頭判定<span class="synStatement">&lt;</span>A<span class="synStatement">,</span> P9<span class="synStatement">,</span> <span class="synIdentifier">[</span>P0<span class="synStatement">,</span> P1<span class="synStatement">,</span> P2<span class="synStatement">,</span> P3<span class="synStatement">,</span> P4<span class="synStatement">,</span> P5<span class="synStatement">,</span> P6<span class="synStatement">,</span> P7<span class="synStatement">,</span> P8<span class="synStatement">,</span> P10<span class="synStatement">,</span> P11<span class="synStatement">,</span> P12<span class="synIdentifier">]</span><span class="synStatement">&gt;,</span> ...雀頭判定<span class="synStatement">&lt;</span>A<span class="synStatement">,</span> P10<span class="synStatement">,</span> <span class="synIdentifier">[</span>P0<span class="synStatement">,</span> P1<span class="synStatement">,</span> P2<span class="synStatement">,</span> P3<span class="synStatement">,</span> P4<span class="synStatement">,</span> P5<span class="synStatement">,</span> P6<span class="synStatement">,</span> P7<span class="synStatement">,</span> P8<span class="synStatement">,</span> P9<span class="synStatement">,</span> P11<span class="synStatement">,</span> P12<span class="synIdentifier">]</span><span class="synStatement">&gt;,</span> ...雀頭判定<span class="synStatement">&lt;</span>A<span class="synStatement">,</span> P11<span class="synStatement">,</span> <span class="synIdentifier">[</span>P0<span class="synStatement">,</span> P1<span class="synStatement">,</span> P2<span class="synStatement">,</span> P3<span class="synStatement">,</span> P4<span class="synStatement">,</span> P5<span class="synStatement">,</span> P6<span class="synStatement">,</span> P7<span class="synStatement">,</span> P8<span class="synStatement">,</span> P9<span class="synStatement">,</span> P10<span class="synStatement">,</span> P12<span class="synIdentifier">]</span><span class="synStatement">&gt;,</span> ...雀頭判定<span class="synStatement">&lt;</span>A<span class="synStatement">,</span> P12<span class="synStatement">,</span> <span class="synIdentifier">[</span>P0<span class="synStatement">,</span> P1<span class="synStatement">,</span> P2<span class="synStatement">,</span> P3<span class="synStatement">,</span> P4<span class="synStatement">,</span> P5<span class="synStatement">,</span> P6<span class="synStatement">,</span> P7<span class="synStatement">,</span> P8<span class="synStatement">,</span> P9<span class="synStatement">,</span> P10<span class="synStatement">,</span> P11<span class="synIdentifier">]</span><span class="synStatement">&gt;,</span> <span class="synIdentifier">]</span><span class="synStatement">;</span> <span class="synStatement">type</span> 雀頭チェック<span class="synStatement">&lt;</span>P <span class="synStatement">extends</span> 雀牌<span class="synIdentifier">[]</span><span class="synStatement">&gt;</span> <span class="synStatement">=</span> Uniq<span class="synStatement">&lt;</span><span class="synIdentifier">[</span> ...雀頭マッチング<span class="synStatement">&lt;</span>P<span class="synIdentifier">[</span><span class="synConstant">0</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">1</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">2</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">3</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">4</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">5</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">6</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">7</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">8</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">9</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">10</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">11</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">12</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">13</span><span class="synIdentifier">]</span><span class="synStatement">&gt;,</span> ...雀頭マッチング<span class="synStatement">&lt;</span>P<span class="synIdentifier">[</span><span class="synConstant">0</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">1</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">2</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">3</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">4</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">5</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">6</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">7</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">8</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">9</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">10</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">11</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">12</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">13</span><span class="synIdentifier">]</span><span class="synStatement">&gt;,</span> ...雀頭マッチング<span class="synStatement">&lt;</span>P<span class="synIdentifier">[</span><span class="synConstant">1</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">0</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">2</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">3</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">4</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">5</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">6</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">7</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">8</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">9</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">10</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">11</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">12</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">13</span><span class="synIdentifier">]</span><span class="synStatement">&gt;,</span> ...雀頭マッチング<span class="synStatement">&lt;</span>P<span class="synIdentifier">[</span><span class="synConstant">2</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">0</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">1</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">3</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">4</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">5</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">6</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">7</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">8</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">9</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">10</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">11</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">12</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">13</span><span class="synIdentifier">]</span><span class="synStatement">&gt;,</span> ...雀頭マッチング<span class="synStatement">&lt;</span>P<span class="synIdentifier">[</span><span class="synConstant">3</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">0</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">1</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">2</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">4</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">5</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">6</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">7</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">8</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">9</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">10</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">11</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">12</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">13</span><span class="synIdentifier">]</span><span class="synStatement">&gt;,</span> ...雀頭マッチング<span class="synStatement">&lt;</span>P<span class="synIdentifier">[</span><span class="synConstant">4</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">0</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">1</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">2</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">3</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">5</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">6</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">7</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">8</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">9</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">10</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">11</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">12</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">13</span><span class="synIdentifier">]</span><span class="synStatement">&gt;,</span> ...雀頭マッチング<span class="synStatement">&lt;</span>P<span class="synIdentifier">[</span><span class="synConstant">5</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">0</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">1</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">2</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">3</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">4</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">6</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">7</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">8</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">9</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">10</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">11</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">12</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">13</span><span class="synIdentifier">]</span><span class="synStatement">&gt;,</span> ...雀頭マッチング<span class="synStatement">&lt;</span>P<span class="synIdentifier">[</span><span class="synConstant">6</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">0</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">1</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">2</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">3</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">4</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">5</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">7</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">8</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">9</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">10</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">11</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">12</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">13</span><span class="synIdentifier">]</span><span class="synStatement">&gt;,</span> ...雀頭マッチング<span class="synStatement">&lt;</span>P<span class="synIdentifier">[</span><span class="synConstant">7</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">0</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">1</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">2</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">3</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">4</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">5</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">6</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">8</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">9</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">10</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">11</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">12</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">13</span><span class="synIdentifier">]</span><span class="synStatement">&gt;,</span> ...雀頭マッチング<span class="synStatement">&lt;</span>P<span class="synIdentifier">[</span><span class="synConstant">8</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">0</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">1</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">2</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">3</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">4</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">5</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">6</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">7</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">9</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">10</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">11</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">12</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">13</span><span class="synIdentifier">]</span><span class="synStatement">&gt;,</span> ...雀頭マッチング<span class="synStatement">&lt;</span>P<span class="synIdentifier">[</span><span class="synConstant">9</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">0</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">1</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">2</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">3</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">4</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">5</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">6</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">7</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">8</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">10</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">11</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">12</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">13</span><span class="synIdentifier">]</span><span class="synStatement">&gt;,</span> ...雀頭マッチング<span class="synStatement">&lt;</span>P<span class="synIdentifier">[</span><span class="synConstant">10</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">0</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">1</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">2</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">3</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">4</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">5</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">6</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">7</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">8</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">9</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">11</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">12</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">13</span><span class="synIdentifier">]</span><span class="synStatement">&gt;,</span> ...雀頭マッチング<span class="synStatement">&lt;</span>P<span class="synIdentifier">[</span><span class="synConstant">11</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">0</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">1</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">2</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">3</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">4</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">5</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">6</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">7</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">8</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">9</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">10</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">12</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">13</span><span class="synIdentifier">]</span><span class="synStatement">&gt;,</span> ...雀頭マッチング<span class="synStatement">&lt;</span>P<span class="synIdentifier">[</span><span class="synConstant">12</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">0</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">1</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">2</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">3</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">4</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">5</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">6</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">7</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">8</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">9</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">10</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">11</span><span class="synIdentifier">]</span><span class="synStatement">,</span> P<span class="synIdentifier">[</span><span class="synConstant">13</span><span class="synIdentifier">]</span><span class="synStatement">&gt;,</span> <span class="synIdentifier">]</span><span class="synStatement">&gt;;</span> <span class="synStatement">type</span> Uniq<span class="synStatement">&lt;</span>A <span class="synStatement">extends</span> <span class="synType">any</span><span class="synIdentifier">[]</span><span class="synStatement">&gt;</span> <span class="synStatement">=</span> A <span class="synStatement">extends</span> <span class="synIdentifier">[</span> infer H<span class="synStatement">,</span> infer I<span class="synStatement">,</span> ...infer T <span class="synIdentifier">]</span> ? H <span class="synStatement">extends</span> I ? <span class="synIdentifier">[</span>H<span class="synStatement">,</span> ...Uniq<span class="synStatement">&lt;</span>T<span class="synStatement">&gt;</span><span class="synIdentifier">]</span> : <span class="synIdentifier">[</span>H<span class="synStatement">,</span> I<span class="synStatement">,</span> ...Uniq<span class="synStatement">&lt;</span>T<span class="synStatement">&gt;</span><span class="synIdentifier">]</span> : A <span class="synStatement">type</span> Ex2 <span class="synStatement">=</span> 雀頭チェック<span class="synStatement">&lt;</span>ToArray<span class="synStatement">&lt;</span><span class="synConstant">&quot;2p3p4p2m3m4m2s3s4s4s5s6s8s8s&quot;</span><span class="synStatement">&gt;&gt;</span> </pre> <p>14個の牌が並ぶ配列型を、力技でマッチングしています。プログラミングならforループを回すところでしょうが、これは型定義なのでそんな高等テクニックは利用できません。 おとなしく要素の位置を変えて14回*14回の型を生成し、spread operatorで単一の配列にマージしていきます(型でspread operatorが使えるのも狂ってますね・・・)。 最後、 <code>雀頭判定</code> では、注目する2つの牌が一致していれば雀頭扱いにしてResult型を返し、一致しなければ雀頭ではない組み合わせとし空配列を返しています。</p> <p>この型を適用すると以下のようになります。</p> <p><img src="https://manaten.net/wp-content/uploads/2021/12/ex2.png" alt="例" /></p> <p>きちんと雀頭っぽい部分だけ抜き出され、残りの雀牌がRestに入っていますね! これが動いたとき僕はニ回目の感動をしました。</p> <p>ちなみに、しれっと Uniq型という配列の連続した重複要素を取り除く型も用意しています。 これを適用しないと、雀頭は同じ牌の組み合わせなので、同じResultが2個以上生成されてしまいます。</p> <h2>面子判定</h2> <p>正直雀頭が判定できたら面子も判定できるような気がしませんか? ほぼ同じ発想で出来てしまいますが、今回型引数となるのはResult型の配列なので一工夫が必要です。</p> <pre class="code lang-typescript" data-lang="typescript" data-unlink> <span class="synStatement">type</span> 数<span class="synStatement">&lt;</span>P<span class="synStatement">&gt;</span> <span class="synStatement">=</span> P <span class="synStatement">extends</span> <span class="synConstant">`</span><span class="synSpecial">${</span>infer N<span class="synSpecial">}${</span>数牌の色<span class="synSpecial">}</span><span class="synConstant">`</span> ? <span class="synConstant">`</span><span class="synSpecial">${</span>N<span class="synSpecial">}</span><span class="synConstant">`</span> : <span class="synType">never</span><span class="synStatement">;</span> <span class="synStatement">type</span> 色<span class="synStatement">&lt;</span>P<span class="synStatement">&gt;</span> <span class="synStatement">=</span> P <span class="synStatement">extends</span> <span class="synConstant">`</span><span class="synSpecial">${</span>数牌の数<span class="synSpecial">}${</span>infer C<span class="synSpecial">}</span><span class="synConstant">`</span> ? <span class="synConstant">`</span><span class="synSpecial">${</span>C<span class="synSpecial">}</span><span class="synConstant">`</span> : <span class="synType">never</span><span class="synStatement">;</span> <span class="synStatement">type</span> 隣の牌<span class="synStatement">&lt;</span>P<span class="synStatement">&gt;</span> <span class="synStatement">=</span> 数<span class="synStatement">&lt;</span>P<span class="synStatement">&gt;</span> <span class="synStatement">extends</span> <span class="synConstant">&quot;1&quot;</span> ? <span class="synConstant">`2</span><span class="synSpecial">${</span>色&lt;P&gt;<span class="synSpecial">}</span><span class="synConstant">`</span> : 数<span class="synStatement">&lt;</span>P<span class="synStatement">&gt;</span> <span class="synStatement">extends</span> <span class="synConstant">&quot;2&quot;</span> ? <span class="synConstant">`3</span><span class="synSpecial">${</span>色&lt;P&gt;<span class="synSpecial">}</span><span class="synConstant">`</span> : 数<span class="synStatement">&lt;</span>P<span class="synStatement">&gt;</span> <span class="synStatement">extends</span> <span class="synConstant">&quot;3&quot;</span> ? <span class="synConstant">`4</span><span class="synSpecial">${</span>色&lt;P&gt;<span class="synSpecial">}</span><span class="synConstant">`</span> : 数<span class="synStatement">&lt;</span>P<span class="synStatement">&gt;</span> <span class="synStatement">extends</span> <span class="synConstant">&quot;4&quot;</span> ? <span class="synConstant">`5</span><span class="synSpecial">${</span>色&lt;P&gt;<span class="synSpecial">}</span><span class="synConstant">`</span> : 数<span class="synStatement">&lt;</span>P<span class="synStatement">&gt;</span> <span class="synStatement">extends</span> <span class="synConstant">&quot;5&quot;</span> ? <span class="synConstant">`6</span><span class="synSpecial">${</span>色&lt;P&gt;<span class="synSpecial">}</span><span class="synConstant">`</span> : 数<span class="synStatement">&lt;</span>P<span class="synStatement">&gt;</span> <span class="synStatement">extends</span> <span class="synConstant">&quot;6&quot;</span> ? <span class="synConstant">`7</span><span class="synSpecial">${</span>色&lt;P&gt;<span class="synSpecial">}</span><span class="synConstant">`</span> : 数<span class="synStatement">&lt;</span>P<span class="synStatement">&gt;</span> <span class="synStatement">extends</span> <span class="synConstant">&quot;7&quot;</span> ? <span class="synConstant">`8</span><span class="synSpecial">${</span>色&lt;P&gt;<span class="synSpecial">}</span><span class="synConstant">`</span> : 数<span class="synStatement">&lt;</span>P<span class="synStatement">&gt;</span> <span class="synStatement">extends</span> <span class="synConstant">&quot;8&quot;</span> ? <span class="synConstant">`9</span><span class="synSpecial">${</span>色&lt;P&gt;<span class="synSpecial">}</span><span class="synConstant">`</span> : <span class="synType">never</span><span class="synStatement">;</span> <span class="synStatement">type</span> 面子判定<span class="synStatement">&lt;</span>R <span class="synStatement">extends</span> Result<span class="synStatement">,</span> A0<span class="synStatement">,</span> A1<span class="synStatement">,</span> A2<span class="synStatement">,</span> Rest<span class="synStatement">&gt;</span> <span class="synStatement">=</span> A2 <span class="synStatement">extends</span> <span class="synType">undefined</span> ? <span class="synIdentifier">[]</span> : <span class="synStatement">(</span> <span class="synComment">// 刻子チェック</span> A0 <span class="synStatement">extends</span> A1 ? A1 <span class="synStatement">extends</span> A2 ? <span class="synIdentifier">[{</span> 雀頭: R<span class="synIdentifier">[</span><span class="synConstant">&quot;雀頭&quot;</span><span class="synIdentifier">]</span><span class="synStatement">,</span> 面子: <span class="synIdentifier">[</span>...R<span class="synIdentifier">[</span><span class="synConstant">&quot;面子&quot;</span><span class="synIdentifier">]</span><span class="synStatement">,</span> <span class="synIdentifier">[</span>A0<span class="synStatement">,</span> A1<span class="synStatement">,</span> A2<span class="synIdentifier">]]</span><span class="synStatement">,</span> Rest: Rest<span class="synStatement">,</span> 役: <span class="synIdentifier">[]</span><span class="synStatement">,</span> <span class="synIdentifier">}]</span> : <span class="synIdentifier">[]</span> <span class="synComment">// 順子チェック</span> : 隣の牌<span class="synStatement">&lt;</span>A0<span class="synStatement">&gt;</span> <span class="synStatement">extends</span> A1 ? 隣の牌<span class="synStatement">&lt;</span>A1<span class="synStatement">&gt;</span> <span class="synStatement">extends</span> A2 ? <span class="synIdentifier">[{</span> 雀頭: R<span class="synIdentifier">[</span><span class="synConstant">&quot;雀頭&quot;</span><span class="synIdentifier">]</span><span class="synStatement">,</span> 面子: <span class="synIdentifier">[</span>...R<span class="synIdentifier">[</span><span class="synConstant">&quot;面子&quot;</span><span class="synIdentifier">]</span><span class="synStatement">,</span> <span class="synIdentifier">[</span>A0<span class="synStatement">,</span> A1<span class="synStatement">,</span> A2<span class="synIdentifier">]]</span><span class="synStatement">,</span> Rest: Rest<span class="synStatement">,</span> 役: <span class="synIdentifier">[]</span><span class="synStatement">,</span> <span class="synIdentifier">}]</span> : <span class="synIdentifier">[]</span> : <span class="synIdentifier">[]</span> <span class="synStatement">)</span> <span class="synStatement">type</span> 面子マッチング<span class="synConstant">2</span><span class="synStatement">&lt;</span>R <span class="synStatement">extends</span> Result<span class="synStatement">,</span> A0<span class="synStatement">,</span> A1<span class="synStatement">,</span> P0<span class="synStatement">,</span> P1<span class="synStatement">,</span> P2<span class="synStatement">,</span> P3<span class="synStatement">,</span> P4<span class="synStatement">,</span> P5<span class="synStatement">,</span> P6<span class="synStatement">,</span> P7<span class="synStatement">,</span> P8<span class="synStatement">,</span> P9<span class="synStatement">&gt;</span> <span class="synStatement">=</span> A1 <span class="synStatement">extends</span> <span class="synType">undefined</span> ? <span class="synIdentifier">[]</span> : <span class="synIdentifier">[</span> ...面子判定<span class="synStatement">&lt;</span>R<span class="synStatement">,</span> A0<span class="synStatement">,</span> A1<span class="synStatement">,</span> P0<span class="synStatement">,</span> <span class="synIdentifier">[</span>P1<span class="synStatement">,</span> P2<span class="synStatement">,</span> P3<span class="synStatement">,</span> P4<span class="synStatement">,</span> P5<span class="synStatement">,</span> P6<span class="synStatement">,</span> P7<span class="synStatement">,</span> P8<span class="synStatement">,</span> P9<span class="synIdentifier">]</span><span class="synStatement">&gt;,</span> ...面子判定<span class="synStatement">&lt;</span>R<span class="synStatement">,</span> A0<span class="synStatement">,</span> A1<span class="synStatement">,</span> P1<span class="synStatement">,</span> <span class="synIdentifier">[</span>P0<span class="synStatement">,</span> P2<span class="synStatement">,</span> P3<span class="synStatement">,</span> P4<span class="synStatement">,</span> P5<span class="synStatement">,</span> P6<span class="synStatement">,</span> P7<span class="synStatement">,</span> P8<span class="synStatement">,</span> P9<span class="synIdentifier">]</span><span class="synStatement">&gt;,</span> ...面子判定<span class="synStatement">&lt;</span>R<span class="synStatement">,</span> A0<span class="synStatement">,</span> A1<span class="synStatement">,</span> P2<span class="synStatement">,</span> <span class="synIdentifier">[</span>P0<span class="synStatement">,</span> P1<span class="synStatement">,</span> P3<span class="synStatement">,</span> P4<span class="synStatement">,</span> P5<span class="synStatement">,</span> P6<span class="synStatement">,</span> P7<span class="synStatement">,</span> P8<span class="synStatement">,</span> P9<span class="synIdentifier">]</span><span class="synStatement">&gt;,</span> <span class="synComment">// ...くりかえし</span> <span class="synIdentifier">]</span><span class="synStatement">;</span> <span class="synStatement">type</span> 面子マッチング<span class="synConstant">1</span><span class="synStatement">&lt;</span>R <span class="synStatement">extends</span> Result<span class="synStatement">,</span> A0<span class="synStatement">,</span> P0<span class="synStatement">,</span> P1<span class="synStatement">,</span> P2<span class="synStatement">,</span> P3<span class="synStatement">,</span> P4<span class="synStatement">,</span> P5<span class="synStatement">,</span> P6<span class="synStatement">,</span> P7<span class="synStatement">,</span> P8<span class="synStatement">,</span> P9<span class="synStatement">,</span> P10<span class="synStatement">&gt;</span> <span class="synStatement">=</span> A0 <span class="synStatement">extends</span> <span class="synType">undefined</span> ? <span class="synIdentifier">[]</span> : <span class="synIdentifier">[</span> ...面子マッチング<span class="synConstant">2</span><span class="synStatement">&lt;</span>R<span class="synStatement">,</span> A0<span class="synStatement">,</span> P0<span class="synStatement">,</span> P1<span class="synStatement">,</span> P2<span class="synStatement">,</span> P3<span class="synStatement">,</span> P4<span class="synStatement">,</span> P5<span class="synStatement">,</span> P6<span class="synStatement">,</span> P7<span class="synStatement">,</span> P8<span class="synStatement">,</span> P9<span class="synStatement">,</span> P10<span class="synStatement">&gt;,</span> ...面子マッチング<span class="synConstant">2</span><span class="synStatement">&lt;</span>R<span class="synStatement">,</span> A0<span class="synStatement">,</span> P1<span class="synStatement">,</span> P0<span class="synStatement">,</span> P2<span class="synStatement">,</span> P3<span class="synStatement">,</span> P4<span class="synStatement">,</span> P5<span class="synStatement">,</span> P6<span class="synStatement">,</span> P7<span class="synStatement">,</span> P8<span class="synStatement">,</span> P9<span class="synStatement">,</span> P10<span class="synStatement">&gt;,</span> ...面子マッチング<span class="synConstant">2</span><span class="synStatement">&lt;</span>R<span class="synStatement">,</span> A0<span class="synStatement">,</span> P2<span class="synStatement">,</span> P0<span class="synStatement">,</span> P1<span class="synStatement">,</span> P3<span class="synStatement">,</span> P4<span class="synStatement">,</span> P5<span class="synStatement">,</span> P6<span class="synStatement">,</span> P7<span class="synStatement">,</span> P8<span class="synStatement">,</span> P9<span class="synStatement">,</span> P10<span class="synStatement">&gt;,</span> <span class="synComment">// ...くりかえし</span> <span class="synIdentifier">]</span><span class="synStatement">;</span> <span class="synStatement">type</span> 面子チェック<span class="synStatement">&lt;</span>RS <span class="synStatement">extends</span> Result<span class="synIdentifier">[]</span><span class="synStatement">&gt;</span> <span class="synStatement">=</span> RS <span class="synStatement">extends</span> <span class="synIdentifier">[</span> infer R<span class="synStatement">,</span> ...infer T <span class="synIdentifier">]</span> ? R <span class="synStatement">extends</span> Result ? T <span class="synStatement">extends</span> Result<span class="synIdentifier">[]</span> ? Uniq<span class="synStatement">&lt;</span><span class="synIdentifier">[</span> ...面子マッチング<span class="synConstant">1</span><span class="synStatement">&lt;</span>R<span class="synStatement">,</span> R<span class="synIdentifier">[</span><span class="synConstant">&quot;Rest&quot;</span><span class="synIdentifier">][</span><span class="synConstant">0</span><span class="synIdentifier">]</span><span class="synStatement">,</span> R<span class="synIdentifier">[</span><span class="synConstant">&quot;Rest&quot;</span><span class="synIdentifier">][</span><span class="synConstant">1</span><span class="synIdentifier">]</span><span class="synStatement">,</span> R<span class="synIdentifier">[</span><span class="synConstant">&quot;Rest&quot;</span><span class="synIdentifier">][</span><span class="synConstant">2</span><span class="synIdentifier">]</span><span class="synStatement">,</span> R<span class="synIdentifier">[</span><span class="synConstant">&quot;Rest&quot;</span><span class="synIdentifier">][</span><span class="synConstant">3</span><span class="synIdentifier">]</span><span class="synStatement">,</span> R<span class="synIdentifier">[</span><span class="synConstant">&quot;Rest&quot;</span><span class="synIdentifier">][</span><span class="synConstant">4</span><span class="synIdentifier">]</span><span class="synStatement">,</span> R<span class="synIdentifier">[</span><span class="synConstant">&quot;Rest&quot;</span><span class="synIdentifier">][</span><span class="synConstant">5</span><span class="synIdentifier">]</span><span class="synStatement">,</span> R<span class="synIdentifier">[</span><span class="synConstant">&quot;Rest&quot;</span><span class="synIdentifier">][</span><span class="synConstant">6</span><span class="synIdentifier">]</span><span class="synStatement">,</span> R<span class="synIdentifier">[</span><span class="synConstant">&quot;Rest&quot;</span><span class="synIdentifier">][</span><span class="synConstant">7</span><span class="synIdentifier">]</span><span class="synStatement">,</span> R<span class="synIdentifier">[</span><span class="synConstant">&quot;Rest&quot;</span><span class="synIdentifier">][</span><span class="synConstant">8</span><span class="synIdentifier">]</span><span class="synStatement">,</span> R<span class="synIdentifier">[</span><span class="synConstant">&quot;Rest&quot;</span><span class="synIdentifier">][</span><span class="synConstant">9</span><span class="synIdentifier">]</span><span class="synStatement">,</span> R<span class="synIdentifier">[</span><span class="synConstant">&quot;Rest&quot;</span><span class="synIdentifier">][</span><span class="synConstant">10</span><span class="synIdentifier">]</span><span class="synStatement">,</span> R<span class="synIdentifier">[</span><span class="synConstant">&quot;Rest&quot;</span><span class="synIdentifier">][</span><span class="synConstant">11</span><span class="synIdentifier">]</span><span class="synStatement">&gt;,</span> ...面子マッチング<span class="synConstant">1</span><span class="synStatement">&lt;</span>R<span class="synStatement">,</span> R<span class="synIdentifier">[</span><span class="synConstant">&quot;Rest&quot;</span><span class="synIdentifier">][</span><span class="synConstant">1</span><span class="synIdentifier">]</span><span class="synStatement">,</span> R<span class="synIdentifier">[</span><span class="synConstant">&quot;Rest&quot;</span><span class="synIdentifier">][</span><span class="synConstant">0</span><span class="synIdentifier">]</span><span class="synStatement">,</span> R<span class="synIdentifier">[</span><span class="synConstant">&quot;Rest&quot;</span><span class="synIdentifier">][</span><span class="synConstant">2</span><span class="synIdentifier">]</span><span class="synStatement">,</span> R<span class="synIdentifier">[</span><span class="synConstant">&quot;Rest&quot;</span><span class="synIdentifier">][</span><span class="synConstant">3</span><span class="synIdentifier">]</span><span class="synStatement">,</span> R<span class="synIdentifier">[</span><span class="synConstant">&quot;Rest&quot;</span><span class="synIdentifier">][</span><span class="synConstant">4</span><span class="synIdentifier">]</span><span class="synStatement">,</span> R<span class="synIdentifier">[</span><span class="synConstant">&quot;Rest&quot;</span><span class="synIdentifier">][</span><span class="synConstant">5</span><span class="synIdentifier">]</span><span class="synStatement">,</span> R<span class="synIdentifier">[</span><span class="synConstant">&quot;Rest&quot;</span><span class="synIdentifier">][</span><span class="synConstant">6</span><span class="synIdentifier">]</span><span class="synStatement">,</span> R<span class="synIdentifier">[</span><span class="synConstant">&quot;Rest&quot;</span><span class="synIdentifier">][</span><span class="synConstant">7</span><span class="synIdentifier">]</span><span class="synStatement">,</span> R<span class="synIdentifier">[</span><span class="synConstant">&quot;Rest&quot;</span><span class="synIdentifier">][</span><span class="synConstant">8</span><span class="synIdentifier">]</span><span class="synStatement">,</span> R<span class="synIdentifier">[</span><span class="synConstant">&quot;Rest&quot;</span><span class="synIdentifier">][</span><span class="synConstant">9</span><span class="synIdentifier">]</span><span class="synStatement">,</span> R<span class="synIdentifier">[</span><span class="synConstant">&quot;Rest&quot;</span><span class="synIdentifier">][</span><span class="synConstant">10</span><span class="synIdentifier">]</span><span class="synStatement">,</span> R<span class="synIdentifier">[</span><span class="synConstant">&quot;Rest&quot;</span><span class="synIdentifier">][</span><span class="synConstant">11</span><span class="synIdentifier">]</span><span class="synStatement">&gt;,</span> ...面子マッチング<span class="synConstant">1</span><span class="synStatement">&lt;</span>R<span class="synStatement">,</span> R<span class="synIdentifier">[</span><span class="synConstant">&quot;Rest&quot;</span><span class="synIdentifier">][</span><span class="synConstant">2</span><span class="synIdentifier">]</span><span class="synStatement">,</span> R<span class="synIdentifier">[</span><span class="synConstant">&quot;Rest&quot;</span><span class="synIdentifier">][</span><span class="synConstant">0</span><span class="synIdentifier">]</span><span class="synStatement">,</span> R<span class="synIdentifier">[</span><span class="synConstant">&quot;Rest&quot;</span><span class="synIdentifier">][</span><span class="synConstant">1</span><span class="synIdentifier">]</span><span class="synStatement">,</span> R<span class="synIdentifier">[</span><span class="synConstant">&quot;Rest&quot;</span><span class="synIdentifier">][</span><span class="synConstant">3</span><span class="synIdentifier">]</span><span class="synStatement">,</span> R<span class="synIdentifier">[</span><span class="synConstant">&quot;Rest&quot;</span><span class="synIdentifier">][</span><span class="synConstant">4</span><span class="synIdentifier">]</span><span class="synStatement">,</span> R<span class="synIdentifier">[</span><span class="synConstant">&quot;Rest&quot;</span><span class="synIdentifier">][</span><span class="synConstant">5</span><span class="synIdentifier">]</span><span class="synStatement">,</span> R<span class="synIdentifier">[</span><span class="synConstant">&quot;Rest&quot;</span><span class="synIdentifier">][</span><span class="synConstant">6</span><span class="synIdentifier">]</span><span class="synStatement">,</span> R<span class="synIdentifier">[</span><span class="synConstant">&quot;Rest&quot;</span><span class="synIdentifier">][</span><span class="synConstant">7</span><span class="synIdentifier">]</span><span class="synStatement">,</span> R<span class="synIdentifier">[</span><span class="synConstant">&quot;Rest&quot;</span><span class="synIdentifier">][</span><span class="synConstant">8</span><span class="synIdentifier">]</span><span class="synStatement">,</span> R<span class="synIdentifier">[</span><span class="synConstant">&quot;Rest&quot;</span><span class="synIdentifier">][</span><span class="synConstant">9</span><span class="synIdentifier">]</span><span class="synStatement">,</span> R<span class="synIdentifier">[</span><span class="synConstant">&quot;Rest&quot;</span><span class="synIdentifier">][</span><span class="synConstant">10</span><span class="synIdentifier">]</span><span class="synStatement">,</span> R<span class="synIdentifier">[</span><span class="synConstant">&quot;Rest&quot;</span><span class="synIdentifier">][</span><span class="synConstant">11</span><span class="synIdentifier">]</span><span class="synStatement">&gt;,</span> <span class="synComment">// ...くりかえし</span> ...面子チェック<span class="synStatement">&lt;</span>T<span class="synStatement">&gt;</span> <span class="synIdentifier">]</span><span class="synStatement">&gt;</span> : <span class="synIdentifier">[]</span> : <span class="synIdentifier">[]</span> : <span class="synIdentifier">[]</span><span class="synStatement">;</span> </pre> <p>雰囲気は雀頭判定で伝わったと思うので、一部コードを省略しています。 基本的な発想は雀頭判定と一緒で、面子は3つの牌が関わるため3重ループ(力技)を行っています。</p> <p>ポイントは2箇所あります。1つ目は、引数が配列なので、inferを使ってHeadとTailに分割し、再起でResult配列を1要素ずつ面子マッチング1に食わせています。</p> <p>もう一つは、面子判定の順子の判定です。刻子は同じ牌の集まりなので、雀頭判定と同じ発想で判定できます。 順子は123のような、連続する牌の集まりですので、隣の牌が隣の数字であることを判定する必要があります。 このために、 <code>1p</code> のような文字列から <code>1</code> と <code>p</code> を取り出す <code>数&lt;P&gt;</code> <code>色&lt;P&gt;</code> 型と、それらを用いて隣の牌の型を返す <code>隣の牌&lt;P&gt;</code> 型を定義しています。 <code>隣の牌&lt;"1p"&gt;</code> は <code>"2p"</code> になるという寸法です。</p> <p>これらを用いて実装された面子チェック型を4回適用すると次のようになります。</p> <p><img src="https://manaten.net/wp-content/uploads/2021/12/ex3.png" alt="例" /></p> <p>赤線は型適用の回数が爆発してきたことによるVSCodeの悲鳴です( <code>型のインスタンス化は非常に深く、無限である可能性があります。ts(2589)</code> などと言ってきます)。幸い、型のプレビューは出来ているので助かりました。</p> <p>これをみると、しっかり面子に分けられていることがわかります。省略されてますが、他の組み合わせ(例の手では順番が違うだけ)も判定され、すべてのパターンが配列になっています。</p> <p>雀頭と面子の分解ができれば、もう役判定はできそうな気がしてきませんか?</p> <h2>役判定</h2> <p>最後に、役判定です。今回時間がなかったため、<a href="https://ja.wikipedia.org/wiki/%E6%96%AD%E4%B9%88%E4%B9%9D">断么九</a>と<a href="https://ja.wikipedia.org/wiki/%E5%B9%B3%E5%92%8C_(%E9%BA%BB%E9%9B%80">平和</a>)のみの判定となります。</p> <pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synStatement">type</span> 断么九判定<span class="synStatement">&lt;</span>R <span class="synStatement">extends</span> Result<span class="synStatement">&gt;</span> <span class="synStatement">=</span> R <span class="synStatement">extends</span> <span class="synIdentifier">{</span> 雀頭: <span class="synIdentifier">[</span>中張牌<span class="synStatement">,</span> 中張牌<span class="synIdentifier">]</span><span class="synStatement">,</span> 面子: <span class="synIdentifier">[[</span>中張牌<span class="synStatement">,</span> 中張牌<span class="synStatement">,</span> 中張牌<span class="synIdentifier">]</span><span class="synStatement">,</span> <span class="synIdentifier">[</span>中張牌<span class="synStatement">,</span> 中張牌<span class="synStatement">,</span> 中張牌<span class="synIdentifier">]</span><span class="synStatement">,</span> <span class="synIdentifier">[</span>中張牌<span class="synStatement">,</span> 中張牌<span class="synStatement">,</span> 中張牌<span class="synIdentifier">]</span><span class="synStatement">,</span> <span class="synIdentifier">[</span>中張牌<span class="synStatement">,</span> 中張牌<span class="synStatement">,</span> 中張牌<span class="synIdentifier">]]</span><span class="synStatement">,</span> <span class="synIdentifier">}</span> ? <span class="synIdentifier">[</span><span class="synConstant">&quot;断么九&quot;</span><span class="synIdentifier">]</span> : <span class="synIdentifier">[]</span> <span class="synStatement">type</span> 平和判定<span class="synStatement">&lt;</span>R <span class="synStatement">extends</span> Result<span class="synStatement">&gt;</span> <span class="synStatement">=</span> R<span class="synIdentifier">[</span><span class="synConstant">&quot;面子&quot;</span><span class="synIdentifier">][</span><span class="synConstant">0</span><span class="synIdentifier">][</span><span class="synConstant">0</span><span class="synIdentifier">]</span> <span class="synStatement">extends</span> infer A ? R<span class="synIdentifier">[</span><span class="synConstant">&quot;面子&quot;</span><span class="synIdentifier">][</span><span class="synConstant">1</span><span class="synIdentifier">][</span><span class="synConstant">0</span><span class="synIdentifier">]</span> <span class="synStatement">extends</span> infer B ? R<span class="synIdentifier">[</span><span class="synConstant">&quot;面子&quot;</span><span class="synIdentifier">][</span><span class="synConstant">2</span><span class="synIdentifier">][</span><span class="synConstant">0</span><span class="synIdentifier">]</span> <span class="synStatement">extends</span> infer C ? R<span class="synIdentifier">[</span><span class="synConstant">&quot;面子&quot;</span><span class="synIdentifier">][</span><span class="synConstant">3</span><span class="synIdentifier">][</span><span class="synConstant">0</span><span class="synIdentifier">]</span> <span class="synStatement">extends</span> infer D ? R <span class="synStatement">extends</span> <span class="synIdentifier">{</span> 雀頭: <span class="synIdentifier">[</span>雀牌<span class="synStatement">,</span> 雀牌<span class="synIdentifier">]</span><span class="synStatement">,</span> 面子: <span class="synIdentifier">[</span> <span class="synIdentifier">[</span>A<span class="synStatement">,</span> 隣の牌<span class="synStatement">&lt;</span>A<span class="synStatement">&gt;,</span> 隣の牌<span class="synStatement">&lt;</span>隣の牌<span class="synStatement">&lt;</span>A<span class="synStatement">&gt;&gt;</span><span class="synIdentifier">]</span><span class="synStatement">,</span> <span class="synIdentifier">[</span>B<span class="synStatement">,</span> 隣の牌<span class="synStatement">&lt;</span>B<span class="synStatement">&gt;,</span> 隣の牌<span class="synStatement">&lt;</span>隣の牌<span class="synStatement">&lt;</span>B<span class="synStatement">&gt;&gt;</span><span class="synIdentifier">]</span><span class="synStatement">,</span> <span class="synIdentifier">[</span>C<span class="synStatement">,</span> 隣の牌<span class="synStatement">&lt;</span>C<span class="synStatement">&gt;,</span> 隣の牌<span class="synStatement">&lt;</span>隣の牌<span class="synStatement">&lt;</span>C<span class="synStatement">&gt;&gt;</span><span class="synIdentifier">]</span><span class="synStatement">,</span> <span class="synIdentifier">[</span>D<span class="synStatement">,</span> 隣の牌<span class="synStatement">&lt;</span>D<span class="synStatement">&gt;,</span> 隣の牌<span class="synStatement">&lt;</span>隣の牌<span class="synStatement">&lt;</span>D<span class="synStatement">&gt;&gt;</span><span class="synIdentifier">]</span><span class="synStatement">,</span> <span class="synIdentifier">]</span><span class="synStatement">,</span> <span class="synIdentifier">}</span> ? <span class="synIdentifier">[</span><span class="synConstant">&quot;平和&quot;</span><span class="synIdentifier">]</span> : <span class="synIdentifier">[]</span> : <span class="synIdentifier">[]</span> : <span class="synIdentifier">[]</span> : <span class="synIdentifier">[]</span> : <span class="synIdentifier">[]</span> <span class="synStatement">type</span> 役チェック<span class="synStatement">&lt;</span>RS <span class="synStatement">extends</span> Result<span class="synIdentifier">[]</span><span class="synStatement">&gt;</span> <span class="synStatement">=</span> RS <span class="synStatement">extends</span> <span class="synIdentifier">[</span> infer R<span class="synStatement">,</span> ...infer T <span class="synIdentifier">]</span> ? R <span class="synStatement">extends</span> Result ? T <span class="synStatement">extends</span> Result<span class="synIdentifier">[]</span> ? Uniq<span class="synStatement">&lt;</span><span class="synIdentifier">[</span> <span class="synIdentifier">{</span> 雀頭: R<span class="synIdentifier">[</span><span class="synConstant">&quot;雀頭&quot;</span><span class="synIdentifier">]</span><span class="synStatement">,</span> 面子: R<span class="synIdentifier">[</span><span class="synConstant">&quot;面子&quot;</span><span class="synIdentifier">]</span><span class="synStatement">,</span> Rest: <span class="synIdentifier">[]</span><span class="synStatement">,</span> 役: <span class="synIdentifier">[</span> ...断么九判定<span class="synStatement">&lt;</span>R<span class="synStatement">&gt;,</span> ...平和判定<span class="synStatement">&lt;</span>R<span class="synStatement">&gt;,</span> <span class="synIdentifier">]</span><span class="synStatement">,</span> <span class="synIdentifier">}</span><span class="synStatement">,</span> ...役チェック<span class="synStatement">&lt;</span>T<span class="synStatement">&gt;</span> <span class="synIdentifier">]</span><span class="synStatement">&gt;</span> : <span class="synIdentifier">[]</span> : <span class="synIdentifier">[]</span> : <span class="synIdentifier">[]</span><span class="synStatement">;</span> </pre> <p>雀頭・面子の判定と比べてかなり素直になっています。 役チェックではResult配列を面子判定と同じテクニックでループさせています。 ループのそれぞれで、役プロパティに各役の判定結果を突っ込んでいます。</p> <p>役判定の方は、<a href="https://ja.wikipedia.org/wiki/%E6%96%AD%E4%B9%88%E4%B9%9D">断么九</a>の判定がかなり美しくないでしょうか?</p> <pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synStatement">type</span> 断么九判定<span class="synStatement">&lt;</span>R <span class="synStatement">extends</span> Result<span class="synStatement">&gt;</span> <span class="synStatement">=</span> R <span class="synStatement">extends</span> <span class="synIdentifier">{</span> 雀頭: <span class="synIdentifier">[</span>中張牌<span class="synStatement">,</span> 中張牌<span class="synIdentifier">]</span><span class="synStatement">,</span> 面子: <span class="synIdentifier">[[</span>中張牌<span class="synStatement">,</span> 中張牌<span class="synStatement">,</span> 中張牌<span class="synIdentifier">]</span><span class="synStatement">,</span> <span class="synIdentifier">[</span>中張牌<span class="synStatement">,</span> 中張牌<span class="synStatement">,</span> 中張牌<span class="synIdentifier">]</span><span class="synStatement">,</span> <span class="synIdentifier">[</span>中張牌<span class="synStatement">,</span> 中張牌<span class="synStatement">,</span> 中張牌<span class="synIdentifier">]</span><span class="synStatement">,</span> <span class="synIdentifier">[</span>中張牌<span class="synStatement">,</span> 中張牌<span class="synStatement">,</span> 中張牌<span class="synIdentifier">]]</span><span class="synStatement">,</span> <span class="synIdentifier">}</span> ? <span class="synIdentifier">[</span><span class="synConstant">&quot;断么九&quot;</span><span class="synIdentifier">]</span> : <span class="synIdentifier">[]</span> </pre> <p>冒頭で定義した、中張牌の型を用いて、Resultがこの形になっているかを判定するだけです。 ちゃんと雀頭も各メンツも中張牌のみで構成されていれば、Resultはこの型のサブ型になっているはずですので、断么九という文字列が返ります。 これをやりたかったがためにこんな苦行をしてきたといっても過言ではない・・・</p> <p>同じ発想で字一色や緑一色は判定できそうですね。混一色などは複合しない清一色を除くのが難しそうです。</p> <p>対して<a href="https://ja.wikipedia.org/wiki/%E5%B9%B3%E5%92%8C_(%E9%BA%BB%E9%9B%80">平和</a>はやや複雑です。面子がすべて順子であることの判定に、隣の牌型を頑張って利用しています。inferは関数型言語のletのように用いることもできるんですね。</p> <pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synStatement">type</span> 平和判定<span class="synStatement">&lt;</span>R <span class="synStatement">extends</span> Result<span class="synStatement">&gt;</span> <span class="synStatement">=</span> R<span class="synIdentifier">[</span><span class="synConstant">&quot;面子&quot;</span><span class="synIdentifier">][</span><span class="synConstant">0</span><span class="synIdentifier">][</span><span class="synConstant">0</span><span class="synIdentifier">]</span> <span class="synStatement">extends</span> infer A ? R<span class="synIdentifier">[</span><span class="synConstant">&quot;面子&quot;</span><span class="synIdentifier">][</span><span class="synConstant">1</span><span class="synIdentifier">][</span><span class="synConstant">0</span><span class="synIdentifier">]</span> <span class="synStatement">extends</span> infer B ? R<span class="synIdentifier">[</span><span class="synConstant">&quot;面子&quot;</span><span class="synIdentifier">][</span><span class="synConstant">2</span><span class="synIdentifier">][</span><span class="synConstant">0</span><span class="synIdentifier">]</span> <span class="synStatement">extends</span> infer C ? R<span class="synIdentifier">[</span><span class="synConstant">&quot;面子&quot;</span><span class="synIdentifier">][</span><span class="synConstant">3</span><span class="synIdentifier">][</span><span class="synConstant">0</span><span class="synIdentifier">]</span> <span class="synStatement">extends</span> infer D ? R <span class="synStatement">extends</span> <span class="synIdentifier">{</span> 雀頭: <span class="synIdentifier">[</span>雀牌<span class="synStatement">,</span> 雀牌<span class="synIdentifier">]</span><span class="synStatement">,</span> 面子: <span class="synIdentifier">[</span> <span class="synIdentifier">[</span>A<span class="synStatement">,</span> 隣の牌<span class="synStatement">&lt;</span>A<span class="synStatement">&gt;,</span> 隣の牌<span class="synStatement">&lt;</span>隣の牌<span class="synStatement">&lt;</span>A<span class="synStatement">&gt;&gt;</span><span class="synIdentifier">]</span><span class="synStatement">,</span> <span class="synIdentifier">[</span>B<span class="synStatement">,</span> 隣の牌<span class="synStatement">&lt;</span>B<span class="synStatement">&gt;,</span> 隣の牌<span class="synStatement">&lt;</span>隣の牌<span class="synStatement">&lt;</span>B<span class="synStatement">&gt;&gt;</span><span class="synIdentifier">]</span><span class="synStatement">,</span> <span class="synIdentifier">[</span>C<span class="synStatement">,</span> 隣の牌<span class="synStatement">&lt;</span>C<span class="synStatement">&gt;,</span> 隣の牌<span class="synStatement">&lt;</span>隣の牌<span class="synStatement">&lt;</span>C<span class="synStatement">&gt;&gt;</span><span class="synIdentifier">]</span><span class="synStatement">,</span> <span class="synIdentifier">[</span>D<span class="synStatement">,</span> 隣の牌<span class="synStatement">&lt;</span>D<span class="synStatement">&gt;,</span> 隣の牌<span class="synStatement">&lt;</span>隣の牌<span class="synStatement">&lt;</span>D<span class="synStatement">&gt;&gt;</span><span class="synIdentifier">]</span><span class="synStatement">,</span> <span class="synIdentifier">]</span><span class="synStatement">,</span> <span class="synIdentifier">}</span> ? <span class="synIdentifier">[</span><span class="synConstant">&quot;平和&quot;</span><span class="synIdentifier">]</span> : <span class="synIdentifier">[]</span> : <span class="synIdentifier">[]</span> : <span class="synIdentifier">[]</span> : <span class="synIdentifier">[]</span> : <span class="synIdentifier">[]</span> </pre> <h1>実行結果</h1> <p>ここまでで、少なくとも断么九と平和を役判定できる型が完成しました。以下に実行例を示します。</p> <p>冒頭の断么九平和を判定する例です。</p> <p><img src="https://manaten.net/wp-content/uploads/2021/12/ex4.png" alt="例" /></p> <p>面子の一つに1pを混ぜると、平和のみになりました。</p> <p><img src="https://manaten.net/wp-content/uploads/2021/12/ex5.png" alt="例" /></p> <p>順子を一つ刻子にすると、断么九のみになりました。</p> <p><img src="https://manaten.net/wp-content/uploads/2021/12/ex6.png" alt="例" /></p> <p>面子を一つ崩すと、アガリ無しとなり空配列になります。</p> <p><img src="https://manaten.net/wp-content/uploads/2021/12/ex7.png" alt="例" /></p> <h1>さいごに</h1> <p>TypeScriptのTemplate Literal Typeの紹介エントリを読んだとき、真っ先に麻雀役の判定を思いついたのですが、なかなか時間が取れず実行できませんでした。 今回Advent Calendarにかこつけて部分的ですが実現することが出来、満足しています。</p> <p>現状だと面子の並び順で同じ手が重複した結果が生成されたり、そもそも手によっては巨大で結果をVSCodeくんが表示されなかったりします(おそらく今のままだと殆どの清一色は判定できません)。</p> <p>TypeScriptの型でこれだけ遊んだのは初めてなので、型の特性をわかっておらず、記述的にも実行効率的にも、より効率の良い書き方はきっとあるのだろうと思います。 また、アルゴリズムとしてもリー牌(ソート)すれば全探索はする必要ないはずですので、改善可能に思えます。</p> <p>特に、現状版は刻子が多い手だと、雀頭候補が大量に判定されるため、結果が出なくなったりするようです。 いずれ改善したい・・・という気持ちだけあるが、所詮一発ネタなので2021年とともに忘れ去られる運命かも知れません。</p> <p>何にせよ、TypeScriptの型の自由度、可能性がこのエントリを通してみなさんに伝われば幸いです。</p> <h1>参考文献</h1> <ul> <li><a href="https://www.typescriptlang.org/docs/handbook/2/conditional-types.html">Conditional Types</a></li> <li><a href="https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html">Template Literal Types</a></li> <li><a href="https://github.com/ghoullier/awesome-template-literal-types">ghoullier/awesome-template-literal-types: Curated list of awesome Template Literal Types examples</a></li> <li><a href="https://blog.andoshin11.me/posts/typescript-sql-interpretor">TypeScriptで世界一型安全な型レベルSQL Interpreterを作っている話 | Studio Andy</a></li> </ul> manaten fitbitの文字盤を作ってみた話と、fitbitを買ってみてよかったこと hatenablog://entry/10257846132608922798 2018-08-09T11:00:00+09:00 2018-08-11T17:08:58+09:00 fitbit versa を購入したので、お試しでドット絵で文字盤を作ってみました。 出来が中途半端なのでfitbitアプリとしてリリースはしていませんが、githubにソースコードは公開してあります。 リポジトリ: manaten/fitbit-pixelart-knight-clock このエントリでは、上記文字盤を作る際のメモと、fitbitを購入してよかったことについて書きます。 <p><img src="https://manaten.net/wp-content/uploads/2018/08/fitbit1.jpg" alt="fitbit" /></p> <p><a href="https://www.fitbit.com/shop/versa">fitbit versa</a> を購入したので、お試しでドット絵で文字盤を作ってみました。 出来が中途半端なのでfitbitアプリとしてリリースはしていませんが、githubにソースコードは公開してあります。</p> <p>リポジトリ: <a href="https://github.com/manaten/fitbit-pixelart-knight-clock">manaten/fitbit-pixelart-knight-clock</a></p> <p><img src="https://manaten.net/wp-content/uploads/2018/08/fitbit2.gif" alt="fitbit" /></p> <p>このエントリでは、上記文字盤を作る際のメモと、fitbitを購入してよかったことについて書きます。</p> <h1>制作時のメモ</h1> <p>以下箇条書きです。</p> <h2>Fitbit Studioについて</h2> <ul> <li><a href="https://studio.fitbit.com/projects">Fitbit Studio</a> というWeb上のIDEを用いてコーディングから実機テストまで行える。</li> <li>実機テストに関しては同一のfitbitアカウントで認証している端末が 「Developer Bridge」 でサーバー接続状態になっている場合に、IDEのプルダウンから選択して「Run」で動作確認できる <ul> <li>アプリのリリースが面倒なので開発終わってもそのまま文字盤として動かし続けている</li> </ul> </li> <li>ファイルはD&amp;Dでアップロードできる(複数でも可)、メニューからzipで一括ダウンロードもできる <ul> <li>現状ローカルファイルとの同期手段はこれしかなさそう?</li> </ul> </li> <li>ローカルのVSCodeで開発したかったので、空プロジェクトでダウンロードしたあとローカルでgitリポジトリ化した <ul> <li>以後同期はローカル → fitbit studio の一方通行とした</li> <li>テキストの同期はエディタで全選択コピペで同期した</li> <li>テキスト以外は同期は頻繁ではないので、必要に応じてD&amp;Dした</li> <li>fitbit studioの出来は悪いわけではないけど、ローカルのvscodeには及ばないので、cliで同期する手段があれば便利なのにな、と思った</li> </ul> </li> </ul> <h2>Fitbitの文字盤開発について</h2> <ul> <li>動作をjs、見た目をsvg+cssで開発できる <ul> <li>既存のWeb技術と同じ技術スタックなのは学習コストが低くて良い</li> <li>jsはモジュールが使え、新し目の構文(object destructuring とか)も利用できる(正確にどのバージョン相当なのかは調べてない)</li> </ul> </li> <li>svgはsvgの皮を被った別物で、例えばpathが利用できなかったり、利用できる要素も利用できる属性値が微妙に異なっていたりする <ul> <li>cssも同じく微妙にプロパティが違う</li> <li>ちゃんと公式の <a href="https://dev.fitbit.com/build/guides/user-interface/svg/">SVG Guide</a> を読んで、「そういうもの」だと思って書いたほうが良い</li> </ul> </li> </ul> <h2>電池消費について</h2> <ul> <li>Web開発のノリで自由にやると、恐ろしい勢いで電池消費する文字盤が爆誕してしまう <ul> <li>一時間で電池15%暗い消費してしまったりする(デフォルトの文字盤だと一日で15%くらいの消費)</li> </ul> </li> <li>HeartBeat(心拍数)を毎秒更新しない、画面がオフのときはアニメーションを行わない、などの工夫をすると消費量は減る</li> </ul> <h2>スプライトについて</h2> <ul> <li>ドット絵のアニメーション(数字含む)はimageタグのhrefをjsで置き換えることで実現している <ul> <li>本当はcss sprite的なことをしたかったができなそう?</li> <li>先述の電池問題があり、imageタグのhrefを毎秒置き換えるのはパフォーマンスで不利では?と思っていたのだが、今のところは問題なさそう</li> </ul> </li> <li>たくさんの画像を切り出すのが手間だったので、一枚の画像から複数のスプライトを切り出して保存する <a href="https://github.com/manaten/sub-tiles-cropper">manaten/sub-tiles-cropper</a> というスクリプトを書いた <ul> <li><a href="https://github.com/oliver-moran/jimp">oliver-moran/jimp</a> を利用して書いた。今後も画像加工する小さなタスクで便利そうなので利用していきたい</li> </ul> </li> </ul> <h1>fitbitを購入してよかったこと</h1> <p>最初は「心拍数を常時計測して記録したい」というつもりで購入したのですが、使ってみると購入前にはわからなかった意外と便利なところが見えてきて、手放せなくなっています。 ちなみにfitbitはレビューを見ると、運動される方が消費カロリーなどのトラッキング目的で利用するケースが多いようですが、僕は運動をしない人です。</p> <h2>やっぱり腕時計は便利</h2> <p>「スマートフォンがあるから腕時計はいらない」という人は一定数いると思っていますし僕自身もそうでした。 ですが腕時計をしてみると「スマートフォンをポケットから取り出し、画面を表示」よりも「左腕を見るだけ」のほうが楽であると気づきます。 もちろんそれだけだと「時刻はスマートフォンで確認できるのにわざわざ腕時計を購入して装着する」動機としては弱いのですが、後述する他のメリットもあります。</p> <h2>腕に来る通知が便利</h2> <p>bluetoothで連携しているスマートフォンの通知をfitbitでも受け取り閲覧することができます。 スマートフォンだと気づかなかったり、確認の手間も少ないのがメリットです(特に両手がふさがっているときなど)。</p> <h2>アラームが便利</h2> <p>朝起きるときと、うっかり働きすぎないように帰る時間にアラームをかけています。 アラームと言ってもfitbit本体が振動するだけなのですが、通知と同じくスマホの振動よりも感知しやすく、 音も鳴らさないため朝や職場で便利です。</p> <p>ちなみに、職場で帰るタイマーは今までslack botに行わせていたのですがそのとおりに帰れた試しはなく、 fitbitでアラームするようにしたらちゃんとその時間に帰れるようになったという小話があります。</p> <h2>タイマーが便利</h2> <p>主に料理で時間を測るのに使っています。 これも一見スマートフォンでもいいのですが、料理中にスマホに手を伸ばしてタイマーを設定するよりコストが低いというのが大きいです。 スマートフォンほど色々なアプリが入っていないため(スペック都合、表示都合でもある)、タイマーアプリの起動がしやすいというのもあるかもしれません。</p> <h2>睡眠管理が便利</h2> <p>今までの紹介ははどっちかというと「スマートフォンでもできるけど、身についてるとより便利」な機能でしたが、これはfitbitならではの機能です(睡眠管理アプリはスマホアプリでもあるにはあるが)。</p> <p>fitbitは睡眠時間を計測してスマートフォンで週単位での睡眠時間グラフや睡眠の内訳(レム睡眠など)を表示してくれます。 ただ計測して表示してくれるだけなのですが、「今週は睡眠時間が短めだから今日こそ早く寝よう」みたいな心理が働くため、夜更かしの防止に役立ってくれています。</p> <h2>まとめ</h2> <p>こうして書き下してみると、「スマホでよくね?」という機能が多いのですが、というか僕自身も買っては見たものの「あっ、これスマホでよくね?ってなって数日で飽きるかも」と思っていたのですが、意外と身につけていると便利なことが多い印象です。 スマートフォンが何でもできる汎用型デバイスであるため、スマートウォッチのような特化型デバイスのほうが特化したシチュエーションで使いやすいのは当然の話なのかもしれません。</p> <iframe style="width:120px;height:240px;" marginwidth="0" marginheight="0" scrolling="no" frameborder="0" src="//rcm-fe.amazon-adsystem.com/e/cm?lt1=_blank&bc1=000000&IS2=1&bg1=FFFFFF&fc1=000000&lc1=0000FF&t=manaten-22&o=9&p=8&l=as4&m=amazon&f=ifr&ref=as_ss_li_til&asins=B07D36PY24&linkId=3a43d3bd8975b835e63e16ebd6f973f6"></iframe> <h1>参照</h1> <ul> <li><a href="https://studio.fitbit.com/projects">Fitbit Studio</a></li> <li><a href="https://dev.fitbit.com/build/guides/user-interface/svg/">SVG Guide</a></li> <li><a href="https://github.com/oliver-moran/jimp">oliver-moran/jimp: An image processing library written entirely in JavaScript for Node, with zero external or native dependencies.</a></li> <li><a href="https://github.com/manaten/fitbit-pixelart-knight-clock">manaten/fitbit-pixelart-knight-clock: A fitbit clockface of pixelart knight.</a></li> <li><a href="https://github.com/manaten/sub-tiles-cropper">manaten/sub-tiles-cropper: An utility for crop sub-tiles from images.</a></li> </ul> manaten シャニマスはPWA時代のGoogleMapなのかもしれないという話 hatenablog://entry/17391345971638468013 2018-04-26T12:00:00+09:00 2018-04-26T12:55:44+09:00 ※ この記事はポエムです。 先日、 アイドルマスター シャイニーカラーズ がリリースされました。 僕自身はアイマスはちょっとアニメを見る程度で熱心なファンというわけではないのですが、こちらのゲームはゲーム内容だけではなく別の側面でも注目されていると思います。 それは、ブラウザ上で動くゲームであるというところです。ブラウザゲーム自体は古来から存在するものですが、このゲームが挑戦的なのは、「PWAであるブラウザゲーム」であるところであると思います(PWAであるかどうかは後述します)。 少し触ってみて「これは昨今のソーシャルゲーム的ゲームをPWAとしてアプローチしたとても挑戦的なプロダクトだ」と自分… <p>※ この記事はポエムです。</p> <p>先日、 <a href="https://shinycolors.enza.fun/">アイドルマスター シャイニーカラーズ</a> がリリースされました。 僕自身はアイマスはちょっとアニメを見る程度で熱心なファンというわけではないのですが、こちらのゲームはゲーム内容だけではなく別の側面でも注目されていると思います。 それは、ブラウザ上で動くゲームであるというところです。ブラウザゲーム自体は古来から存在するものですが、このゲームが挑戦的なのは、「<a href="https://developers.google.com/web/progressive-web-apps/">PWA</a>であるブラウザゲーム」であるところであると思います(PWAであるかどうかは後述します)。 少し触ってみて「これは昨今のソーシャルゲーム的ゲームをPWAとしてアプローチしたとても挑戦的なプロダクトだ」と自分の中で盛り上がっているので、なぜ盛り上がっているかを冷めないうちに書いてみようと思います。</p> <h1>シャニマスはPWA?</h1> <h2>そもそもPWAとはなんであったか</h2> <p><a href="https://developers.google.com/web/progressive-web-apps/">PWA</a> という言葉はGoogleが提唱して以降よく耳にするようになりましたが厳格な定義なく、 乱暴に言ってしまうと「<strong>スマートフォンのネイティブアプリの特性を備えたWebアプリ</strong>」「<strong>ネイティブアプリっぽいWebアプリ</strong>」のことであると考えています。 「スマートフォンのネイティブアプリ」として挙げられる代表的な特性として、以下のものがあります。</p> <ul> <li>ネイティブアプリのようなUI・ブラウザ感がない</li> <li>オフライン状態でも起動できる</li> <li>push通知・ホーム画面追加など、OS/端末により親密な動作が可能である</li> </ul> <p>そして、これらを満たすための新しめの技術として、 <a href="https://developers.google.com/web/fundamentals/primers/service-workers/">Service Worker</a> 、 <a href="https://developers.google.com/web/fundamentals/web-app-manifest/">Web App Manifest</a> などがあります。Service Workerを使えば、オフライン起動を可能にしたり、push通知をできたりします。Web App Manifestを適切に記述すれば、スマートフォンのホーム画面にWebアプリを追加し、ロケーションバーなどが省略されたブラウザっぽさのないUIで起動します。</p> <h2>PWAとはプロダクトデザイン</h2> <p>PWAの条件とは大体以上のようですが、必ずしもService WorkerやWeb App Manifestのような技術スタックを利用すればPWAである、とは僕は思いません。「ネイティブアプリっぽいWebアプリ」として最も大事なのは、ユーザー視点で <strong>「ネイティブアプリと変わりない」という体験</strong> であると思います。そしてそのためには上記機能だけではなく、「ネイティブアプリのようなプロダクトデザイン」が必要です。例えば、「ネイティブアプリのようなUI」「ネイティブアプリのような利用形態」などです。</p> <h2>シャニマスはPWAなのか</h2> <p>ではシャニマスはPWAなのかというと、ここは人によって意見が分かれそうですが、僕個人はPWAであると言ってしまって良いと思っています。少なくともPWAの特徴をいくつか満たしています。 UIはスマートフォンのネイティブゲームアプリとほとんどかわらず、またホーム画面に追加することでネイティブアプリのように起動することが可能です。</p> <p><img src="https://manaten.net/wp-content/uploads/2018/04/shinymas_1.jpg" alt="ホーム画面に追加" /></p> <p><img src="https://manaten.net/wp-content/uploads/2018/04/shinymas_3.gif" alt="ネイティブアプリのように起動できる" /></p> <p>いまはまだオフラインで起動することはできませんが、2018/4/26現在Service Worker自体はコードは用意されており (<a href="https://shinycolors.enza.fun/sw.js">https://shinycolors.enza.fun/sw.js</a>) 、実装中のようなコメントも見かけられ、いずれ対応するであろうことが察せます(おそらく、リリース時期を優先したのであろうと思われます)。</p> <p>これらを鑑みて、僕はシャニマスはPWAであると考えます。 今はオフラインでの起動はできないものの、プロダクトデザインはWebアプリというよりネイティブアプリを意識しているように感じられます。UIはネイティブのゲームと比べて見劣らないと感じますし、上記gifのようにホーム画面に追加してしまえば、利用形態もほとんどネイティブアプリです。 ゲーム自体も「Webアプリだからネイティブアプリより劣っている」という点はないように思えます。</p> <h1>シャニマスはPWA界に何をもたらすか</h1> <h2>PWAプロダクト制作の難しさ</h2> <p>PWAという言葉自体は出始めて2年前後経過していると思いますが、現状ザ・PWAと呼べるような代表的なプロダクトは少なくとも日本国内であまり思いつかず、大きな成功例も聞きません。 これについて僕は、PWAプロダクトを制作すること自体が難しく、正解がないからであると考えています。</p> <p>PWA制作が難しい理由は上で述べたように、技術スタックだけでなく「<strong>ネイティブアプリのようなプロダクトデザイン」が必要</strong> だからです。これを達成するためにはネイティブアプリ・Webの両方の最新動向に精通したディレクターが必要であると考えています。その上で、プロダクトに求められているものをPWAとして再定義する必要があり、これがとても難易度の高く、明快な正解がないことです。また特に大きなプロダクトの場合、従来型のネイティブアプリとして実装せずPWAとして実装する決断をするのも大変です。</p> <p>シャニマスは上は述べたとおり、ネイティブアプリと相違ないプロダクトデザインを達成できており、新しい技術スタックもプロダクトデザインの一部として利用できていることがとても画期的であると思います。</p> <h2>シャニマスがPWAでのGoogleMapかもしれない</h2> <p>PWAの実装が難しいのは前節で述べたように、「ネイティブアプリのようなプロダクトデザイン」が難しいからです。しかし、「シャニマス」という一つのPWAゲームの正解が現れたことで、後続のゲームはある意味シャニマスを <strong>お手本</strong> としてプロダクトデザインをすることができます。ビジネスオーナーに対して「次のプロダクトはPWAで作りたいです」と言うのは難しいですが、「次のプロダクトはシャニマスのような、ネイティブアプリのように動作するWebアプリとして作りたいです」とは比較的説明しやすいと思います。</p> <p>これは、ajaxの使い方がまだ確立しておらず、各々が自由に実装していた頃にGoogle Mapが登場し、一気にWebアプリという概念が浸透したのに似ていると感じます。「アイマス」という巨大IPが先駆者となったことも影響力という観点で大きく、シャニマスが成功すれば他にも後続でPWAゲームが増え、各々がより優れた正解を模索しながらPWAという概念が徐々に広がっていくのではないかと期待しています。今までPWAという言葉はあったがネイティブアプリの代替としてはなかなか成立していなかった中で(もちろん最近までiOSでService Workerが利用できなかったのも一つの要因ではあるとは思いますが)、大きな一歩が踏み出されたのではないでしょうか。</p> <h1>その他雑感</h1> <h2>Service Workerへの期待</h2> <p>2018/4/26現在のシャニマスはService Workerを積極的に利用していないようですが、これは今後改善されていくのだろうと思います。 例えば、モバイルでの回線使用量はリソースの多いゲームアプリにおいてネックとなり得るため、ネイティブアプリでよくある「Wifi回線でのファイル一括ダウンロードボタン」のようなものは必要になるだろうし、実装されるのだろうと思います。これができると、より「ネイティブゲームアプリと変わらないPWAゲーム」として完成度が上がるため、個人的に今後のアップデートに期待しているところであります。</p> <h2>プラットフォーム非依存ゲームの可能性</h2> <p>PWAとしてゲームを作るのことの最大の利点は、iTunes Storeのようなネイティブアプリ配信のプラットフォームへの依存をなくせることにあると考えます。これによりストアのルールに縛られず、自由なタイミングでのアプリ更新が可能になったり、他にもプラットフォームに禁止された手法を取ることができたりなども考えられます。 たとえばAppleは以前、ストア上のアプリでのシリアルコードの利用を禁止しました ( <a href="http://ascii.jp/elem/000/001/069/1069030/">参照</a> ) が、PWAゲームであれば再びシリアルコードを利用したインセンティブも増えていくかもしれません。他にも、ストアでは配信できないタイプのアプリ(アダルトゲームなど)の可能性も広がっていくと思います。</p> <h2>Androidのクソダサスプラッシュ問題</h2> <p>Web App Manifestを設定するとAndroidではホーム画面に追加し、ホーム画面からアプリのように起動することを可能にしますが、その際に独自のスプラッシュを表示します。</p> <p><img src="https://manaten.net/wp-content/uploads/2018/04/shinymas_2.jpg" alt="Androidのスプラッシュ" /></p> <p>これが画像のように、とてもダサいのです。特にゲームでは雰囲気を壊す要因になりえるので、シャニマスや後続のゲームが成功することで、今後Googleによって改善されていくのだろうなあと思います。</p> <h1>参考リンク</h1> <ul> <li><a href="https://developers.google.com/web/progressive-web-apps/">Progressive Web Apps  |  Web  |  Google Developers</a></li> <li><a href="https://shinycolors.enza.fun/">アイドルマスター シャイニーカラーズ</a></li> <li><a href="https://developers.google.com/web/fundamentals/web-app-manifest/">ウェブアプリ マニフェスト  |  Web  |  Google Developers</a></li> <li><a href="https://developers.google.com/web/fundamentals/primers/service-workers/">Service Worker の紹介  |  Web  |  Google Developers</a></li> <li><a href="https://www.famitsu.com/news/201802/21152268.html">BXDによるスマホ向けブラウザゲームプラットフォーム“enza”の発表会が開催、『ドラゴンボール』、『アイマス』、『ファミスタ』のプレイリポートもお届け!(1/2) - ファミ通.com</a></li> <li><a href="http://ascii.jp/elem/000/001/069/1069030/">ASCII.jp:シリアルコードに代わるアプリ集客の3つの手法|週刊デジタルマーケティング最前線 by D2Cスマイル</a></li> </ul> manaten husky + lint-stagedでgitのprecommit時にimageminを行い、minifyした画像のみコミットされるようにする hatenablog://entry/8599973812292680874 2017-08-30T12:00:00+09:00 2017-08-30T12:00:30+09:00 かなり昔に こちらの記事 でgitのpre-commitを紹介しました。今回は、pre-commitにまつわる便利なnpm package typicode/husky、 okonet/lint-staged を利用し、画像ファイルのコミット時に imagemin/imagemin を使い自動で画像ファイルのminifyを行う方法を紹介します。 <p><img src="https://manaten.net/wp-content/uploads/2017/08/precommit-imagemin.png" alt="imagemin-precommit" /></p> <p>かなり昔に <a href="http://blog.manaten.net/entry/645">こちらの記事</a> でgitのpre-commitを紹介しました。今回は、pre-commitにまつわる便利なnpm package <a href="https://github.com/typicode/husky">typicode/husky</a>、 <a href="https://github.com/okonet/lint-staged">okonet/lint-staged</a> を利用し、画像ファイルのコミット時に <a href="https://github.com/imagemin/imagemin">imagemin/imagemin</a> を使い自動で画像ファイルのminifyを行う方法を紹介します。</p> <h1>目的</h1> <p><a href="https://github.com/imagemin/imagemin">imagemin/imagemin</a> を利用すると、画像ファイルのminifyを行うことができます。 画像のminifyはいうまでもなくwebで重要であり、可能な限りこういったツールを利用したいです。</p> <p>画像を作成するたびに都度imageminを実行するのは手間ですので、こうった作業はできるだけ自動化したいです。 ビルド時の自動minifyであれば、grunt、gulp、webpackのそれぞれで <a href="https://github.com/gruntjs/grunt-contrib-imagemin">gruntjs/grunt-contrib-imagemin</a> 、<a href="https://github.com/sindresorhus/gulp-imagemin">sindresorhus/gulp-imagemin</a> 、<a href="https://github.com/Klathmon/imagemin-webpack-plugin">Klathmon/imagemin-webpack-plugin</a> といった<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D7%A5%E9%A5%B0%A5%A4%A5%F3">プラグイン</a>があります。</p> <p>ですが、ビルド時のminifyですと、ビルドのたびに毎回すべての画像をminifyすることとなり、画像ファイルの数が増えてきたときに 時間がかかるようになってしまいます。また、オリジナルをコミットする意味のある<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>と違い、画像ファイルを ビルドのたびに処理する必要性も薄いです。<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>に追加する時点でminifyしてしまえば十分に思えます。</p> <p>そこで、今回はgitのpre-commit hookを利用し、コミット時に自動でminifyできるようにしたいと思います。</p> <h1>ツールの紹介</h1> <p>今回利用するツールの紹介です。</p> <h2>imagemin</h2> <ul> <li><a href="https://github.com/imagemin/imagemin">imagemin/imagemin</a></li> </ul> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/png">png</a>、<a class="keyword" href="http://d.hatena.ne.jp/keyword/jpeg">jpeg</a>、gif、<a class="keyword" href="http://d.hatena.ne.jp/keyword/svg">svg</a> の画像をminifyできるツールです。 今回はこのツールをコミット時にコミット対象の画像ファイルに対して実行することを目標にします。</p> <h2>husky</h2> <ul> <li><a href="https://github.com/typicode/husky">typicode/husky</a></li> </ul> <p>gitのpre-commit hookとnpmのpackage.<a class="keyword" href="http://d.hatena.ne.jp/keyword/json">json</a>をうまく組み合わせてくれるツールです。 <code>npm install --save-dev huskey</code> でインストールすると、自動でgitのpre-commit hookを設定してくれ、 package.<a class="keyword" href="http://d.hatena.ne.jp/keyword/json">json</a>に記述した <code>precommit</code> <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>の内容をコミット時に実行してくれるようになります。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synComment">// package.json</span> <span class="synIdentifier">{</span> <span class="synConstant">&quot;scripts&quot;</span>: <span class="synIdentifier">{</span> <span class="synConstant">&quot;precommit&quot;</span>: <span class="synConstant">&quot;ここにpre-commit時に実行したい内容を記述する&quot;</span> <span class="synIdentifier">}</span> <span class="synIdentifier">}</span> </pre> <p>npm install 時に自動でhookを登録してくれるのがなかなか便利で、面倒なhook<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>の記述・配置をさぼることができ、 複数人で作業する場合ではpre-commit hookの利用を半強制することもできます。</p> <h2>lint-staged</h2> <ul> <li><a href="https://github.com/okonet/lint-staged">okonet/lint-staged</a></li> </ul> <p>実行するとgitのステージに上がっているファイルだけに対して指定した<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>を実行できます。 上記のhuskyと組み合わせることで、pre-commit時の処理を簡単に記述できます。 通常は、eslintやstylelintなどのlintをコミット前に行い、lintを通らないファイルがある場合にコミットを中断する目的で利用します。</p> <p>package.<a class="keyword" href="http://d.hatena.ne.jp/keyword/json">json</a>に <code>lint-staged</code> セクションを追加することで、 コミットされるファイルのうち指定した拡張子に対してコマンドを実行してくれます。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synComment">// package.json</span> <span class="synIdentifier">{</span> <span class="synConstant">&quot;scripts&quot;</span>: <span class="synIdentifier">{</span> <span class="synConstant">&quot;precommit&quot;</span>: <span class="synConstant">&quot;lint-staged&quot;</span> <span class="synIdentifier">}</span>, <span class="synConstant">&quot;lint-staged&quot;</span>: <span class="synIdentifier">{</span> <span class="synConstant">&quot;*.{css,less,scss,sss}&quot;</span>: <span class="synIdentifier">[</span> <span class="synConstant">&quot;stylelint --fix&quot;</span>, <span class="synConstant">&quot;git add&quot;</span> <span class="synIdentifier">]</span> <span class="synIdentifier">}</span> <span class="synIdentifier">}</span> </pre> <p>この例だと、<a class="keyword" href="http://d.hatena.ne.jp/keyword/css">css</a>系のファイルに対して <code>stylelint --fix</code> を行い、修正できた場合に <code>git add</code> しそのままコミットします。 この例のようにlintだけでなくfixにも用いることができるため(<a href="https://github.com/okonet/lint-staged#automatically-fix-code-style-with---fix-and-add-to-commit">README</a> でも触れられています)、今回は画像ファイルに対して imagemin を実行するようにします。</p> <h1>設定</h1> <p>lint-stagedを使い、 <code>*.{png,jpeg,jpg,gif}</code> に対してimageminを実行し、それぞれをminifyしたファイルで置き換えるようにします。 imageminには<a href="https://github.com/imagemin/imagemin-cli">imagemin/imagemin-cli</a> という<a class="keyword" href="http://d.hatena.ne.jp/keyword/cli">cli</a>ツールがあり、 このツールを実行するようにしたいところですが、このツールはオプションがあまり豊富ではなく、 残念ながら今回実行したい「複数の画像ファイルを指定してそれぞれを置き換え」といった用途には利用できません。</p> <p>そこで、 imagemin-<a class="keyword" href="http://d.hatena.ne.jp/keyword/cli">cli</a>の内容を参考に、以下のような<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>を記述しました。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synComment">// scripts/imagemin.js</span> <span class="synConstant">'use strict'</span>; <span class="synStatement">const</span> imagemin = require(<span class="synConstant">'imagemin'</span>); <span class="synStatement">const</span> fs = require(<span class="synConstant">'fs'</span>); (async () =&gt; <span class="synIdentifier">{</span> <span class="synStatement">const</span> input = process.argv.filter(arg =&gt; <span class="synConstant">/(png|jpe?g|gif|svg)/</span>.test(arg)); <span class="synStatement">const</span> plugins = <span class="synIdentifier">[</span> <span class="synConstant">'gifsicle'</span>, <span class="synConstant">'jpegtran'</span>, <span class="synConstant">'optipng'</span>, <span class="synConstant">'svgo'</span> <span class="synIdentifier">]</span>.map(x =&gt; require(`imagemin-$<span class="synIdentifier">{</span>x<span class="synIdentifier">}</span>`)()); <span class="synStatement">for</span> (<span class="synStatement">const</span> inputPath of input) <span class="synIdentifier">{</span> <span class="synStatement">const</span> outputData = (await imagemin(<span class="synIdentifier">[</span>inputPath<span class="synIdentifier">]</span>, <span class="synIdentifier">{</span>plugins<span class="synIdentifier">}</span>))<span class="synIdentifier">[</span>0<span class="synIdentifier">]</span>.data; console.log(`$<span class="synIdentifier">{</span>inputPath<span class="synIdentifier">}</span>: $<span class="synIdentifier">{</span>fs.statSync(inputPath).size<span class="synIdentifier">}</span> <span class="synStatement">byte</span> -&gt; $<span class="synIdentifier">{</span>outputData.length<span class="synIdentifier">}</span> <span class="synStatement">byte</span>.`); fs.writeFileSync(inputPath, outputData); <span class="synIdentifier">}</span> <span class="synIdentifier">}</span>)().<span class="synStatement">catch</span>(e =&gt; <span class="synIdentifier">{</span> console.log(e.message, e); process.exit(1); <span class="synIdentifier">}</span>); </pre> <p>lint-staged から引数でコミット対象のファイルのパス(とプログラム実行時についてくる関係ない文字列)が来るので、それぞれに対して imageminを実行し、同じパスに <code>writeFileSync</code> で上書きしています (async functionを利用しているのでnode7.6以降でないと動きませんが、async functionが利用できる環境ではこのような<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>を 簡潔に記述できるため便利です)。 ファイルサイズの変化もわかるように <code>console.log</code> したのですが、lint-stagedが正常実行時の標準出力を捨ててしまうようで 見ることができませんでした(方法を知っている方がいたら教えてください)。</p> <p>この<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>をlint-stagedから呼び出すようにします。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synComment">// package.json</span> <span class="synIdentifier">{</span> <span class="synConstant">&quot;lint-staged&quot;</span>: <span class="synIdentifier">{</span> <span class="synConstant">&quot;*.{png,jpeg,jpg,gif}&quot;</span>: <span class="synIdentifier">[</span> <span class="synConstant">&quot;node ./scripts/imagemin.js&quot;</span>, <span class="synConstant">&quot;git add&quot;</span> <span class="synIdentifier">]</span> <span class="synIdentifier">}</span> <span class="synIdentifier">}</span> </pre> <p>以上で、 <code>git commit</code> 時に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A1%BC%A5%F3%A5%B7%A5%E7%A5%C3%A5%C8">スクリーンショット</a>のように自動でimageminを実行してくれます。</p> <p><img src="https://manaten.net/wp-content/uploads/2017/08/precommit-imagemin-2.png" alt="imagemin-precommit" /></p> <p>コミット前後で画像のサイズが小さくなっていることがわかります(もちろん画像の見た目には変化はありません)。</p> <p>このブログで利用している画像も、ここで紹介した方法でコミット時に最適化するようにしています。 <a href="https://github.com/manaten/manaten.net/blob/1ee1f9cb8d1dabae56f0e3d9fe7cf0cb295535ab/package.json#L108-L110">manaten.net/package.json</a> にて設定の最終系を閲覧できるので、よろしければ参考にしてください。</p> <h1>参考</h1> <ul> <li><a href="https://github.com/imagemin/imagemin">imagemin/imagemin: Tense, nervous, minifying images?</a></li> <li><a href="https://github.com/typicode/husky">typicode/husky: Git hooks made easy</a></li> <li><a href="https://github.com/okonet/lint-staged">okonet/lint-staged: 🚫💩 — Run linters on git staged files</a></li> <li><a href="http://blog.manaten.net/entry/645">gitのpre-commit hookを使って、綺麗なPHPファイルしかコミットできないようにする - MANA-DOT</a> <ul> <li>以前書いたgitのpre-commitに関する記事</li> </ul> </li> </ul> manaten 海外版Surface Laptopで一部の日本語アプリケーションが文字化けする + おまけでSurface Laptopの感想 hatenablog://entry/8599973812292665912 2017-08-28T12:00:00+09:00 2017-08-28T12:00:32+09:00 家電量販店で触っていい感じだったので、Surface Laptopを購入してしまいました(一か月くらい前に)。 英字キーボードが使いたかったこと、日本で未発売のcore i7+メモリ16GBモデルが欲しかったことから、海外版を米Amazonで購入しました。 Amazon.com: Microsoft Surface Laptop (Intel Core i7, 16GB RAM, 512GB) - Platinum: Computers & Accessories 購入自体は発送に時間はかかったものの、きちんと手元に届き、ACアダプタなども問題なく利用できています。 海外版のWindowsを使… <p><img src="https://manaten.net/wp-content/uploads/2017/08/surface-laptop.jpg" alt="surface-laptop" /></p> <p>家電量販店で触っていい感じだったので、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Surface">Surface</a> Laptopを購入してしまいました(一か月くらい前に)。 英字キーボードが使いたかったこと、日本で未発売の<a class="keyword" href="http://d.hatena.ne.jp/keyword/core%20i7">core i7</a>+メモリ16GBモデルが欲しかったことから、海外版を米<a class="keyword" href="http://d.hatena.ne.jp/keyword/Amazon">Amazon</a>で購入しました。</p> <p><a href="https://www.amazon.com/gp/product/B0713X4DKY/ref=oh_aui_detailpage_o00_s00?ie=UTF8&amp;psc=1">Amazon.com: Microsoft Surface Laptop (Intel Core i7, 16GB RAM, 512GB) - Platinum: Computers &amp; Accessories</a></p> <p>購入自体は発送に時間はかかったものの、きちんと手元に届き、ACアダプタなども問題なく利用できています。</p> <p>海外版の<a class="keyword" href="http://d.hatena.ne.jp/keyword/Windows">Windows</a>を使うにあたって一部の日本語アプリケーションの日本語文字が文字化けてしまうという問題があったため、紹介します。</p> <h1>症状</h1> <p>画像のように、一部の日本語アプリケーションでメニューなどの文字が化けてしまいます(画像は<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%C3%A5%C8%B3%A8">ドット絵</a>エディタのEDGE2)。</p> <p><img src="https://manaten.net/wp-content/uploads/2017/08/foreign-windows.jpg" alt="文字化け" /></p> <p>日本語言語パックは届いて起動してすぐに入れ<a class="keyword" href="http://d.hatena.ne.jp/keyword/Windows">Windows</a>のメニューなどは日本語で表示できていたため、最初原因がわかりませんでした。 別のWindows10機では正しく表示できていたのでOSの違いではなさそうだし、Insider preview(主に<a class="keyword" href="http://d.hatena.ne.jp/keyword/bash">bash</a> on <a class="keyword" href="http://d.hatena.ne.jp/keyword/windows">windows</a>のために入れていた)のせいかな? とも思ったりしました。</p> <h1>解決</h1> <p><a href="https://oshiete.goo.ne.jp/qa/6565030.html">海外PCの文字化け -海外在住で現地で購入したウィンドウズ7PCを日- Windows 7 | 教えて!goo</a></p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%B6%B5%A4%A8%A4%C6goo">教えてgoo</a>に答えが書いてありました。</p> <blockquote><p>Control Panel のClock, Language and Region の Regional and Language Options の Aministrativeの non-<a class="keyword" href="http://d.hatena.ne.jp/keyword/Unicode">Unicode</a> programs は Japanese になっていますよね?</p></blockquote> <p><img src="https://manaten.net/wp-content/uploads/2017/08/system-locale.png" alt="システムロケールの変更" /></p> <p>コン<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C8%A5%ED%A1%BC%A5%EB">トロール</a>パネルの「時計、言語、および地域」 → 「地域」 → 「管理」と開いて、「<a class="keyword" href="http://d.hatena.ne.jp/keyword/Unicode">Unicode</a>対応ではないプログラムの言語」の「システム<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%ED%A5%B1%A1%BC%A5%EB">ロケール</a>の変更」で「日本語」 を選ぶことで直りました。</p> <p><img src="https://manaten.net/wp-content/uploads/2017/08/locale-fixed.png" alt="直った" /></p> <p>簡単な話でしたが、海外でPCを購入したのは初めてだったので気づくのに時間がかかりました。</p> <h1>余談 <a class="keyword" href="http://d.hatena.ne.jp/keyword/Surface">Surface</a> Laptopについて</h1> <p>おまけで<a class="keyword" href="http://d.hatena.ne.jp/keyword/Surface">Surface</a> Laptopの感想を箇条書きで書いておきます。</p> <ul> <li>見た目がかっこいい <ul> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/Windows">Windows</a> ラップトップは天板にメーカーロゴがでかでか書いてあったりし、あまりかっこよくないPCが多いので、すごく大事</li> <li>かっこいい = テンションが上がる = 作業効率が上がる なので、大事</li> </ul> </li> <li>キーボードが押しやすい <ul> <li>キーが打ちやすいラップトップを求めていたので、この点はよい</li> </ul> </li> <li>キーボード配列はおおむね良いが、ctrlとfnは逆であってほしかった <ul> <li>ctrl+Sをはじめとして、ctrl組み合わせのショートカットのほうが圧倒的に押すので。</li> <li>fnはオン状態(F1-12が有効な状態)からめったに押すことはない。ボリューム切り替えくらい(それもそこまで必須ではない)</li> </ul> </li> <li>キーボードの矢印キーの上下キーの間に、<a class="keyword" href="http://d.hatena.ne.jp/keyword/macbook%20pro">macbook pro</a>のように掘り込みが欲しかった <ul> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/mac">mac</a>はなんだかんだで考えられてるなあと思う</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/mac">mac</a>は最下段だけ縦幅広くて、それもよい。<a class="keyword" href="http://d.hatena.ne.jp/keyword/Surface">Surface</a> Laptopは他と同じ幅なので、上下キーが半分のサイズで少し押しづらい。</li> </ul> </li> <li>ディスプレイがタッチパネルなのはなんだかんだで便利 <ul> <li>膝上でダラダラ動画見るときに<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BF%A5%C3%A5%C1%A5%D1%A5%C3%A5%C9">タッチパッド</a>でカーソルを操作するよりもずっと素早く操作できる</li> <li>ただキーボードたたいてるときに指が触れちゃって誤操作することもある、慣れれば問題はない</li> </ul> </li> <li>Displayportの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%CD%A5%AF%A5%BF">コネクタ</a>が固い <ul> <li>個体差かもしれない</li> <li>普段はディスプレイにつないでデスクトップスタイル、息抜きしたいときは外して膝上スタイルという使い分けをしたいので、外しづらいのは少しマイナス</li> </ul> </li> <li>USBポートは一つしかないけど、そんなに問題ない <ul> <li>現状普段使いの周辺機器は、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%ED%A5%B8%A5%AF%A1%BC%A5%EB">ロジクール</a>の高機能マウス(USB)と、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Thinkpad">Thinkpad</a>キーボード(<a class="keyword" href="http://d.hatena.ne.jp/keyword/bluetooth">bluetooth</a>)。</li> <li>マウス調子が悪いので、買い替えるなら<a class="keyword" href="http://d.hatena.ne.jp/keyword/bluetooth">bluetooth</a>にする予定。</li> <li>いろいろつなぐ人は困るかも?ハブがあれば問題ない気もする</li> </ul> </li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/Windows">Windows</a> Helloの顔認証ログインはすごく便利 <ul> <li>フタ開いている間に認証が終わってPCを使い始められる</li> </ul> </li> <li>マルチディスプレイでそれぞれの解像度が違う場合は少し苦手 <ul> <li>本体は高解像度ディスプレイだが、外付けで<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EBHD">フルHD</a>程度のディスプレイを利用すると、一部アプリケーション(<a class="keyword" href="http://d.hatena.ne.jp/keyword/IME">IME</a>の変換窓など)のサイズが狂う</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/Surface">Surface</a> Laptopというより、Windows10の問題。<a class="keyword" href="http://d.hatena.ne.jp/keyword/Mac">Mac</a>は解決できてるんだから<a class="keyword" href="http://d.hatena.ne.jp/keyword/Windows">Windows</a>もそのうちよくなるはず</li> </ul> </li> <li>海外<a class="keyword" href="http://d.hatena.ne.jp/keyword/Amazon">Amazon</a>での購入なので、壊れた時が怖い <ul> <li>大事に使うつもりではあるけど、基本つけっぱで使うし壊れるときは壊れるので&hellip;</li> <li>壊れたらネタ記事を書く</li> </ul> </li> </ul> <p>以上です。</p> <h1>参考</h1> <ul> <li><a href="https://oshiete.goo.ne.jp/qa/6565030.html">海外PCの文字化け -海外在住で現地で購入したウィンドウズ7PCを日- Windows 7 | 教えて!goo</a></li> <li><a href="https://www.amazon.com/gp/product/B0713X4DKY/ref=oh_aui_detailpage_o00_s00?ie=UTF8&amp;psc=1">Amazon.com: Microsoft Surface Laptop (Intel Core i7, 16GB RAM, 512GB) - Platinum: Computers &amp; Accessories</a></li> </ul> manaten Nexus6の電池交換をした hatenablog://entry/8599973812292250332 2017-08-26T19:18:21+09:00 2017-08-27T17:37:42+09:00 2年以上使ってるNexus6の様子が最近おかしく、ソシャゲのFate/Grand Orderをプレイしてたり 、Google Mapを起動していたりすると突然電源が落ちてしまうようになりました。 あまりに不便で困っていたのですが、スマホを電源につないでいるときはこの現象は発生せず、 電池の劣化が原因かな?と思ったため、自分で電池の交換をしてみました。 手順については、 こちら の記事および、 こちら の動画の通りにやっただけなので、 実際に購入したものや、交換時に困ったことについてまとめます。 <p>2年以上使ってるNexus6の様子が最近おかしく、ソシャゲの<a class="keyword" href="http://d.hatena.ne.jp/keyword/Fate/Grand%20Order">Fate/Grand Order</a>をプレイしてたり 、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Google">Google</a> Mapを起動していたりすると突然電源が落ちてしまうようになりました。 あまりに不便で困っていたのですが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%DE%A5%DB">スマホ</a>を電源につないでいるときはこの現象は発生せず、 電池の劣化が原因かな?と思ったため、自分で電池の交換をしてみました。</p> <p>手順については、 <a href="http://sato-farm.info/archives/3584">こちら</a> の記事および、 <a href="https://www.youtube.com/watch?v=3jbZDD8-Yqs">こちら</a> の動画の通りにやっただけなので、 実際に購入したものや、交換時に困ったことについてまとめます。</p> <h1>購入したもの</h1> <p>すべて<a class="keyword" href="http://d.hatena.ne.jp/keyword/Amazon">Amazon</a>で購入しました。すべて作業途中に必要になったので別々に購入しています&hellip; 時系列順に紹介します。</p> <h2>[WL JUST]<a class="keyword" href="http://d.hatena.ne.jp/keyword/Google">Google</a> <a class="keyword" href="http://d.hatena.ne.jp/keyword/Nexus">Nexus</a> 6用 交換バッテリー電池 <a class="keyword" href="http://d.hatena.ne.jp/keyword/PSE">PSE</a>認証品 3020mAh/3.8V 交換用工具付き</h2> <iframe style="width:120px;height:240px;" marginwidth="0" marginheight="0" scrolling="no" frameborder="0" src="https://rcm-fe.amazon-adsystem.com/e/cm?ref=qf_sp_asin_til&t=manaten-22&m=amazon&o=9&p=8&l=as1&IS1=1&detail=1&asins=B06ZXRRDWN&linkId=b99ef125f5c636ad0f141827cf76e647&bc1=ffffff&lt1=_top&fc1=333333&lc1=0066c0&bg1=ffffff&f=ifr"></iframe> <p>Nexus6の電池と、電池交換に必要な道具のセット。 はじめはこれ一つ購入すれば他に道具は必要ないだろうと思い込んでいて、これのみ購入しました。 ですが、実際は背面カバーをはがすことすらできずに、追加でいろいろ購入しています。</p> <h2>エーモン 内張りはがし(S)青 プラスチック製ハードタイプ 1425</h2> <iframe style="width:120px;height:240px;" marginwidth="0" marginheight="0" scrolling="no" frameborder="0" src="https://rcm-fe.amazon-adsystem.com/e/cm?ref=qf_sp_asin_til&t=manaten-22&m=amazon&o=9&p=8&l=as1&IS1=1&detail=1&asins=B001VNRTMC&linkId=3a7024bd4ac2ad0a19ba75d0146baf42&bc1=ffffff&lt1=_top&fc1=333333&lc1=0066c0&bg1=ffffff&f=ifr"></iframe> <p>参考にしたブログで背面カバーをはがすのに利用されてたツール。 バッテリーの付属品の工具では背面カバーをはがせなかったため、追加で購入しましたがこれでも太すぎてカバーははがせませんでした(そもそも隙間に入らない)。 ですが、以下のUltimate Case Opening Toolで作った隙間にねじ込んでカバー全体をはがす役割はしてくれたので、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A4%A4%A4%E9%A4%CA%A4%A4%BB%D2">いらない子</a>ではありませんでした。</p> <h2>iSesamo 開腹・分解・修理・補修・ハンドツール【Ultimate Case Opening Tool 】</h2> <iframe style="width:120px;height:240px;" marginwidth="0" marginheight="0" scrolling="no" frameborder="0" src="https://rcm-fe.amazon-adsystem.com/e/cm?ref=qf_sp_asin_til&t=manaten-22&m=amazon&o=9&p=8&l=as1&IS1=1&detail=1&asins=B072NB5FS4&linkId=c673eeb3b7123da22c74a6ea7e06b5d6&bc1=ffffff&lt1=_top&fc1=333333&lc1=0066c0&bg1=ffffff&f=ifr"></iframe> <p>エーモンの内張りはがしでカバーをはがせず、途方に暮れて数日間放置していたのですが、 気になって検索したところ <a href="https://www.youtube.com/watch?v=3jbZDD8-Yqs">こちら</a> の動画を見つけ、購入したものです。 先端が細く、金属製でヘタることもないため、背面カバーの隙間に簡単に潜り込ませることができ、無事はがすことができました。 最初から買っておけばよかった・・・</p> <p>しかし、背面カバーをはがしてもバッテリーの付属ドライバーでねじを開けることができず、以下の道具も買うことになります。</p> <h2>LIHAO 25in1 精密特殊ドライバーセット <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%DE%A5%DB">スマホ</a>修理 プロ用 24ビット+1ホルダ</h2> <iframe style="width:120px;height:240px;" marginwidth="0" marginheight="0" scrolling="no" frameborder="0" src="https://rcm-fe.amazon-adsystem.com/e/cm?ref=qf_sp_asin_til&t=manaten-22&m=amazon&o=9&p=8&l=as1&IS1=1&detail=1&asins=B01E5BTVOU&linkId=c529a8d5f4d707e0b8ca765d07354268&bc1=ffffff&lt1=_top&fc1=333333&lc1=0066c0&bg1=ffffff&f=ifr"></iframe> <p>参考にしたブログで紹介されていたもの。 こういった分解作業ではネジ穴をつぶしてしまわないか心配になるのですが、T4のサイズでぴったりはまり無事分解と分解後のネジ止めできました。</p> <h1>困ったこと・反省</h1> <h2>道具を最初にそろえなかったことで、最終的に1か月以上かかってしまった</h2> <p>最初にバッテリーセットを購入したのが7月16日だったようですが、交換完了したのは8月26日でした。 作業のたびに足りない道具が出てきて追加で購入してたためですが、おとなしくブログを見た時点で必要そうなものをすべて購入しておくべきでした。 バッテリーセットにカバーはがしもドライバーも同梱されていたので油断していました。</p> <h2>Qi のコイル回りのパーツを破壊した</h2> <p>背面カバーの接着が強く、はがす過程でQiのコイル付近の内カバーを割ってしまいました。 また、いつ壊したのかはわかりませんが、Qiのコイルから延びる線も切れてしまいました(はがす過程で力が加わたっためと思われる)。</p> <p>Qiは使っておらず、参考にしたブログでもQiのコイルは外してしまっているようなので問題ないと判断していますが、 もしこのエントリを読んだ後に電池交換に挑戦する方がいる場合、動画などでカバーの構造はあらかじめ把握しておいたほうがいいと思います (とはいえ、動画は見ていたが、どこがどう接着されているかまでは理解してなかったのが敗因であったとは思う)</p> <h1>まとめ</h1> <p>いろいろひどい目にあいましたが、電池交換自体は(おそらく)成功し、Nexus6は無事起動しています。 <a class="keyword" href="http://d.hatena.ne.jp/keyword/FGO">FGO</a>をプレイしてても<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%DE%A5%DB">スマホ</a>が落ちなくなりました。 Qi周りを壊したこともあり、若干不安はありますが、今後使っていく中で不具合があれば追記していこうと思います。</p> <h2>追記</h2> <p>バッテリーに関しては持ちがこんな感じになりました。最初の急な部分が<a class="keyword" href="http://d.hatena.ne.jp/keyword/FGO">FGO</a>プレイ中で、以前はプレイ中に電源が落ちてしまったことを考えると すごく改善しました。</p> <p><img src="https://manaten.net/wp-content/uploads/2017/08/nexus6-battery.png" alt="battery" /></p> <h1>参考</h1> <ul> <li><a href="https://www.youtube.com/watch?v=3jbZDD8-Yqs">Motorola Nexus 6 Battery Replacement Guide - RepairsUniverse - YouTube</a></li> <li><a href="http://sato-farm.info/archives/3584">NEXUS6の電池を自分で交換してみた – 30代 農家を目指す金融マンの日常</a></li> </ul> manaten Node.js8になって util.promisify が利用できるようになったのでメモ hatenablog://entry/10328749687256281716 2017-05-31T14:56:46+09:00 2017-05-31T15:00:12+09:00 先日Node.js8 がリリースされました (参考) 。 追加機能の中に util.promisify というものがあります。これは、すでに bluebird や es6-promisify といったパッケージで提供されていた、 コールバック関数を伴う非同期関数を、Promiseを返す関数化するユーティリティ関数ですが、今回のリリースでNode本体に含まれるようになったようです。 <p>先日Node.js8 がリリースされました (<a href="https://nodejs.org/en/blog/release/v8.0.0/">参考</a>) 。 追加機能の中に <code>util.promisify</code> というものがあります。これは、すでに <a href="http://bluebirdjs.com/docs/api/promise.promisify.html">bluebird</a> や <a href="https://www.npmjs.com/package/es6-promisify">es6-promisify</a> といったパッケージで提供されていた、 コールバック関数を伴う非同期関数を、Promiseを返す関数化するユーティリティ関数ですが、今回のリリースでNode本体に含まれるようになったようです。</p> <h1>何に使うのか</h1> <p>コールバック関数を伴う非同期関数、たとえば <code>fs.readdir</code> は次のように使います。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink>fs.readdir(__dirname, (err, result) =&gt; <span class="synIdentifier">{</span> console.log(result); <span class="synIdentifier">}</span>); </pre> <p>しかし、現状<a class="keyword" href="http://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a>での非同期処理関数は、コール<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%AF%A5%B9%A5%BF%A5%A4%A5%EB">バックスタイル</a>よりもPromiseを返す関数のほうがメジャーであり、 他の関数と合わせて使う上で不便です。また、ネストしたときの見通しもあまり良くないです。</p> <p>そこで、 <code>promisify</code> 関数を使うと <code>fs.readdir</code> をPromiseを返す関数化でき、上記処理は次のように書けます。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">const</span> <span class="synIdentifier">{</span>promisify<span class="synIdentifier">}</span> = require(<span class="synConstant">'util'</span>); promisify(fs.readdir)(__dirname) .then(result =&gt; console.log(result)); </pre> <p>また、最近のNode.jsではasync/awaitがサポートされているため、次のように書くこともできます。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">const</span> result = await promisify(fs.readdir)(__dirname); console.log(result); </pre> <p>npmには便利なパッケージがコール<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%AF%A5%B9%A5%BF%A5%A4%A5%EB">バックスタイル</a>で用意されていることも多く、そういった資産をモダンなPromiseスタイルやasync/awaitスタイルのコードで利用するときに <code>promisify</code> は重宝します。</p> <h1>thisの扱いについて</h1> <p>ところで、普通の非同期関数を <code>promisify</code> する場合は問題ないのですが、オブジェクトのメソッドを <code>promisify</code> する場合は単純には行きません。 メソッドはその実装で <code>this</code> を利用しているため、何も考えずにメソッドを <code>promisify</code> の引数にしてしまうと、 <code>this</code> を参照できなくなってしまうからです。</p> <p>例えば<a class="keyword" href="http://d.hatena.ne.jp/keyword/mysql">mysql</a>のクエリ実行などです。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">const</span> <span class="synIdentifier">{</span>promisify<span class="synIdentifier">}</span> = require(<span class="synConstant">'util'</span>); <span class="synStatement">const</span> mysql = require(<span class="synConstant">'mysql'</span>); <span class="synStatement">const</span> conn = mysql.createConnection(<span class="synIdentifier">{</span>...<span class="synIdentifier">}</span>); conn.connect(); <span class="synStatement">const</span> result = await promisify(conn.query)(<span class="synConstant">'SELECT 1 + 2 AS solution'</span>); console.log(result); </pre> <p>これは一見うまくいきそうですが、以下のようなエラーとなってしまいます。</p> <pre class="code" data-lang="" data-unlink>TypeError: Cannot read property &#39;typeCast&#39; of undefined</pre> <p>これは、 <code>query</code> メソッドが実際に動作するときに、本来なら <code>this</code> 経由で得られる <code>conn</code> への参照が失われたことで、 <code>conn</code> の持っている情報(この場合は <code>typeCast</code> プロパティ)にアクセスできなくなるためです。</p> <h2>bluebird, s6-promisify の場合</h2> <p>このような問題を解決するために、 <a href="http://bluebirdjs.com/docs/api/promise.promisify.html">bluebird</a> や <a href="https://www.npmjs.com/package/es6-promisify">es6-promisify</a> では、以下のような記述ができるようになっています。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">const</span> <span class="synIdentifier">{</span>promisify<span class="synIdentifier">}</span> = require(<span class="synConstant">'bluebird'</span>); <span class="synStatement">const</span> result = await promisify(conn.query, <span class="synIdentifier">{</span>context: conn<span class="synIdentifier">}</span>)(<span class="synConstant">'SELECT 1 + 2 AS solution'</span>); console.log(result); </pre> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">const</span> promisify = require(<span class="synConstant">'es6-promisify'</span>); <span class="synStatement">const</span> result = await promisify(conn.query, conn)(<span class="synConstant">'SELECT 1 + 2 AS solution'</span>); console.log(result); </pre> <p>どちらも、 <code>promisify</code> の第二引数で、何らかの形で <code>this</code> への参照を渡せるようになっています。</p> <h2>util.promisifyではどうするか</h2> <p><a href="https://nodejs.org/api/util.html#util_util_promisify_original">ドキュメント</a>を読んだところ、 <code>this</code> を渡す方法は特に書かれていないようです。 <a href="https://github.com/nodejs/node/blob/v8.0.0/lib/internal/util.js#L204">コード</a>を読んでも、 <code>promisify</code> の引数経由で <code>this</code> を渡す方法は定義されていないようです。 ですがよく見ると、 <code>promisify</code> の生成する関数は、その関数の <code>this</code> を元になった関数にも <code>this</code> として渡す挙動になっているようです(<a href="https://github.com/nodejs/node/blob/v8.0.0/lib/internal/util.js#L229">参考</a>)。 ですので、次のように、 <code>promisify</code> で生成された関数に <code>this</code> を束縛することで正しく動作させることができます。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">const</span> <span class="synIdentifier">{</span>promisify<span class="synIdentifier">}</span> = require(<span class="synConstant">'util'</span>); <span class="synStatement">const</span> result = await promisify(conn.query).bind(conn)(<span class="synConstant">'SELECT 1 + 2 AS solution'</span>); console.log(result); </pre> <p>少し冗長ですが、元となった関数の挙動をそのままにPromise化する、と考えれば自然なのかもしれません。</p> <h2>bind-operator が実装されたら・・・</h2> <p>余談ですが、 <a href="https://github.com/tc39/proposal-bind-operator">proposal-bind-operator</a> が実装されれば、次のように書くことができます。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">const</span> result = await conn::(promisify(conn.query))(<span class="synConstant">'SELECT 1 + 2 AS solution'</span>); </pre> <p>または</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">const</span> result = await promisify(::conn.query)(<span class="synConstant">'SELECT 1 + 2 AS solution'</span>); </pre> <p>随分シンプルに書けるようになるため、実装が待ち遠しいです。</p> <h1>参考</h1> <ul> <li><a href="https://nodejs.org/api/util.html#util_util_promisify_original">Util | Node.js v8.0.0 Documentation</a> - ドキュメント</li> <li><a href="http://2ality.com/2017/05/util-promisify.html">Node.js 8: <code>util.promisify()</code></a> - 解説記事</li> <li><a href="http://abouthiroppy.hatenablog.jp/entry/2017/04/27/110733">Nodeへutil.promisify()の追加 - 技術探し</a> - 解説記事</li> <li><a href="https://github.com/nodejs/node/blob/v8.0.0/lib/internal/util.js#L204">node/util.js at v8.0.0 · nodejs/node</a> - util.promisifyの実装</li> <li><a href="https://www.npmjs.com/package/es6-promisify">es6-promisify</a></li> <li><a href="http://bluebirdjs.com/docs/api/promise.promisify.html">Promise.promisify | bluebird</a></li> <li><a href="https://github.com/tc39/proposal-bind-operator">tc39/proposal-bind-operator: This-Binding Syntax for ECMAScript</a></li> <li><a href="http://blog.manaten.net/entry/hubot-with-async-func">Hubotでasync functionを使う - MANA-DOT</a></li> </ul> manaten Slackのstatusをtaskerを使って自分の居場所によって自動変更する hatenablog://entry/10328749687237678217 2017-04-18T11:00:00+09:00 2017-04-19T16:10:06+09:00 先日、slackで 名前の横に自分のステータスを表示する機能 がリリースされました。 これはDMを送ろうとしたときの入力欄などにも表示されるため、適切に設定すれば「まだ出社してないのにメンション飛ばされた」「有給休暇中なのにDM送られた」みたいな気まずい悲劇を回避するのに役立ちそうです。 ただ、有給休暇などの特別なイベントならともかく、毎日出社時・退社時にslackのstatusをマメに変更するのは少々面倒です。そこで、Androidアプリの Tasker を使い、Android端末の位置情報から自動でslackのstatusを更新できるようにしてみました。 <p><img src="http://manaten.net/wp-content/uploads/2017/04/slack-status-tasker-01.png" alt="slack status" /></p> <p>先日、slackで <a href="https://slackhq.com/set-your-status-in-slack-28a793914b98">名前の横に自分のステータスを表示する機能</a> がリリースされました。 これはDMを送ろうとしたときの入力欄などにも表示されるため、適切に設定すれば「まだ出社してないのにメンション飛ばされた」「有給休暇中なのにDM送られた」みたいな気まずい悲劇を回避するのに役立ちそうです。</p> <p><img src="http://manaten.net/wp-content/uploads/2017/04/slack-status-tasker-02.png" alt="slack status" /></p> <p>ただ、有給休暇などの特別なイベントならともかく、毎日出社時・退社時にslackのstatusをマメに変更するのは少々面倒です。そこで、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Android">Android</a>アプリの <a href="https://play.google.com/store/apps/details?id=net.dinglisch.android.taskerm&amp;hl=en">Tasker</a> を使い、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Android">Android</a>端末の位置情報から自動でslackのstatusを更新できるようにしてみました。</p> <h1>Taskerとは</h1> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%DE%A1%BC%A5%C8%A5%D5%A5%A9%A5%F3">スマートフォン</a>の様々なイベントの発生時に予め指定したタスクを実行してくれる<a class="keyword" href="http://d.hatena.ne.jp/keyword/Android">Android</a>アプリです。 イベントは時間指定やディスプレイのオンオフといったベーシックなものから、位置情報や各種センサーなど様々なものが利用できます。 実行するタスクも、電話をかけるだとか、音量を下げるだとかいろいろなものがあります。</p> <p>今回は、イベントとして「特定の<a class="keyword" href="http://d.hatena.ne.jp/keyword/Wifi">Wifi</a>に繋がったとき」を、タスクとして「httpのgetメソッド」を利用してslackの<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>を叩き、 家の<a class="keyword" href="http://d.hatena.ne.jp/keyword/wifi">wifi</a>に繋がったときにstatusを「在宅中」に、会社の<a class="keyword" href="http://d.hatena.ne.jp/keyword/wifi">wifi</a>に繋がったときに「出社中」に、 どちらかの<a class="keyword" href="http://d.hatena.ne.jp/keyword/wifi">wifi</a>から接続が切れたときに「外出中」に変更するようにしてみました。</p> <h1>Taskerの設定</h1> <p>最終型として以下のような状態を目指します。</p> <p><img src="http://manaten.net/wp-content/uploads/2017/04/slack-status-tasker-03.png" alt="slack status" /></p> <p>自宅<a class="keyword" href="http://d.hatena.ne.jp/keyword/wifi">wifi</a>用のprofileを作り、条件を満たしたときにslackのstatusを自宅に、条件から外れたときに statusを外出中にします。他のprofileも同様です。</p> <h2>タスクの作成</h2> <p>タスクは変えたいslackのstatusの個数分それぞれ作ります。tasksのビューで+ボタンを押してタスクを作ります。</p> <p><img src="http://manaten.net/wp-content/uploads/2017/04/slack-status-tasker-04.png" alt="slack status" /></p> <p>それぞれのタスクの中身は次のようになっています。</p> <p><img src="http://manaten.net/wp-content/uploads/2017/04/slack-status-tasker-05.png" alt="slack status" /></p> <p>ここではstatusを変えたいslack teamが2つあったのでhttp getを二回しています。 http getはNetカテゴリの中にあります。http getの内容はこんな感じです。</p> <p><img src="http://manaten.net/wp-content/uploads/2017/04/slack-status-tasker-06.png" alt="slack status" /></p> <p><a href="https://api.slack.com/methods/users.profile.set">users.profile.set</a> でstatusを変更することができます。 slack tokenを発行し、profileに<a class="keyword" href="http://d.hatena.ne.jp/keyword/JSON">JSON</a>オブジェクトを与えています。 <a class="keyword" href="http://d.hatena.ne.jp/keyword/JSON">JSON</a>オブジェクトの <code>status_text</code> に変更後のstatusの文字列を、 <code>status_emoji</code> に変更後のstatusの絵文字を指定します。</p> <p>ここでhttp getの設定が正しいかどうかは、task editの画面で ▶ ボタンを 押すことで確認できます。指定したとおりにstatusが変更されればOKです。(slack <a class="keyword" href="http://d.hatena.ne.jp/keyword/api">api</a>は失敗しても200を返すためtaskerではエラーにならない点が少し罠です)</p> <h2>profileの設定</h2> <p>profileタブに戻り、 + ボタンを押してシチュエーションごとにprofileを作っていきます。 <a class="keyword" href="http://d.hatena.ne.jp/keyword/Wifi">Wifi</a>の接続状態を使ったprofileは State -> Net -> <a class="keyword" href="http://d.hatena.ne.jp/keyword/Wifi">Wifi</a> connectedを使います。 <a class="keyword" href="http://d.hatena.ne.jp/keyword/Wifi">Wifi</a> connectedでは<a class="keyword" href="http://d.hatena.ne.jp/keyword/SSID">SSID</a>と<a class="keyword" href="http://d.hatena.ne.jp/keyword/MAC%A5%A2%A5%C9%A5%EC%A5%B9">MACアドレス</a>のどちらかを識別に利用できます。</p> <p><img src="http://manaten.net/wp-content/uploads/2017/04/slack-status-tasker-03.png" alt="slack status" /></p> <p>profileにはEnter taskとExit taskの2つのtaskを紐付けることができ、それぞれ、profileの条件を満たした時と条件から外れたときに実行するタスクです。 profileの設定を終えれば完了です。</p> <h2>動作確認</h2> <p><img src="http://manaten.net/wp-content/uploads/2017/04/slack-status-tasker-07.png" alt="slack status" /></p> <p>通知領域にtaskerが常駐し、満たしているべきprofileの名前が表示されていればprofileの設定はOKです。 あとは、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Wifi">Wifi</a>をオン・オフしてみてtaskerの表示が変わることと、slackのstatusが変化するかを確認します。</p> <h1>応用</h1> <p>上記の<a class="keyword" href="http://d.hatena.ne.jp/keyword/Wifi">Wifi</a>接続状況によるstatusの変更に加えて、会社で充電中の場合は「自席にいる」、会社だけど充電中じゃない場合は「会社にいるけど離席中」みたいな statusにするようにしてみました。 他にもたくさんのイベントを扱えるので、いろんなことができそうです。</p> <h1>気になってること</h1> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Wifi">Wifi</a> connectedのexit taskでhttp getを実行しているわけですが(上図の「slack status 外出中」)、 <a class="keyword" href="http://d.hatena.ne.jp/keyword/wifi">wifi</a>が切れた瞬間は瞬間的にネットワークが存在してない状態になってしまい、http getが高確率で失敗してしまいます。 リトライが設定できればいいのですが、軽く調べた感じだととても面倒そうであったため、http getの前にwaitを15秒程度挟んでいます。 もっと良いやり方があったら教えていただきたいところ。</p> <p>追記: 「外出中」へ変更する条件を「<a class="keyword" href="http://d.hatena.ne.jp/keyword/Wifi">Wifi</a>が切れたとき」ではなく、「モバイルに繋がったとき」にすると良さそうです。</p> <h1>参考リンク</h1> <ul> <li><a href="https://play.google.com/store/apps/details?id=net.dinglisch.android.taskerm&amp;hl=en">Tasker</a> - 様々な条件に基づいてタスクを実行できる<a class="keyword" href="http://d.hatena.ne.jp/keyword/Android">Android</a>アプリ</li> <li><a href="http://tasker.dinglisch.net/userguide/en/loctears.html">Tasker: Location Without Tears</a> - taskerの位置情報系イベントの消費電力・制度についてまとまっている</li> <li><a href="https://slackhq.com/set-your-status-in-slack-28a793914b98">Set your status in Slack – Several People Are Typing — The Official Slack Blog</a> - slackのstatus機能について</li> <li><a href="https://api.slack.com/methods/users.profile.set">users.profile.set method | Slack</a> - statusを変更するときに叩く<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>のドキュメント</li> </ul> manaten Hubotでasync functionを使う hatenablog://entry/10328749687230053320 2017-03-24T11:00:00+09:00 2017-03-24T11:00:09+09:00 最近(と言っても一ヶ月前ですが・・・)node7.6.0がリリースされ、 --harmony-async-await をつけなくても async/await が利用可能となりました。 これにより、非同期処理を行うスクリプティングがより行いやすくなった(スクリプティング以上の用途ならばどうせ babel を使うためそこまで影響はない)と感じています。 hubotなどのbotプログラミングも 定常タスクを楽にするためのスクリプティングの一種であり、用途上連携サービスのAPIをたくさん叩くため、 恩寵を存分に得ることができます。この記事ではhubotで async/await を使う例を紹介します。 <p><img src="http://manaten.net/wp-content/uploads/2014/04/hubot.gif" alt="Hubotでasync functionを使う" /></p> <p>最近(と言っても一ヶ月前ですが・・・)<a href="https://nodejs.org/en/blog/release/v7.6.0/">node7.6.0がリリースされ</a>、 <code>--harmony-async-await</code> をつけなくても <code>async/await</code> が利用可能となりました。 これにより、非同期処理を行う<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C6%A5%A3%A5%F3%A5%B0">スクリプティング</a>がより行いやすくなった(<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C6%A5%A3%A5%F3%A5%B0">スクリプティング</a>以上の用途ならばどうせ <code>babel</code> を使うためそこまで影響はない)と感じています。</p> <p><a href="https://hubot.github.com/">hubot</a>などの<a class="keyword" href="http://d.hatena.ne.jp/keyword/bot">bot</a>プログラミングも 定常タスクを楽にするための<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C6%A5%A3%A5%F3%A5%B0">スクリプティング</a>の一種であり、用途上連携サービスの<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>をたくさん叩くため、 恩寵を存分に得ることができます。この記事ではhubotで <code>async/await</code> を使う例を紹介します。</p> <h1>準備</h1> <p>手元の開発環境及び、<a class="keyword" href="http://d.hatena.ne.jp/keyword/bot">bot</a>の動作環境のnodeを <code>7.6.0</code> 以上にします。 僕は手元では<a href="https://github.com/hokaccha/nodebrew">nodebrew</a>を使っていますので、 <code>nodebrew install-binary 7.6.0</code> <code>nodebrew use 7.6.0</code> とします。</p> <p>動作環境は、herokuや<a href="https://github.com/dokku/dokku">dokku</a>を使っている場合は <code>package.json</code> の <code>engines</code> フィールドに <code>7.6.0</code> を指定してやるだけです。楽ちん。</p> <pre class="code lang-json" data-lang="json" data-unlink><span class="synSpecial">{</span> &quot;<span class="synStatement">name</span>&quot;: &quot;<span class="synConstant">myapp</span>&quot;, &quot;<span class="synStatement">description</span>&quot;: &quot;<span class="synConstant">a really cool app</span>&quot;, &quot;<span class="synStatement">version</span>&quot;: &quot;<span class="synConstant">1.0.0</span>&quot;, &quot;<span class="synStatement">engines</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">node</span>&quot;: &quot;<span class="synConstant">7.6.0</span>&quot; <span class="synSpecial">}</span> <span class="synSpecial">}</span> </pre> <p>余談ですが、個人のhubotや他のアプリケーションを気楽にデプロイする環境としてdokkuはとても便利です。 プライベートサーバーにインス<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C8%A1%BC%A5%EB">トール</a>しておくととても捗ります。</p> <h1>コーディング</h1> <p>コーディングと言っても、特に工夫することはありません。 hubotのハンドラにおもむろにasync functionを渡すだけです。 hubotはasync functionに対応しているわけではないのですが、単にasync functionの終了を誰も待たない結果に 終わるだけです。もともとhubotはハンドラ内で非同期処理をバリバリ行う思想であるため、それで問題はありません。</p> <p>さて、ここでは <code>async/await</code> によって記述が楽になる例として、かんたんな<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>を書いてみます。</p> <h2>hubot-slackで指定した相手にDMを送る</h2> <p>slackのDMを指定したユーザーに送るだけの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>です。 DMは <a href="https://api.slack.com/methods">API Methods | Slack</a> の、 <code>im.open</code> で送る相手をユーザーIDで指定してDMを開き、得られるチャンネルIDを <code>chat.postMessage</code> することで送ることができます。</p> <p>hubot-slackの4.0.0くらいから、 <code>robot.adapter.client</code> 経由でslackの<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>を叩くことができるようになりました (<a href="https://slackapi.github.io/hubot-slack/basic_usage#general-web-api-patterns">参照</a>)。 加えて、叩くことのできる<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>のメソッドは、コールバックを指定しない場合はPromiseを返します。 よって、async/await を使い、次のようにして引数のユーザーにDMを送る<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>が書けます。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink>robot.respond(<span class="synConstant">/dm:send\s+([^\s]+)\s+(.+)/</span>, async res =&gt; <span class="synIdentifier">{</span> <span class="synStatement">const</span> username = res.match<span class="synIdentifier">[</span>1<span class="synIdentifier">]</span>; <span class="synStatement">const</span> text = res.match<span class="synIdentifier">[</span>2<span class="synIdentifier">]</span>; <span class="synStatement">const</span> user = robot.adapter.client.rtm.dataStore.getUserByName(username); <span class="synStatement">if</span> (!user) <span class="synIdentifier">{</span> <span class="synStatement">return</span>; <span class="synIdentifier">}</span> <span class="synStatement">const</span> im = await robot.adapter.client.web.im.open(user.id); await robot.adapter.client.web.chat.postMessage(im.channel.id, text, <span class="synIdentifier">{</span> as_user: <span class="synConstant">true</span> <span class="synIdentifier">}</span>); res.send(<span class="synConstant">'ok!'</span>); <span class="synIdentifier">}</span>); </pre> <p>動かすと次のような感じです。</p> <p><img src="http://manaten.net/wp-content/uploads/2017/03/hubot_async_1.png" alt="hubot-slackで指定した相手にDMを送る - 1" /></p> <p><img src="http://manaten.net/wp-content/uploads/2017/03/hubot_async_2.png" alt="hubot-slackで指定した相手にDMを送る - 2" /></p> <h2><a class="keyword" href="http://d.hatena.ne.jp/keyword/Github">Github</a> <a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>をたたき、指定したユーザーのそれぞれのpull request一覧を取得する</h2> <p>次の例は、<a class="keyword" href="http://d.hatena.ne.jp/keyword/github">github</a>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>を叩いて指定したユーザーのすべての所有<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>のpull requestを一覧する<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>です。 <a class="keyword" href="http://d.hatena.ne.jp/keyword/github">github</a> <a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>の <code>repos.getForUser</code> でユーザーの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>一覧を取得した後、それぞれの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>に対して <code>pullRequests.getAll</code> でプルリクエストを取得することで実現できます。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink>robot.respond(<span class="synConstant">/github:repos\s+(.+)/</span>, async res =&gt; <span class="synIdentifier">{</span> <span class="synStatement">const</span> username = res.match<span class="synIdentifier">[</span>1<span class="synIdentifier">]</span>; <span class="synStatement">const</span> repos = (await github.repos.getForUser(<span class="synIdentifier">{</span>username<span class="synIdentifier">}</span>)).data; <span class="synStatement">for</span> (<span class="synStatement">const</span> repo of repos.filter(repo =&gt; repo.open_issues_count &gt; 0)) <span class="synIdentifier">{</span> <span class="synStatement">const</span> pulls = (await github.pullRequests.getAll(<span class="synIdentifier">{</span> owner: username, repo : repo.name, state: <span class="synConstant">'open'</span> <span class="synIdentifier">}</span>)).data; <span class="synStatement">if</span> (pulls.length &gt; 0) <span class="synIdentifier">{</span> <span class="synStatement">const</span> text = pulls.map(pull =&gt; `:octocat: *$<span class="synIdentifier">{</span>username<span class="synIdentifier">}</span>/$<span class="synIdentifier">{</span>repo.name<span class="synIdentifier">}</span>* &lt;$<span class="synIdentifier">{</span>pull.html_url<span class="synIdentifier">}</span>|$<span class="synIdentifier">{</span>pull.title<span class="synIdentifier">}</span>&gt;` ).join(<span class="synSpecial">'\n'</span>); await robot.adapter.client.web.chat.postMessage(res.envelope.room, text, <span class="synIdentifier">{</span> as_user : <span class="synConstant">true</span>, unfurl_links: <span class="synConstant">false</span> <span class="synIdentifier">}</span>); <span class="synIdentifier">}</span> <span class="synIdentifier">}</span> <span class="synIdentifier">}</span>); </pre> <p>動かすと次のような感じです。</p> <p><img src="http://manaten.net/wp-content/uploads/2017/03/hubot_async_3.png" alt="Github APIをたたき、指定したユーザーのそれぞれのpull request一覧を取得する" /></p> <p>※これらの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>は要点だけを抜き出しているため、実際はエラー処理などがあったほうが望ましいです。 <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>の全体は、<a href="https://github.com/manaten/AsyncHubotExample">manaten/AsyncHubotExample</a> に置いてあります。</p> <h1>参考リンク</h1> <ul> <li><a href="https://github.com/manaten/AsyncHubotExample">manaten/AsyncHubotExample</a></li> <li><a href="https://nodejs.org/en/blog/release/v7.6.0/">Node v7.6.0 (Current) | Node.js</a></li> <li><a href="https://hubot.github.com/">HUBOT</a></li> <li><a href="https://github.com/hokaccha/nodebrew">hokaccha/nodebrew: Node.js version manager</a></li> <li><a href="https://github.com/dokku/dokku">dokku/dokku: A docker-powered PaaS that helps you build and manage the lifecycle of applications</a></li> <li><a href="https://devcenter.heroku.com/articles/nodejs-support#specifying-a-node-js-version">Heroku Node.js Support | Heroku Dev Center</a></li> <li><a href="https://api.slack.com/methods">API Methods | Slack</a></li> <li><a href="https://slackapi.github.io/hubot-slack/basic_usage#general-web-api-patterns">Slack | Basic Usage</a></li> </ul> manaten Slack上でインタラクティブに遊べるゲームを作るためのフレームワークを作った hatenablog://entry/10328749687200721579 2016-12-24T00:00:00+09:00 2016-12-24T00:00:13+09:00 この記事は Slack Advent Calendar 2016 24日目の記事です。 <p><img src="http://manaten.net/wp-content/uploads/2016/12/maze.gif" alt="Slack上でインタラクティブに遊べるゲームを作るためのフレームワークを作った" /></p> <p>この記事は <a href="http://qiita.com/advent-calendar/2016/slack">Slack Advent Calendar 2016</a> 24日目の記事です。</p> <p>半年ほど前に下のようなエントリを書きました。</p> <p><a href="http://blog.manaten.net/entry/hubot-slack-soukoban">Slack上でインタラクティブに倉庫番を遊べるhubot-slack-soukobanを作った - MANA-DOT</a></p> <p>Slackのリアクション機能と編集機能を活用し、Slack上で<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A5%E9%A5%AF%A5%C6%A5%A3%A5%D6">インタラクティブ</a>にゲームを作るという趣旨の内容でした。 今回は、このようなゲームを汎用的に作るための <a href="https://www.npmjs.com/package/slack-game-bot">slack-game-bot</a> というnpmパッケージを (<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%C9%A5%D9%A5%F3%A5%C8%A5%AB%A5%EC%A5%F3%A5%C0%A1%BC">アドベントカレンダー</a>のネタのために)作ったので紹介します。</p> <h1>概要</h1> <p>兎にも角にも、例を見ていただくのが早いです。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">const</span> <span class="synIdentifier">{</span>Game, GameBot<span class="synIdentifier">}</span> = require(<span class="synConstant">'slack-game-bot'</span>); <span class="synStatement">class</span> MyGame <span class="synStatement">extends</span> Game <span class="synIdentifier">{</span> getButtons() <span class="synIdentifier">{</span> <span class="synStatement">return</span> <span class="synIdentifier">[</span><span class="synConstant">'one'</span>, <span class="synConstant">'two'</span>, <span class="synConstant">'three'</span><span class="synIdentifier">]</span>; <span class="synIdentifier">}</span> async initialize() <span class="synIdentifier">{</span> await <span class="synIdentifier">this</span>.draw(<span class="synConstant">'press button!'</span>); <span class="synIdentifier">}</span> async onPushButton(reactionType) <span class="synIdentifier">{</span> await <span class="synIdentifier">this</span>.draw(reactionType); <span class="synIdentifier">}</span> <span class="synIdentifier">}</span> <span class="synStatement">new</span> GameBot(<span class="synIdentifier">{</span> myGame: MyGame, <span class="synIdentifier">}</span>).run(process.env.SLACK_TOKEN); </pre> <p>上記のコードを(babelなど然るべき変換をしたのちに)実行すると、指定したトークンに紐づくSlackチームで<a class="keyword" href="http://d.hatena.ne.jp/keyword/bot">bot</a>が動き、以下のような挙動をします。</p> <p><img src="http://manaten.net/wp-content/uploads/2016/12/mygame.gif" alt="mygame.gif (320×240)" /></p> <p>この<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EC%A1%BC%A5%E0%A5%EF%A1%BC%A5%AF">フレームワーク</a>は、自分でゲームを作るための抽象クラス <code>Game</code> と、 作ったGameを<a class="keyword" href="http://d.hatena.ne.jp/keyword/bot">bot</a>上で動かすための <code>GameBot</code> クラスから成ります。</p> <h2>Gameクラス</h2> <p><code>Game</code> クラスには抽象メソッド <code>getButtons</code>, <code>initialize</code>, <code>onPushButton</code> があり、それぞれを実装することでゲームを作ります。</p> <p><code>getButtons</code> はゲームで利用するボタンの配列を返すようにします。例では、 <code>['one', 'two', 'three']</code> を指定しているため、 <code>:one:</code>, <code>:two:</code>, <code>:three:</code> の3つのボタンが(リアクションとして)描画されています。</p> <p><code>initialize</code> では初期化処理を記述し、最初の描画を <code>draw</code> メソッドを用いて行います。また、ここで <code>this</code> に対してゲームの初期状態をセットしておく こともできます。</p> <p><code>onPushButton</code> はユーザーがボタンを押したときに発火し、押されたリアクションの種別と、押したユーザーの情報が得られます。 ここで、押されたボタンの内容に従って状態を書き換えた後に <code>draw</code> してあげることで<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A5%E9%A5%AF%A5%C6%A5%A3%A5%D6">インタラクティブ</a>なゲームを作ることができます。</p> <h2>GameBotクラス</h2> <p><code>GameBot</code> クラスに実装した <code>Game</code> を登録し、トークンを指定して <code>run</code> メソッドを実行することで<a class="keyword" href="http://d.hatena.ne.jp/keyword/bot">bot</a>が起動します。 <a class="keyword" href="http://d.hatena.ne.jp/keyword/bot">bot</a>は、登録の際に指定したオブジェクトのキーと同一の文字列に反応し、そのゲームを開始します。 複数回反応した場合は別々に新規ゲームが開始されます。また、複数の <code>Game</code> を別々のキーに登録することも可能です。</p> <h1>例</h1> <p><a href="https://github.com/manaten/slack-game-bot/tree/master/src/samples">src/samples</a> 下に配置してあります。</p> <h2>Janken</h2> <p><img src="http://manaten.net/wp-content/uploads/2016/12/janken.gif" alt="janken.gif (320×240)" /></p> <p>乱数を使ったシンプルなじゃんけんゲームです。ユーザー名を利用するサンプルでもあります。</p> <h2>Slot</h2> <p><img src="http://manaten.net/wp-content/uploads/2016/12/slot.gif" alt="slot.gif (320×240)" /></p> <p>setTimeoutを使い、定期的に表示を書き換えることで実装したスロットゲームです。</p> <h2>Soukoban</h2> <p><img src="http://manaten.net/wp-content/uploads/2016/12/soukoban.gif" alt="soukoban.gif (320×320)" /></p> <p><a href="http://blog.manaten.net/entry/hubot-slack-soukoban">以前実装した倉庫番</a> を移植したものです。ゲーム起動時に引数を受け付けるサンプルにもなっています。</p> <h2>Maze</h2> <p><img src="http://manaten.net/wp-content/uploads/2016/12/maze.gif" alt="maze.gif (320×320)" /></p> <p>自動生成された迷路をさまようだけの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AF%A5%BD%A5%B2%A1%BC">クソゲー</a>です。</p> <h1>余談</h1> <p>Slackには <a href="https://api.slack.com/docs/message-buttons">messages buttons</a> という、よりゲーム向け(に悪用できそうな)機能もあり、 当初こちらでボタンを実装しようと思ったのですが、ボタンのhookを受けるためのサーバーが必要なようであったため、手軽さにかけると思い 今回は見送りました。</p> <p>もっと気軽に使えるようにならないかなあ。</p> <h1>あとがき</h1> <p>奇しくも今日はクリスマス・イブとなります。みなさんクリスマスプレゼントは決まりましたか? まだの方はSlack上で遊べるゲームをプレゼントしてみてはいかがでしょうか? 職場の生産性を低下させること間違いなしです。</p> <p>Slack上でゲームを作ることのメリットですが、チャットに参加している全員が参加可能なゲームを作れるというのが大きいです。 たかが<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C1%D2%B8%CB%C8%D6">倉庫番</a>でも、複数人が操作するとまた違った面白さが見えてきます。 Twitch<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DD%A5%B1%A5%E2%A5%F3">ポケモン</a>のようなカオスなゲームを作れる可能性を秘めています。</p> <p>※この記事の内容は突貫で作ったネタですので、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EC%A1%BC%A5%E0%A5%EF%A1%BC%A5%AF">フレームワーク</a>のくせに設計が美しくないだとか、テストがないだとかは勘弁してください><。</p> <h1>参考リンク</h1> <ul> <li><a href="https://www.npmjs.com/package/slack-game-bot">slack-game-bot</a></li> <li><a href="https://github.com/manaten/slack-game-bot">manaten/slack-game-bot</a></li> <li><a href="http://blog.manaten.net/entry/hubot-slack-soukoban">Slack上でインタラクティブに倉庫番を遊べるhubot-slack-soukobanを作った - MANA-DOT</a></li> <li><a href="http://blog.manaten.net/entry/20_lines_soukoban">文字列置換により20行程度で実装する倉庫番ゲームのミニマム実装 - MANA-DOT</a></li> </ul> manaten すべてのリポジトリでGithubのpull requestをfetchする設定 hatenablog://entry/6653812171399550727 2016-06-06T13:42:09+09:00 2019-01-18T14:28:54+09:00 githubのプルリクエストのコミットをローカルにfetchする方法はよく知らてていますが (知らない人は是非設定をオススメします。特にコードレビューでレビュー相手がfork先からプルリクエストを出している場合でも対象コミットを取得できるため便利です)、 この方法はリポジトリごとに毎回設定する必要があり多少面倒です。 そこでこの設定を、 git config --global --add remote.origin.fetch '+refs/pull/*:refs/remotes/pr/*' としてグローバルに設定してみたところ、普通に動きすべてのローカルリポジトリでプルリクエストをfetch… <p><img src="http://manaten.net/wp-content/uploads/2015/05/octcat.gif" alt="すべてのリポジトリでgithubのpull requestをfetchする設定" /></p> <p><a href="https://gist.github.com/piscisaureus/3342247">githubのプルリクエストのコミットをローカルにfetchする方法</a>はよく知らてていますが (知らない人は是非設定をオススメします。特にコードレビューでレビュー相手がfork先からプルリクエストを出している場合でも対象コミットを取得できるため便利です)、 この方法はリポジトリごとに毎回設定する必要があり多少面倒です。</p> <p>そこでこの設定を、</p> <pre class="code lang-sh" data-lang="sh" data-unlink>git config <span class="synSpecial">--global</span> <span class="synSpecial">--add</span> remote.origin.fetch <span class="synStatement">'</span><span class="synConstant">+refs/pull/*:refs/remotes/pr/*</span><span class="synStatement">'</span> </pre> <p>としてグローバルに設定してみたところ、普通に動きすべてのローカルリポジトリでプルリクエストをfetchしてくれるようになりました。</p> <p><img src="http://manaten.net/wp-content/uploads/2016/06/fetch_pr.gif" alt="" /></p> <p>便利です。</p> <h1>気になること</h1> <p>remoteの設定をglobalに記述するのはあまり聞いたことがなく若干の不安はあります。 この状態でリポジトリの <code>remote.origin.fetch</code> の値を取ると、</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synStatement">&gt;&gt;</span> git config <span class="synSpecial">--get-all</span> remote.origin.fetch +refs/pull/*:refs/remotes/pr/* +refs/heads/*:refs/remotes/origin/* </pre> <p>となっており、リポジトリのconfigに記述した時と同じになっているため、問題ないのかな?とは思います。 今のところはGithub以外のリポジトリへのfetchも問題なく快適です。</p> <p>もし詳しい方がいれば、補足していただけると助かります。</p> <h1>参考リンク</h1> <ul> <li><a href="https://gist.github.com/piscisaureus/3342247">Checkout github pull requests locally</a></li> </ul> manaten 文字列置換により20行程度で実装する倉庫番ゲームのミニマム実装 hatenablog://entry/6653812171397813659 2016-05-27T13:13:56+09:00 2016-05-27T19:47:01+09:00 先日、Slack上でインタラクティブに倉庫番を遊べるhubot-slack-soukobanを作ったにてSlack上で遊べる倉庫番の紹介を行いました。 このhubotスクリプトでは文字列置換による実装を駆使して倉庫番のゲームロジック部分が20行程度で実装されており、今回はその実装について解説をします。 ※このエントリは倉庫番というゲームのルールを知っている前提で書かれていますので、予めご了承ください。 <p><img src="http://manaten.net/wp-content/uploads/2016/05/soukoban_pixel.gif" alt="文字列置換により20行程度で実装する倉庫番ゲームのミニマム実装" /></p> <p>先日、<a href="http://blog.manaten.net/entry/hubot-slack-soukoban">Slack上でインタラクティブに倉庫番を遊べるhubot-slack-soukobanを作った</a>にてSlack上で遊べる<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C1%D2%B8%CB%C8%D6">倉庫番</a>の紹介を行いました。 このhubot<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>では文字列置換による実装を駆使して<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C1%D2%B8%CB%C8%D6">倉庫番</a>のゲームロジック部分が20行程度で実装されており、今回はその実装について解説をします。</p> <p>※このエントリは<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C1%D2%B8%CB%C8%D6">倉庫番</a>というゲームのルールを知っている前提で書かれていますので、予めご了承ください。</p> <h1>コード全貌</h1> <p><a href="https://github.com/manaten/hubot-slack-soukoban">hubot-slack-soukoban</a> のうち、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C1%D2%B8%CB%C8%D6">倉庫番</a>の出力用の処理を抜いたロジック部分は以下のようになっています。</p> <pre class="code lang-coffee" data-lang="coffee" data-unlink><span class="synType">Util</span> <span class="synStatement">=</span> <span class="synIdentifier">strToMatrix</span><span class="synStatement">:</span> <span class="synSpecial">(</span>str<span class="synSpecial">)</span> <span class="synStatement">-&gt;</span> str<span class="synStatement">.</span>split<span class="synSpecial">(</span><span class="synConstant">/</span><span class="synSpecial">\n</span><span class="synConstant">/</span><span class="synSpecial">)</span><span class="synStatement">.</span>map<span class="synSpecial">((</span>s<span class="synSpecial">)</span> <span class="synStatement">-&gt;</span> s<span class="synStatement">.</span>split<span class="synSpecial">(</span><span class="synConstant">''</span><span class="synSpecial">))</span> <span class="synIdentifier">matrixToStr</span><span class="synStatement">:</span> <span class="synSpecial">(</span>matrix<span class="synSpecial">)</span> <span class="synStatement">-&gt;</span> matrix<span class="synStatement">.</span>map<span class="synSpecial">((</span>a<span class="synSpecial">)</span> <span class="synStatement">-&gt;</span> a<span class="synStatement">.</span>join<span class="synSpecial">(</span><span class="synConstant">''</span><span class="synSpecial">))</span><span class="synStatement">.</span>join<span class="synSpecial">(</span><span class="synConstant">'</span><span class="synSpecial">\n</span><span class="synConstant">'</span><span class="synSpecial">)</span> <span class="synIdentifier">translocateMatrix</span><span class="synStatement">:</span> <span class="synSpecial">(</span>matrix<span class="synSpecial">)</span> <span class="synStatement">-&gt;</span> _<span class="synStatement">.</span>range<span class="synSpecial">(</span>matrix<span class="synSpecial">[</span><span class="synConstant">0</span><span class="synSpecial">]</span><span class="synStatement">.</span>length<span class="synSpecial">)</span><span class="synStatement">.</span>map<span class="synSpecial">((</span>j<span class="synSpecial">)</span> <span class="synStatement">-&gt;</span> _<span class="synStatement">.</span>range<span class="synSpecial">(</span>matrix<span class="synStatement">.</span>length<span class="synSpecial">)</span><span class="synStatement">.</span>map<span class="synSpecial">((</span>i<span class="synSpecial">)</span> <span class="synStatement">-&gt;</span> matrix<span class="synSpecial">[</span>i<span class="synSpecial">][</span>j<span class="synSpecial">]))</span> <span class="synIdentifier">translocateStr</span><span class="synStatement">:</span> <span class="synSpecial">(</span>str<span class="synSpecial">)</span> <span class="synStatement">-&gt;</span> <span class="synType">Util</span><span class="synStatement">.</span>matrixToStr <span class="synType">Util</span><span class="synStatement">.</span>translocateMatrix <span class="synType">Util</span><span class="synStatement">.</span>strToMatrix str <span class="synIdentifier">flipStr</span><span class="synStatement">:</span> <span class="synSpecial">(</span>str<span class="synSpecial">)</span> <span class="synStatement">-&gt;</span> <span class="synType">Util</span><span class="synStatement">.</span>matrixToStr <span class="synType">Util</span><span class="synStatement">.</span>strToMatrix<span class="synSpecial">(</span>str<span class="synSpecial">)</span><span class="synStatement">.</span>map<span class="synSpecial">(</span>_<span class="synStatement">.</span>reverse<span class="synSpecial">)</span> <span class="synIdentifier">moveRight</span><span class="synStatement">:</span> <span class="synSpecial">(</span>state<span class="synSpecial">)</span> <span class="synStatement">-&gt;</span> state <span class="synStatement">.</span>replace<span class="synSpecial">(</span><span class="synConstant">/[PBg]/g</span><span class="synSpecial">,</span> <span class="synSpecial">(</span>s<span class="synSpecial">)</span> <span class="synStatement">-&gt;</span> <span class="synSpecial">{</span><span class="synType">P</span><span class="synStatement">:</span> <span class="synConstant">'ap'</span><span class="synSpecial">,</span> <span class="synType">B</span><span class="synStatement">:</span> <span class="synConstant">'ab'</span><span class="synSpecial">,</span> <span class="synIdentifier">g</span><span class="synStatement">:</span> <span class="synConstant">'a.'</span><span class="synSpecial">}[</span>s<span class="synSpecial">])</span> <span class="synStatement">.</span>replace<span class="synSpecial">(</span><span class="synConstant">/(a?)p(a?)b(a?)</span><span class="synSpecial">\.</span><span class="synConstant">/</span><span class="synSpecial">,</span> <span class="synConstant">'$1.$2p$3b'</span><span class="synSpecial">)</span> <span class="synStatement">.</span>replace<span class="synSpecial">(</span><span class="synConstant">/(a?)p(a?)</span><span class="synSpecial">\.</span><span class="synConstant">/</span><span class="synSpecial">,</span> <span class="synConstant">'$1.$2p'</span><span class="synSpecial">)</span> <span class="synStatement">.</span>replace<span class="synSpecial">(</span><span class="synConstant">/a[pb</span><span class="synSpecial">\.</span><span class="synConstant">]/g</span><span class="synSpecial">,</span> <span class="synSpecial">(</span>s<span class="synSpecial">)</span> <span class="synStatement">-&gt;</span> <span class="synSpecial">{</span><span class="synIdentifier">ap</span><span class="synStatement">:</span> <span class="synConstant">'P'</span><span class="synSpecial">,</span> <span class="synIdentifier">ab</span><span class="synStatement">:</span> <span class="synConstant">'B'</span><span class="synSpecial">,</span> <span class="synConstant">'a.'</span><span class="synStatement">:</span> <span class="synConstant">'g'</span><span class="synSpecial">}[</span>s<span class="synSpecial">])</span> <span class="synStatement">class</span> <span class="synType">SoukobanGame</span> <span class="synIdentifier">constructor</span><span class="synStatement">:</span> <span class="synSpecial">(</span><span class="synIdentifier">@state</span><span class="synSpecial">)</span> <span class="synStatement">-&gt;</span> <span class="synIdentifier">@work</span> <span class="synStatement">=</span> <span class="synConstant">0</span> <span class="synIdentifier">isClear</span><span class="synStatement">:</span> <span class="synStatement">-&gt;</span> <span class="synStatement">!</span><span class="synSpecial">(</span><span class="synConstant">/[gP]/</span><span class="synStatement">.</span>test <span class="synIdentifier">@state</span><span class="synSpecial">)</span> <span class="synIdentifier">updateState</span><span class="synStatement">:</span> <span class="synSpecial">(</span>newState<span class="synSpecial">)</span> <span class="synStatement">-&gt;</span> <span class="synStatement">if</span> newState <span class="synStatement">isnt</span> <span class="synIdentifier">@state</span> <span class="synIdentifier">@state</span> <span class="synStatement">=</span> newState <span class="synIdentifier">@work</span> <span class="synStatement">=</span> <span class="synIdentifier">@work</span> <span class="synStatement">+</span> <span class="synConstant">1</span> <span class="synIdentifier">right</span><span class="synStatement">:</span> <span class="synStatement">-&gt;</span> <span class="synIdentifier">@updateState</span> <span class="synType">Util</span><span class="synStatement">.</span>moveRight <span class="synIdentifier">@state</span> <span class="synIdentifier">up</span><span class="synStatement">:</span> <span class="synStatement">-&gt;</span> <span class="synIdentifier">@updateState</span> <span class="synType">Util</span><span class="synStatement">.</span>translocateStr <span class="synType">Util</span><span class="synStatement">.</span>flipStr <span class="synType">Util</span><span class="synStatement">.</span>moveRight <span class="synType">Util</span><span class="synStatement">.</span>flipStr <span class="synType">Util</span><span class="synStatement">.</span>translocateStr <span class="synIdentifier">@state</span> <span class="synIdentifier">down</span><span class="synStatement">:</span> <span class="synStatement">-&gt;</span> <span class="synIdentifier">@updateState</span> <span class="synType">Util</span><span class="synStatement">.</span>translocateStr <span class="synType">Util</span><span class="synStatement">.</span>moveRight <span class="synType">Util</span><span class="synStatement">.</span>translocateStr <span class="synIdentifier">@state</span> <span class="synIdentifier">left</span><span class="synStatement">:</span> <span class="synStatement">-&gt;</span> <span class="synIdentifier">@updateState</span> <span class="synType">Util</span><span class="synStatement">.</span>flipStr <span class="synType">Util</span><span class="synStatement">.</span>moveRight <span class="synType">Util</span><span class="synStatement">.</span>flipStr <span class="synIdentifier">@state</span> </pre> <p>改行を抜かすと23行です。見やすさのための改行も消せば20行切る程度のコード量です。</p> <h1>解説</h1> <p>コードはユーティリティオブジェクトUtilと、SoukobanGameクラスからなっています。 SoukobanGameクラスは以下の様な文字列を初期状態として<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%B9%A5%C8%A5%E9%A5%AF%A5%BF">コンストラクタ</a>で受け取ります。 そしてup、right、left、down、の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E1%A5%BD%A5%C3%A5%C9">メソッド</a>を呼ぶと、フィールドに保持するこの文字列を置換します。</p> <pre class="code" data-lang="" data-unlink>######### #.......# #.##b##.# #.p...b.# ##bggg#.# .#....#.# .###..### ...####..</pre> <p>初期状態として与えるこの文字列は、見たまんま<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C1%D2%B8%CB%C8%D6">倉庫番</a>のマップを表しています。 <code>#</code> は壁、 <code>.</code> は床です。 <code>p</code> はプレイヤー、 <code>b</code> は箱、 <code>g</code> は箱を運ぶべきゴールとなっています。 またこの文字列には出現していませんが、<code>P</code> はゴールに乗っている状態のプレイヤー、 <code>B</code> はゴールに乗っている状態の箱となっています。</p> <h2>right<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E1%A5%BD%A5%C3%A5%C9">メソッド</a>(右方向への移動)</h2> <p>right<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E1%A5%BD%A5%C3%A5%C9">メソッド</a>は、右に移動する<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E1%A5%BD%A5%C3%A5%C9">メソッド</a>です。この<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E1%A5%BD%A5%C3%A5%C9">メソッド</a>の実態は見ての通り <code>Util.moveRight</code> です。この<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E1%A5%BD%A5%C3%A5%C9">メソッド</a>は例えば、</p> <pre class="code" data-lang="" data-unlink>######### #.......# #.##b##.# #.p...b.# ##bggg#.# .#....#.# .###..### ...####..</pre> <p>という文字列を</p> <pre class="code" data-lang="" data-unlink>######### #.......# #.##b##.# #..p..b.# ##bggg#.# .#....#.# .###..### ...####..</pre> <p>に置換します。また、箱が右側にある場合は箱も右に移動させます。 つまりゴールとの重なりを考慮しなければ、 <code>p.</code> という部分文字列を <code>.p</code> に置換し、 <code>pb.</code> という部分文字列を <code>.pb</code> と置換します。プレイヤーや箱の右側に壁がある場合は 置換パターンにないため、特に当たり判定をしなくても、壁の方向に進むことはできません。</p> <p>ゴールとの重なりを考慮しなければ、これを素直に置換すれば実装完了なのですが、ゴールとの重なりを考慮する必要があります。例えば、</p> <pre class="code" data-lang="" data-unlink>######### #.......# #.##b##.# #.b...b.# ##pggg#.# .#....#.# .###..### ...####..</pre> <p>という文字列は、</p> <pre class="code" data-lang="" data-unlink>######### #.......# #.##b##.# #.b...b.# ##.Pgg#.# .#....#.# .###..### ...####..</pre> <p>に置換されなければなりません。</p> <p>このゴールとの重なりも考慮した結果が、 <code>Util.moveRight</code> の実装となります。</p> <pre class="code lang-coffee" data-lang="coffee" data-unlink><span class="synIdentifier">moveRight</span><span class="synStatement">:</span> <span class="synSpecial">(</span>state<span class="synSpecial">)</span> <span class="synStatement">-&gt;</span> state <span class="synStatement">.</span>replace<span class="synSpecial">(</span><span class="synConstant">/[PBg]/g</span><span class="synSpecial">,</span> <span class="synSpecial">(</span>s<span class="synSpecial">)</span> <span class="synStatement">-&gt;</span> <span class="synSpecial">{</span><span class="synType">P</span><span class="synStatement">:</span> <span class="synConstant">'ap'</span><span class="synSpecial">,</span> <span class="synType">B</span><span class="synStatement">:</span> <span class="synConstant">'ab'</span><span class="synSpecial">,</span> <span class="synIdentifier">g</span><span class="synStatement">:</span> <span class="synConstant">'a.'</span><span class="synSpecial">}[</span>s<span class="synSpecial">])</span> <span class="synStatement">.</span>replace<span class="synSpecial">(</span><span class="synConstant">/(a?)p(a?)b(a?)</span><span class="synSpecial">\.</span><span class="synConstant">/</span><span class="synSpecial">,</span> <span class="synConstant">'$1.$2p$3b'</span><span class="synSpecial">)</span> <span class="synStatement">.</span>replace<span class="synSpecial">(</span><span class="synConstant">/(a?)p(a?)</span><span class="synSpecial">\.</span><span class="synConstant">/</span><span class="synSpecial">,</span> <span class="synConstant">'$1.$2p'</span><span class="synSpecial">)</span> <span class="synStatement">.</span>replace<span class="synSpecial">(</span><span class="synConstant">/a[pb</span><span class="synSpecial">\.</span><span class="synConstant">]/g</span><span class="synSpecial">,</span> <span class="synSpecial">(</span>s<span class="synSpecial">)</span> <span class="synStatement">-&gt;</span> <span class="synSpecial">{</span><span class="synIdentifier">ap</span><span class="synStatement">:</span> <span class="synConstant">'P'</span><span class="synSpecial">,</span> <span class="synIdentifier">ab</span><span class="synStatement">:</span> <span class="synConstant">'B'</span><span class="synSpecial">,</span> <span class="synConstant">'a.'</span><span class="synStatement">:</span> <span class="synConstant">'g'</span><span class="synSpecial">}[</span>s<span class="synSpecial">])</span> </pre> <p>ゲームの状態を示す文字列に対して、4つの置換を行っています。</p> <p>まず最初の置換</p> <pre class="code lang-coffee" data-lang="coffee" data-unlink><span class="synStatement">.</span>replace<span class="synSpecial">(</span><span class="synConstant">/[PBg]/g</span><span class="synSpecial">,</span> <span class="synSpecial">(</span>s<span class="synSpecial">)</span> <span class="synStatement">-&gt;</span> <span class="synSpecial">{</span><span class="synType">P</span><span class="synStatement">:</span> <span class="synConstant">'ap'</span><span class="synSpecial">,</span> <span class="synType">B</span><span class="synStatement">:</span> <span class="synConstant">'ab'</span><span class="synSpecial">,</span> <span class="synIdentifier">g</span><span class="synStatement">:</span> <span class="synConstant">'a.'</span><span class="synSpecial">}[</span>s<span class="synSpecial">])</span> </pre> <p>はゴール上のプレイヤー <code>P</code> を <code>ap</code> という文字列に、 ゴール上の箱 <code>B</code> を <code>ab</code> に、 ゴール <code>g</code> を <code>a.</code> に置換します。 察しがいい方は分かりそうですが、ゴールの有無を <code>a</code> という一時的な文字列に置換し、ゴールの有無+ <code>p</code> , <code>b</code> , <code>.</code> のどれかに統一しています。 つまり、この置換をしたあとaが直前にある文字はゴール上にあるというフラグです。</p> <p>次の文字列</p> <pre class="code" data-lang="" data-unlink>######### #.......# #.##b##.# #.b...b.# ##.Pgg#.# .#....#.# .###..### ...####..</pre> <p>は、</p> <pre class="code" data-lang="" data-unlink>######### #.......# #.##b##.# #.b...b.# ##.apa.a.#.# .#....#.# .###..### ...####..</pre> <p>こう置換されます。</p> <p>次とその次の置換</p> <pre class="code lang-coffee" data-lang="coffee" data-unlink><span class="synStatement">.</span>replace<span class="synSpecial">(</span><span class="synConstant">/(a?)p(a?)b(a?)</span><span class="synSpecial">\.</span><span class="synConstant">/</span><span class="synSpecial">,</span> <span class="synConstant">'$1.$2p$3b'</span><span class="synSpecial">)</span> <span class="synStatement">.</span>replace<span class="synSpecial">(</span><span class="synConstant">/(a?)p(a?)</span><span class="synSpecial">\.</span><span class="synConstant">/</span><span class="synSpecial">,</span> <span class="synConstant">'$1.$2p'</span><span class="synSpecial">)</span> </pre> <p>は、先ほど出てきた「<code>p.</code> という部分文字列を <code>.p</code> に置換し、<code>pb.</code> という部分文字列を <code>.pb</code> と置換する」ことを <code>a</code> の位置=マップ城のゴールの位置を維持したまま行う置換です。 例えば <code>ap.</code> は <code>a.p</code> に、 <code>paba.</code> は <code>.apab</code> に置換されます。</p> <pre class="code" data-lang="" data-unlink>######### #.......# #.##b##.# #.b...b.# ##.apa.a.#.# .#....#.# .###..### ...####..</pre> <p>は、</p> <pre class="code" data-lang="" data-unlink>######### #.......# #.##b##.# #.b...b.# ##.a.apa.#.# .#....#.# .###..### ...####..</pre> <p>に置換されます。</p> <p>最後の変換</p> <pre class="code lang-coffee" data-lang="coffee" data-unlink><span class="synStatement">.</span>replace<span class="synSpecial">(</span><span class="synConstant">/a[pb</span><span class="synSpecial">\.</span><span class="synConstant">]/g</span><span class="synSpecial">,</span> <span class="synSpecial">(</span>s<span class="synSpecial">)</span> <span class="synStatement">-&gt;</span> <span class="synSpecial">{</span><span class="synIdentifier">ap</span><span class="synStatement">:</span> <span class="synConstant">'P'</span><span class="synSpecial">,</span> <span class="synIdentifier">ab</span><span class="synStatement">:</span> <span class="synConstant">'B'</span><span class="synSpecial">,</span> <span class="synConstant">'a.'</span><span class="synStatement">:</span> <span class="synConstant">'g'</span><span class="synSpecial">}[</span>s<span class="synSpecial">])</span> </pre> <p>は、 <code>a</code> を元に戻す置換ですね。</p> <pre class="code" data-lang="" data-unlink>######### #.......# #.##b##.# #.b...b.# ##.a.apa.#.# .#....#.# .###..### ...####..</pre> <p>は、</p> <pre class="code" data-lang="" data-unlink>######### #.......# #.##b##.# #.b...b.# ##.gPg#.# .#....#.# .###..### ...####..</pre> <p>に置換されます。</p> <p>以上の置換で、プレイヤーは無事右に移動することができました。</p> <h2>left, up, down<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E1%A5%BD%A5%C3%A5%C9">メソッド</a></h2> <p>left, up, down<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E1%A5%BD%A5%C3%A5%C9">メソッド</a>はそれぞれ左、上、下に移動する<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E1%A5%BD%A5%C3%A5%C9">メソッド</a>ですが、これらは右に移動させる<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E1%A5%BD%A5%C3%A5%C9">メソッド</a> <code>Util.moveRight</code> と2つのUtil<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E1%A5%BD%A5%C3%A5%C9">メソッド</a> <code>flipStr</code> と <code>translocateStr</code> によって実装されています。</p> <p><code>flipStr</code> はマップ文字列を左右反転させる<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E1%A5%BD%A5%C3%A5%C9">メソッド</a>です。</p> <pre class="code" data-lang="" data-unlink>######### #.......# #.##b##.# #.b...b.# ##.gPg#.# .#....#.# .###..### ...####..</pre> <p>は</p> <pre class="code" data-lang="" data-unlink>######### #.......# #.##b##.# #.b...b.# #.#gPg.## #.#....#. ###..###. ..####...</pre> <p>に変換されます。</p> <p><code>translocateStr</code> はマップ文字列を転置させる<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E1%A5%BD%A5%C3%A5%C9">メソッド</a>です。</p> <pre class="code" data-lang="" data-unlink>######### #.......# #.##b##.# #.b...b.# ##.gPg#.# .#....#.# .###..### ...####..</pre> <p>は</p> <pre class="code" data-lang="" data-unlink>#####... #...###. #.#b..#. #.#.g.## #.b.P..# #.#.g..# #.#b#### #.....#. #######. </pre> <p>に変換されます。</p> <p>これらの関数でマップを変形し、任意の方向を右に向けることで、どの方向への移動も右移動で実装しています。</p> <p>左移動 <code>left</code> は左右反転してから右移動し、もう一度左右反転することで実装しています。 下移動 <code>down</code> は転地してから右移動し、もう一度転地することで実装しています。 そして上移動 <code>up</code> は、転地してから左右反転して右移動し、その後左右反転と転置をすることで実装しています。</p> <p>これで四方向への移動が実装できました。</p> <h2>クリア判定isClear</h2> <p>ここまで実装すればクリア判定は簡単で、文字列にゴール <code>g</code> またはゴールを踏んでいるプレイヤー <code>P</code> が残ってないかを判定するだけです。</p> <pre class="code" data-lang="" data-unlink>isClear: -&gt; !(/[gP]/.test @state)</pre> <p>これで、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C1%D2%B8%CB%C8%D6">倉庫番</a>ゲームのメイン機能は完成です。</p> <h1>雑感</h1> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%C1%D2%B8%CB%C8%D6">倉庫番</a>の状態を文字列として表し、その文字列を置換して次の状態を作ることで<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C1%D2%B8%CB%C8%D6">倉庫番</a>のゲームを実装する方法について紹介しました。 これは以前このブログで紹介した、 <a href="http://blog.manaten.net/entry/867">正規表現による置換の繰り返しだけでライフゲームを作る</a> と似た話になっています。</p> <p>この実装方法の楽ちんなのは、変化する部分にだけマッチさせてそこを次の状態に遷移させるという記述の組み合わせで、ゲーム全体を実装できるところです。 今回は「プレイヤーと床が隣接してる時、それらを入れ替える」と「プレイヤー→箱→床の順で並んでる時、床→プレイヤー→箱の順番に並べ替える」という2つのルールがありました。 この2つのルールを比較的素直に置換で記述してあげれば、それだけで<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C1%D2%B8%CB%C8%D6">倉庫番</a>が作れてしまいます。</p> <p>このア<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%C7%A5%A2">イデア</a>は、うまくゲームルールにマッチする状態表現と、状態遷移の記述表現があればそのゲームが簡単に作れてしまうということだと思います。 <a class="keyword" href="http://d.hatena.ne.jp/keyword/%C1%D2%B8%CB%C8%D6">倉庫番</a>というゲームの表現として、普通の文字列が状態表現の記述としてうまく機能し、文字列置換が状態遷移の記述としてうまく機能していると考えられます。 文字列では限界が見えるものの、もっと多くのゲームの記述に適した表現を考え出せればすごく簡単にゲームが作れるのではないのかと思ったりします。 まあ夢なんですが。</p> <p>また、上下左への移動の記述を、マップを変形させることで右移動だけで記述してサボるという技も使いました。 左はともかく、文字列でマップを表現した場合上下へのマッチングは比較的面倒です(<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E9%A5%A4%A5%D5%A5%B2%A1%BC%A5%E0">ライフゲーム</a>の実装でも鬼門でした)。 転置して上下を左右に変えてしまうことで、ここはスマートに実装できました。</p> <h1>関連リンク</h1> <ul> <li><a href="http://blog.manaten.net/entry/hubot-slack-soukoban">Slack上でインタラクティブに倉庫番を遊べるhubot-slack-soukobanを作った - MANA-DOT</a></li> <li><a href="https://github.com/manaten/hubot-slack-soukoban">manaten/hubot-slack-soukoban</a></li> <li><a href="http://blog.manaten.net/entry/867">正規表現による置換の繰り返しだけでライフゲームを作る - MANA-DOT</a></li> <li><a href="http://blog.manaten.net/entry/regexp_lifegame">正規表現で作るワンライナーライフゲーム - MANA-DOT</a></li> </ul> manaten Slack上でインタラクティブに倉庫番を遊べるhubot-slack-soukobanを作った hatenablog://entry/6653812171396654320 2016-05-18T12:11:52+09:00 2016-05-27T13:28:40+09:00 Slack 上でリアクションを操作ボタンとして利用して倉庫番を遊べる、 hubot-slack-soukoban を作りました。 <p><img src="http://manaten.net/wp-content/uploads/2016/05/soukoban.gif" alt="Slack上でインタラクティブに倉庫番ゲームを遊べるhubot-slack-soukobanを作った" /></p> <p><a href="https://slack.com/">Slack</a> 上でリアクションを操作ボタンとして利用して<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C1%D2%B8%CB%C8%D6">倉庫番</a>を遊べる、 <a href="https://www.npmjs.com/package/hubot-slack-soukoban">hubot-slack-soukoban</a> を作りました。</p> <p>その昔、 <a href="http://blog.manaten.net/entry/slack-puzzle-bot">Hubot-slack で絵文字でアニメーションする8パズルゲームができるbotを作った</a> という記事を書きました。 この記事では、Slackの編集を<a class="keyword" href="http://d.hatena.ne.jp/keyword/bot">bot</a>に叩かせまくり、Slack上で動く<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D1%A5%BA%A5%EB%A5%B2%A1%BC%A5%E0">パズルゲーム</a>を作ったことを紹介しました。 しかしこの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D1%A5%BA%A5%EB%A5%B2%A1%BC%A5%E0">パズルゲーム</a>には謎のコマンド列をタイプして<a class="keyword" href="http://d.hatena.ne.jp/keyword/bot">bot</a>とやり取りしなければ遊べないという弱点がありました。 そこで今回、Slackのリアクション機能で配置されるボタンを操作ボタンとして利用することで、よりプレイヤーが直感的に遊べるゲームをSlack上に実現しました。</p> <p><code>soukoban</code> という文字列に反応して<a class="keyword" href="http://d.hatena.ne.jp/keyword/bot">bot</a>がランダムに選ばれた<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C1%D2%B8%CB%C8%D6">倉庫番</a>のマップと、操作するためのリアクションボタンを表示します。 操作法は上記画像のままです。リアクションのボタンがそれぞれ上下左右の移動ボタンになっており、押したボタンの方向にキャラクターが進み、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C1%D2%B8%CB%C8%D6">倉庫番</a>が遊べます。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%C1%D2%B8%CB%C8%D6">倉庫番</a>のマップは<a class="keyword" href="http://d.hatena.ne.jp/keyword/Google">Google</a>画像検索で見つかったものを拝借しましたが、もし問題があるようであれば教えていただければ幸いです。</p> <h1>インストール</h1> <p><a href="https://www.npmjs.com/package/hubot-slack-soukoban">hubot-slack-soukoban</a> を <code>npm install</code> したのち、 <code>external-scripts.json</code> に <code>hubot-slack-soukoban</code> を追加することでhubotにこの機能を追加できます。</p> <p>あと、READMEに書き忘れましたが、 <code>:mu:</code> という名前の透明ピクセルのみの絵文字が必要です。</p> <h1><a class="keyword" href="http://d.hatena.ne.jp/keyword/bot">bot</a>の動作</h1> <p>大体以下の様な処理を行っています。</p> <ul> <li>「soukoban」というキーワードに反応し、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C1%D2%B8%CB%C8%D6">倉庫番</a>の画面を書き込む。</li> <li>上記書き込みに、操作用のリアクションボタンを<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>で付与する。</li> <li>操作用のリアクションボタンのうちどれかが押されると、編集で操作後の画面に更新する。</li> <li>ゲームクリアするとなんか出る。</li> </ul> <h1>実装にまつわること</h1> <p>実はこのhubot-slack-soukoban、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C1%D2%B8%CB%C8%D6">倉庫番</a>のロジック部分が20行程度で実装されています。 Slackで<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A5%E9%A5%AF%A5%C6%A5%A3%A5%D6">インタラクティブ</a>ゲームを作るというネタが本来の目的であり、ゲーム本体部分の実装に労力を割きたくなかっためできるだけ短く実装しためです。 <a class="keyword" href="http://d.hatena.ne.jp/keyword/%C1%D2%B8%CB%C8%D6">倉庫番</a>の実装として、かなりミニマムな方なのではないかなと思っています。 具体的にはキー入力時の状態遷移を文字列置換で実装し、更に各方向への移動の実装をマップを反転させてサボることで実装量を減らしているのですが、 後日この事についてもエントリを書こうと思っています。</p> <p>興味がある方は <a href="https://github.com/manaten/hubot-slack-soukoban/blob/master/src/index.coffee#L27">こちら</a> を見ていただけると幸いです。</p> <h3>追記</h3> <p><a href="http://blog.manaten.net/entry/20_lines_soukoban">文字列置換により20行程度で実装する倉庫番ゲームのミニマム実装 - MANA-DOT</a> にて、この実装についての紹介を書きました。</p> <h1>感想</h1> <p>この手法を使えばいろんなゲームをSlack上で実現できそうです。 ただ、応答性はそんなに良くないので、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C1%D2%B8%CB%C8%D6">倉庫番</a>のような速効性は求められないゲームに限られますね。 <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%ED%A1%BC%A5%B0%A5%E9%A5%A4%A5%AF">ローグライク</a>なんかは実装できて相性もいいのではとか妄想したりします(作りませんが)。</p> <p>また、Slackはあくまでリアルタイムチャットなので、ゲームのプレイ状況もリアルタイムに共有されます。 <a class="keyword" href="http://d.hatena.ne.jp/keyword/%C1%D2%B8%CB%C8%D6">倉庫番</a>だと、誰かが操作中に他人が操作して意図せず邪魔したりなどの面白みがあったりします。なにか可能性だけ感じますね!</p> <h1>参考リンク</h1> <ul> <li><a href="https://slack.com/">Slack</a></li> <li><a href="https://github.com/github/hubot">github/hubot</a></li> <li><a href="https://github.com/slackhq/hubot-slack">slackhq/hubot-slack</a></li> <li><a href="https://www.npmjs.com/package/hubot-slack-soukoban">manaten/hubot-slack-soukoban</a></li> <li><a href="http://blog.manaten.net/entry/20_lines_soukoban">文字列置換により20行程度で実装する倉庫番ゲームのミニマム実装 - MANA-DOT</a></li> </ul> manaten レスポンシブなCSSスプライト hatenablog://entry/6653586347149425100 2016-05-17T12:57:30+09:00 2016-05-17T12:57:58+09:00 よく知られたCSSスプライトはボックスの幅・高さをスプライト画像と同じに指定し、background-position をずらすことで複数のスプライト画像を含むスプライトシートの中から対象画像だけを表示します。 この方法だと表示できる画像のサイズは固定なのですが、 background-sizeとbackground-positionを % で指定し、ボックスの幅によって可変サイズな (レスポンシブ、あるいはフレキシブルな)CSSスプライトの表示法を紹介します。 <p><img src="http://manaten.net/wp-content/uploads/2014/07/responsive_2.png" alt="レスポンシブなCSSスプライト" /></p> <p>よく知られた<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>スプライトはボックスの幅・高さをスプライト画像と同じに指定し、background-position をずらすことで<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CA%A3%BF%F4">複数</a>のスプライト画像を含むスプライトシートの中から対象画像だけを表示します。 この方法だと表示できる画像のサイズは固定なのですが、 background-sizeとbackground-positionを % で指定し、ボックスの幅によって可変サイズな (レスポンシブ、あるいはフレキシブルな)<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>スプライトの表示法を紹介します。</p> <h1>デモ</h1> <h3>元画像</h3> <p><img src="http://manaten.net/wp-content/uploads/2015/12/sprite.png" alt="レスポンシブなCSSスプライト" /></p> <p><a href="https://www.npmjs.com/package/gulp-spritesmith">gulp-spritesmith</a> で作成した適当なスプライトシートです。</p> <h3>スプライトの利用例</h3> <p>このページの幅を変えてみると、以下のスプライト画像のサイズが画面幅によって可変であることがわかると思います。 上からwidthを <code>70%</code> 、 <code>50%</code>、 <code>100%</code> で指定しています。</p> <div> <style> .sprite-demo__button1 { margin-bottom: 10px; width: 70%; white-space: nowrap; text-indent: 100%; overflow: hidden; font-size: 0; background-image: url("http://manaten.net/wp-content/uploads/2015/12/sprite.png"); background-size: 350% 387.5%; background-position: 0% 0%; } .sprite-demo__button1::after { content: ''; display: block; padding-top: 40%; } .sprite-demo__button2 { margin-bottom: 10px; width: 50%; white-space: nowrap; text-indent: 100%; overflow: hidden; font-size: 0; background-image: url("http://manaten.net/wp-content/uploads/2015/12/sprite.png"); background-size: 291.66666666666663% 310%; background-position: 44.565217391304344% 40.476190476190474%; } .sprite-demo__button2::after { content: ''; display: block; padding-top: 41.66666666666667%; } .sprite-demo__button3 { margin-bottom: 10px; width: 100%; max-width: 500px; white-space: nowrap; text-indent: 100%; overflow: hidden; font-size: 0; background-image: url("http://manaten.net/wp-content/uploads/2015/12/sprite.png"); background-size: 280% 258.33333333333337%; background-position: 100% 100%; } .sprite-demo__button3::after { content: ''; display: block; padding-top: 48%; } </style> <div class="sprite-demo__button1">ボタン1</div> <div class="sprite-demo__button2">ボタン2</div> <div class="sprite-demo__button3">ボタン3</div> </div> <p>利用例を含めたコードの全体は、 <a href="https://github.com/manaten/responsive-css-sprite-demo">manaten/responsive-css-sprite-demo</a> にあります。</p> <h1>コーディング</h1> <p><a href="https://www.npmjs.com/package/gulp-spritesmith">gulp-spritesmith</a> と <a href="http://stylus-lang.com/">stylus</a> を用いて記述した上記サンプルのコードは以下のようになっています。</p> <pre class="code" data-lang="" data-unlink>// spritesmithのスプライトを引数に、レスポンシブなスプライトを表示する関数 sprite-responsive($sprite) $sheet_w = $sprite[6] // スプライトシートの幅 $sheet_h = $sprite[7] // スプライトシートの高さ $sprite_w = $sprite[4] // スプライト画像の幅 $sprite_h = $sprite[5] // スプライト画像の高さ $offset_x = $sprite[0] // スプライト画像のシート上のx位置 $offset_y = $sprite[1] // スプライト画像のシート上のy位置 // テキストを隠す white-space nowrap text-indent 100% overflow hidden font-size 0 // 要素の幅によってスプライトの表示サイズを可変にする background-image url($sprite[8]) background-size ($sheet_w / $sprite_w * 100)% ($sheet_h / $sprite_h * 100)% background-position ($offset_x / ($sheet_w - $sprite_w) * 100)% ($offset_y / ($sheet_h - $sprite_h) * 100)% // 画像のアスペクト比固定 &amp;::after content &#39;&#39; display block padding-top ($sprite_h / $sprite_w * 100)% .button1 margin-bottom 10px width 70% sprite-responsive($sprite_button1) .button2 margin-bottom 10px width 50% sprite-responsive($sprite_button2) .button3 margin-bottom 10px width 100% max-width 500px sprite-responsive($sprite_button3)</pre> <p><code>sprite-responsive</code> がspritesmithで生成したスプライトの変数を受け取ってスタイルを設定する関数で、 button1~3はそれを適用し、さらにデモのために <code>width</code> を異なる値に設定しています。</p> <p><code>sprite-responsive</code> 関数は2つのテクニックの組み合わせになっていて、一つは<a class="keyword" href="http://d.hatena.ne.jp/keyword/css">css</a>スプライトを%で指定し、要素の幅によってサイズが可変になるようにするもの、 もう一つは要素の幅によって高さも可変にし、常に要素の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%B9%A5%DA%A5%AF%A5%C8%C8%E6">アスペクト比</a>が維持されるようにするものです。</p> <p>例えばボタン1は 実際はスタイル指定になります。</p> <pre class="code" data-lang="" data-unlink>.button1 { margin-bottom: 10px; width: 70%; white-space: nowrap; text-indent: 100%; overflow: hidden; font-size: 0; background-image: url(&#34;http://manaten.net/wp-content/uploads/2015/12/sprite.png&#34;); background-size: 350% 387.5%; background-position: 0% 0%; } .button1::after { content: &#39;&#39;; display: block; padding-top: 40%; }</pre> <p>以下で詳しく見ていきます。各指定について長い説明になってしまっているので、どうしてこういう指定になっているのか気にならない人は飛ばしてしまっても問題無いです。</p> <h1>要素のサイズによってスプライトの表示サイズを可変にする</h1> <pre class="code" data-lang="" data-unlink>background-image url($sprite[8]) background-size ($sheet_w / $sprite_w * 100)% ($sheet_h / $sprite_h * 100)% background-position ($offset_x / ($sheet_w - $sprite_w) * 100)% ($offset_y / ($sheet_h - $sprite_h) * 100)%</pre> <p>この部分がスプライトのサイズを可変にするものです。 二行目の <code>background-size</code> 、 三行目の <code>background-position</code> ともに、要素の大きさに対して表示する背景画像の大きさを可変にするために、%指定をしています。</p> <h2>backgroud-size の%指定</h2> <p><code>backgroud-size</code> を%で指定した場合、要素の幅・高さに対してどのくらいの大きさで表示するかという指定になります。 100%の場合、要素の幅・高さいっぱいになるように画像が拡大縮小されます。</p> <pre class="code lang-css" data-lang="css" data-unlink>background-size: 100% 100%; </pre> <div> <div style="border: solid 1px black; width: 160px; height: 100px; background-image: url('http://manaten.net/wp-content/uploads/2015/12/sprite.png'); background-size: 100% 100%;"></div> </div> <p>要素の幅によって可変にスプライト画像を表示するためには、要素の幅がいくつであっても目的のスプライト画像がピッタリのサイズになるような倍率を考えてあげる必要があります。</p> <p>例えば例のボタン1はスプライトの幅が80px、スプライトシートの画像全体の幅が280pxとなっています。 この時、要素に対してスプライトがピッタリ表示されるような%指定を考えます。 要素の幅が80pxのとき、100%指定で表示すると、画像は全体が80pxにピッタリ収まるように表示されるので、80/280倍のサイズで表示されます。 この逆数である <code>280 / 80 * 100%</code> 指定で表示すると、80pxの要素に対してピクセルが等倍で表示されることになり、要件が満たせます。</p> <pre class="code lang-css" data-lang="css" data-unlink>width: 80px; background-size: 350% <span class="synComment">/* 280/80*100 */</span> 100%; </pre> <div> <div style="border: solid 1px black; width: 80px; height: 100px; background-image: url('http://manaten.net/wp-content/uploads/2015/12/sprite.png'); background-size: 350% 100%;"></div> </div> <p>これは要素の幅に対する割合指定なので、要素の幅が変わってもぴったりに表示されます。</p> <pre class="code lang-css" data-lang="css" data-unlink>width: 160px; background-size: 350% <span class="synComment">/* 280/80*100 */</span> 100%; </pre> <div> <div style="border: solid 1px black; width: 160px; height: 100px; background-image: url('http://manaten.net/wp-content/uploads/2015/12/sprite.png'); background-size: 350% 100%;"></div> </div> <p>高さに対しても同じ考え方ができ、これをstylusの関数として一般化したのが</p> <pre class="code" data-lang="" data-unlink>background-size ($sheet_w / $sprite_w * 100)% ($sheet_h / $sprite_h * 100)%</pre> <p>です。</p> <pre class="code lang-css" data-lang="css" data-unlink>background-size: 350% <span class="synComment">/* 280/80*100 */</span> 387<span class="synSpecial">.</span>5% <span class="synComment">/* 124/32*100 */</span>; </pre> <div> <div style="border: solid 1px black; width: 160px; height: 100px; background-image: url('http://manaten.net/wp-content/uploads/2015/12/sprite.png'); background-size: 350% 387.5%;"></div> </div> <p>このように、要素に対してスプライトがぴったりに引き伸ばされます。</p> <h2>backgroud-position の%指定</h2> <p><code>background-size</code> の指定で要素いっぱいに左上のスプライトを引き伸ばして表示することはできましたが、これでは左上のスプライトしか表示できません。 他のスプライトを表示するには、通常の<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>スプライトと同じく、 <code>background-position</code> の指定で表示位置をずらしてあげる必要があります。 可変にするためにこちらも%で指定してやる必要があります。</p> <p><code>background-position</code> を%で指定した場合、 <code>top left</code> の状態を0% 0%、 <code>right bottom</code> の状態を <code>100% 100%</code> として、割合で位置が決まります。</p> <pre class="code lang-css" data-lang="css" data-unlink>background-position: 0% 0%; </pre> <div> <div style="border: solid 1px black; width: 160px; height: 100px; background-image: url('http://manaten.net/wp-content/uploads/2015/12/sprite.png'); background-position: 0% 0%; background-repeat: no-repeat;"></div> </div> <pre class="code lang-css" data-lang="css" data-unlink>background-position: 100% 100%; </pre> <div> <div style="border: solid 1px black; width: 160px; height: 100px; background-image: url('http://manaten.net/wp-content/uploads/2015/12/sprite.png'); background-position: 100% 100%; background-repeat: no-repeat;"></div> </div> <p>これをピクセル指定に換算すると、 <code>0% 0%</code> は <code>0px 0px</code> と同じ、 <code>100% 100%</code> は <code>(要素の幅 - 画像の幅)px (要素の高さ - 画像の高さ)px</code> となります。 これは、 -画像の幅px -画像の高さpx を指定するとちょうど要素の左上に画像の右下が来るので(ちょうど画像が非表示になる状態)、そこからさらに要素の幅・高さの分だけずらしてやることで 画像の右下を要素の右下に合わせてやることができるからです。 <code>background-position</code> を0から100の%指定した場合、この区間を線形に動くことになるため、</p> <pre class="code" data-lang="" data-unlink>ずらすpx数 = background-positionで指定する%/100 * (要素の幅or高さ - 画像の幅or高さ)px</pre> <p>が成り立ちます。</p> <p>さて、真ん中の赤色のボタン2を要素ぴったりに表示することを考えます。 <code>background-size</code> はスプライトの幅が96px、高さが40pxでスプライトシート全体の幅が280px、高さが124pxなので、 280 / 96 * 100 = 291.66666666666663%, 124 / 40 * 100 = 310% と指定します。</p> <pre class="code lang-css" data-lang="css" data-unlink>background-size: 291<span class="synSpecial">.</span>66666666666663% 310%; background-position: 0% 0%; </pre> <div> <div style="border: solid 1px black; width: 160px; height: 100px; background-image: url('http://manaten.net/wp-content/uploads/2015/12/sprite.png'); background-size: 291.66666666666663% 310%; background-position: 0% 0%;"></div> </div> <p>スプライトシート上では、ボタン2は82px、34pxの位置にあるため、 <code>background-size</code> 等倍の通常の<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>スプライトでは、 <code>background-position: -82px -34px</code> を指定します。もちろんこれをそのまま指定しても、望み通りの表示にはなりません。</p> <pre class="code lang-css" data-lang="css" data-unlink>background-size: 291<span class="synSpecial">.</span>66666666666663% 310%; background-position: -82px -34px </pre> <div> <div style="border: solid 1px black; width: 160px; height: 100px; background-image: url('http://manaten.net/wp-content/uploads/2015/12/sprite.png'); background-size: 291.66666666666663% 310%; background-position: -82px -34px;"></div> </div> <p>これは、スプライトシートのサイズは今、要素にピッタリのサイズから更に幅は2.91倍、高さは3.1倍になっているからです。 つまり、幅は 要素の幅 / スプライトシートの幅 * 2.91倍、高さは 要素の高さ / スプライトシートの高さ * 3.1倍です。 今、要素の幅は160px、高さは100pxを指定しています。 なので、スプライトシートの幅は 160 / 280 * 2.91倍 = 1.66倍、 高さは 100 / 124 * 3.1=2.5倍 になっているはずです。 <code>background-position</code> も同じ倍率をかけてあげれば、正しい位置に表示されます。</p> <pre class="code lang-css" data-lang="css" data-unlink>background-size: 291<span class="synSpecial">.</span>66666666666663% 310%; background-position: -136<span class="synSpecial">.</span>12px <span class="synComment">/* -82*1.66 */</span> -85px <span class="synComment">/* -34*2.5 */</span> </pre> <div> <div style="border: solid 1px black; width: 160px; height: 100px; background-image: url('http://manaten.net/wp-content/uploads/2015/12/sprite.png'); background-size: 291.66666666666663% 310%; background-position: -136.12px -85px;"></div> </div> <p>ぴったりに表示されました。 ただし、これだと <code>bakchround-position</code> の計算に要素のサイズが関わってしまい、汎用的に用いることができませんので、%で指定できるようにする必要があります。</p> <p>先ほどの等式</p> <pre class="code" data-lang="" data-unlink>ずらすpx数 = background-positionで指定するxの%/100 * (要素の幅or高さ - 画像の幅or高さ)px</pre> <p>に、 <code>現在の画像の幅=要素の幅/スプライトシートの幅*画像の幅*(スプライトシートの幅/スプライトの幅)</code> と <code>ずらすpx数=要素の幅/スプライトシートの幅*スプライトシート上のx座標*(スプライトシートの幅/スプライトの幅)</code> を代入します。</p> <pre class="code" data-lang="" data-unlink>要素の幅/スプライトシートの幅*スプライトシート上のx座標*(スプライトシートの幅/スプライトの幅) = background-positionで指定するxの% / 100 * (要素の幅 - 要素の幅/スプライトシートの幅*画像の幅*(スプライトシートの幅/スプライトの幅))px</pre> <p>このままだと複雑ですが、式を整理すると、</p> <pre class="code" data-lang="" data-unlink>background-positionで指定するxの% = 100 * スプライトシート上のx座標 / (スプライトの幅 - 画像の幅)</pre> <p>とすることができ、要素の幅を使わない%の値の計算方法を得ることができました。</p> <p>高さも同じく、</p> <pre class="code" data-lang="" data-unlink>background-positionで指定するyの% = 100 * スプライトシート上のy座標 / (スプライトの高さ - 画像の高さ)</pre> <p>です。ここから、 <code>background-position: 100 * 82 / (280 - 96) % 100 * 34 / (124 - 40) % ;</code> とします。</p> <pre class="code lang-css" data-lang="css" data-unlink>background-size: 291<span class="synSpecial">.</span>66666666666663% 310%; background-position: 44<span class="synSpecial">.</span>56% <span class="synComment">/* 100 * 82 / (280 - 96) % */</span> 40<span class="synSpecial">.</span>47% <span class="synComment">/* 100 * 34 / (124 - 40) % */</span> </pre> <div> <div style="border: solid 1px black; width: 160px; height: 100px; background-image: url('http://manaten.net/wp-content/uploads/2015/12/sprite.png'); background-size: 291.66666666666663% 310%; background-position: 44.56% 40.47%;"></div> </div> <p>%指定でぴったりに表示できました。要素の幅や高さを変えてもぴったりに表示できます。</p> <pre class="code lang-css" data-lang="css" data-unlink>width: 200px; height 70px background-size: 291<span class="synSpecial">.</span>66666666666663% 310%; background-position: 44<span class="synSpecial">.</span>56% <span class="synComment">/* 100 * 82 / (280 - 96) % */</span> 40<span class="synSpecial">.</span>47% <span class="synComment">/* 100 * 34 / (124 - 40) % */</span> </pre> <div> <div style="border: solid 1px black; width: 200px; height: 70px; background-image: url('http://manaten.net/wp-content/uploads/2015/12/sprite.png'); background-size: 291.66666666666663% 310%; background-position: 44.56% 40.47%;"></div> </div> <p>これをstylus関数として一般化すると、</p> <pre class="code lang-css" data-lang="css" data-unlink>background-position ($offset_x / ($sheet_w - $sprite_w) <span class="synStatement">*</span> 100)% ($offset_y / ($sheet_h - $sprite_h) <span class="synStatement">*</span> 100)% </pre> <p>となります。</p> <h2>参考</h2> <ul> <li><a href="http://stackoverflow.com/questions/21810262/responsive-sprites-percentages">css - responsive sprites / percentages - Stack Overflow</a></li> <li><a href="http://www.w3schools.com/cssref/css3_pr_background-size.asp">CSS3 background-size property</a></li> <li><a href="http://www.w3schools.com/cssref/pr_background-position.asp">CSS background-position property</a></li> </ul> <h1>要素の幅によって要素の高さを<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%B9%A5%DA%A5%AF%A5%C8%C8%E6">アスペクト比</a>を維持して可変にする</h1> <p>前節のスタイル指定で要素の幅高さにぴったりにスプライトを表示できるようになりました。 あとは、要素の幅が決まった時に要素の高さが元のスプライトと<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%B9%A5%DA%A5%AF%A5%C8%C8%E6">アスペクト比</a>が同じになれば、レスポンシブな<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>スプライトが実現できます。 <code>sprite-responsive</code> 関数の以下の指定が要素の幅によって要素の高さが元のスプライトと同じ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%B9%A5%DA%A5%AF%A5%C8%C8%E6">アスペクト比</a>になるようにしている箇所です。</p> <pre class="code" data-lang="" data-unlink> &amp;::after content &#39;&#39; display block padding-top ($sprite_h / $sprite_w * 100)%</pre> <p>子要素の <code>padding-top</code> は%指定した時、親要素の <code>width</code> に比例した値になります。 ブロック要素のafterの <code>padding-top</code> を%指定することで、要素の高さを作ってあげます。 要素の高さは、 <code>要素の幅 * スプライトの高さ / スプライトの幅</code> となれば<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%B9%A5%DA%A5%AF%A5%C8%C8%E6">アスペクト比</a>が一緒になるので、 <code>padding-top ($sprite_h / $sprite_w * 100)%</code> を指定します。</p> <p>先ほどのボタン2の場合は次のようになります。</p> <pre class="code lang-css" data-lang="css" data-unlink>width: 30%; background-size: 291<span class="synSpecial">.</span>66666666666663% 310%; background-position: 44<span class="synSpecial">.</span>56% 40<span class="synSpecial">.</span>47%; <span class="synIdentifier">.button2</span>::<span class="synPreProc">after</span> <span class="synIdentifier">{</span> <span class="synType">content</span>: <span class="synConstant">''</span>; <span class="synType">display</span>: <span class="synType">block</span>; <span class="synType">padding-top</span>: <span class="synConstant">41.66%</span>; <span class="synComment">/* 100 * 40 / 96 */</span> <span class="synIdentifier">}</span> </pre> <div> <div style="border: solid 1px black; width: 30%;" class="sprite-demo__button2"></div> </div> <p>幅指定だけでスプライト画像と同じ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%B9%A5%DA%A5%AF%A5%C8%C8%E6">アスペクト比</a>になりました。</p> <h2>参考</h2> <ul> <li><a href="http://stackoverflow.com/questions/12121090/responsively-change-div-size-keeping-aspect-ratio">html - Responsively change div size keeping aspect ratio - Stack Overflow</a></li> </ul> <h1>スプライトのパディングの指定</h1> <p>上記までのやり方で伸び縮みするスプライトは作れるのですが、これだけだと倍率によってはスプライトの隣のスプライトのピクセルが紛れ込んできてしまうことがあります。 これを防ぐために、gulp-spritesmithでのスプライト生成時にスプライト間のパディングを指定して透明ピクセルを挿入してあげています。</p> <p>今回gulpfileでのspritesmithの指定を以下のようにしました。 <code>padding: 2</code> がパディングの挿入となっています。</p> <pre class="code" data-lang="" data-unlink>gulp.task(&#39;build:sprite&#39;, () =&gt; { const spriteData = gulp.src(`${SRC_DIR}/sprites/**/*.png`) .pipe(spritesmith({ imgName : &#39;sprite.png&#39;, cssName : &#39;sprite.styl&#39;, imgPath : &#39;/assets/sprite.png&#39;, cssFormat: &#39;stylus&#39;, algorithm: &#39;diagonal&#39;, padding : 2, cssVarMap: sprite =&gt; { sprite.name = &#39;sprite-&#39; + sprite.name; } })) return mergeStream( spriteData.img.pipe(gulp.dest(`${BUILD_DIR}/assets`)), spriteData.css.pipe(gulp.dest(`${TMP_DIR}/css`)) ); });</pre> <p>gulpfileの全体は <a href="https://github.com/manaten/responsive-css-sprite-demo">github</a> にあります。</p> <h1>雑感</h1> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>のキモとなる記述自体は参考リンクの内容を読めばすぐ求められるのですが、あとから自分で見て理解できるように詳しく説明を書いたら長くなってしまいました。 特に <code>backgroud-position</code> の説明を簡潔に書くことができず・・・</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>スプライト自体http2の普及で必要性が薄くなりそうですし、レスポンシブな画像もこれからは<a class="keyword" href="http://d.hatena.ne.jp/keyword/SVG">SVG</a>がどんどん利用されていきそうな気配ではあります。 とはいえ、ラスタ画像のスプライトも当分利用されそうですし、それをレスポンシブに利用したいケースは昨今のWeb事情だとそこそこあるのではと思います。 そんな時に本エントリのの内容が参考になれば幸いです。</p> <h1>参考リンク</h1> <ul> <li><a href="https://github.com/manaten/responsive-css-sprite-demo">manaten/responsive-css-sprite-demo</a> <ul> <li>今回の内容のデモ用<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%DD%A5%B8%A5%C8%A5%EA">レポジトリ</a>。gulpfile、stylusファイルなど。</li> </ul> </li> <li><a href="http://stackoverflow.com/questions/12121090/responsively-change-div-size-keeping-aspect-ratio">html - Responsively change div size keeping aspect ratio - Stack Overflow</a></li> <li><a href="http://stackoverflow.com/questions/21810262/responsive-sprites-percentages">css - responsive sprites / percentages - Stack Overflow</a></li> <li><a href="https://www.npmjs.com/package/gulp-spritesmith">gulp-spritesmith</a></li> <li><a href="http://www.w3schools.com/cssref/css3_pr_background-size.asp">CSS3 background-size property</a></li> <li><a href="http://www.w3schools.com/cssref/pr_background-position.asp">CSS background-position property</a></li> </ul> manaten 部屋の照明を操作するかっこいいWebアプリを作った hatenablog://entry/6653812171396288606 2016-05-16T12:51:32+09:00 2016-05-25T01:15:40+09:00 GW中に上記画像のような見た目と操作感で部屋の証明をコントロールできるWebアプリを作ったので紹介します。 <p><img src="http://manaten.net/wp-content/uploads/2016/05/light_app.gif" alt="部屋の証明を操作するかっこいいWebアプリを作った" /></p> <p>GW中に上記画像のような見た目と操作感で部屋の証明をコントロールできるWebアプリを作ったので紹介します。</p> <h1>概要</h1> <p>ページを開くと上記アニメ<a class="keyword" href="http://d.hatena.ne.jp/keyword/gif">gif</a>のような部屋の間取りのUIが表示されます。 青いエリアは照明がOFFのエリア、緑のエリアは照明がONのエリアとなっています。 照明をON/OFFしたいエリアをクリックすると、そこのON/OFFが逆転します。また、全部ONにするボタンと全部OFFにするボタンもあります。</p> <p>今回部屋のすべての照明をコントロールできましたが、単に照明を操作するボタンを並べるだけだとどのボタンがどこに対応してるのか難しいため、このように部屋の間取りをUIとすることでわかりやすくしてみました。 また、最初はただ地図を<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AF%A5%EA%A5%C3%A5%AB%A5%D6%A5%EB">クリッカブル</a>にするだけのつもりだったのですが、それだとつまらないので無駄に近未来っぽいかっこいいデザインにしてみました。</p> <p>Webアプリは自宅内のサーバーで稼働していて、LAN内のPCや<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%DE%A5%DB">スマホ</a>から操作できるようになっています。</p> <h1>実装</h1> <p>実は今住んでる建物はオーナーが<a href="http://nefrock.com/">ITベンチャー企業</a>の社長で、その関係で借りてる部屋の照明操作のできるWeb <a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>が提供されています。 そのため今回実装したのはWeb UIと、家のLAN内から操作するための<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>をラップするサーバーアプリケーションだけです。 IoT的な内容を期待した方には申し訳ない。</p> <p>だいたい以下の様な技術を利用しました。</p> <ul> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/SVG">SVG</a> <ul> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AF%A5%EA%A5%C3%A5%AB%A5%D6%A5%EB">クリッカブル</a>なマップの実現に利用。かなり昔に本ブログでも触れました(<a href="http://blog.manaten.net/entry/797">JavaScriptで変な形のクリック領域を作るとき、svgが便利 - MANA-DOT</a>)。各エリアが<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AF%A5%EA%A5%C3%A5%AB%A5%D6%A5%EB">クリッカブル</a>なパスになっていて、ON/OFF状態によってクラスを付け替えています。</li> </ul> </li> <li><a href="https://facebook.github.io/react/">React</a> <ul> <li>最近は仕事でも使っています。現在の全照明の状態からパスごとのクラスを計算するという用途はReactとマッチしていました。</li> </ul> </li> <li>ES2015 + aync/await <ul> <li>最近<a class="keyword" href="http://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a>を書くときは趣味でも仕事でも、<a href="https://babeljs.io/">babel</a>を使ってES2015で記述しているのですが、今回はasync/awaitも利用してみました。サーバーサイド/クライアントサイドともに<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CA%A3%BF%F4%B2%F3">複数回</a>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>呼び出しをする構造なので、簡潔に記述できて便利でした。</li> </ul> </li> <li><a href="https://github.com/dokku/dokku">dokku</a> <ul> <li>自宅内Web<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%D7%A5%EA%A5%B1%A1%BC%A5%B7%A5%E7%A5%F3%A5%B5%A1%BC%A5%D0">アプリケーションサーバ</a>ーに利用。他にもhubotなどが動いています。個人製作のおもちゃみたいなアプリケーション動かすのに気軽に利用できて便利です。</li> </ul> </li> </ul> <h1>感想</h1> <p><img src="http://manaten.net/wp-content/uploads/2016/05/light_app_2.jpg" alt="スマホで開くといい感じ" /></p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%DE%A5%DB">スマホ</a>で開くと近未来デバイスっぽくてかっこいい? です。というか<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%DE%A5%DB">スマホ</a>から電気消せるの寝る時とかに普通に便利。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/SVG">SVG</a>+Reactは日本ではあまり実用例を見かけませんが、今回はパスごとにクラスを付け替えるだけの単純な用途ですが、可能性感じて面白いですね。 いずれもっと複雑で面白いことにも挑戦してみたい。パスうにょうにょ動かすとか。</p> manaten RPGツクールMVで作ったゲームをコマンドラインで動かしてみる hatenablog://entry/6653586347146364228 2015-11-25T00:30:42+09:00 2015-11-25T00:30:42+09:00 会社のLT用のネタで、RPGツクールMV で作ったゲームをCLI上で動かすということをやりました。 ニコナレ にLTで使ったスライドをアップしてあります。 <p><img src="http://manaten.net/wp-content/uploads/2015/11/rpgcli.gif" alt="RPGツクールMVで作ったゲームをコマンドラインで動かしてみる" /></p> <p>会社のLT用のネタで、<a href="https://tkool.jp/mv/">RPGツクールMV</a> で作ったゲームを<a class="keyword" href="http://d.hatena.ne.jp/keyword/CLI">CLI</a>上で動かすということをやりました。 <a href="http://niconare.nicovideo.jp/watch/kn696">ニコナレ</a> にLTで使ったスライドをアップしてあります。</p> <h1>実装</h1> <p>LT用のネタなのでそんなに真面目には実装していません。 スライドでも紹介していますが、</p> <ol> <li>各種Polyfillを用いて無理やり<a class="keyword" href="http://d.hatena.ne.jp/keyword/CLI">CLI</a>上で起動する</li> <li>描画を担当する関数だけ書き換えて、 <a href="https://github.com/chjj/blessed">chjj/blessed</a> による<a class="keyword" href="http://d.hatena.ne.jp/keyword/CLI">CLI</a>描画に置き換える</li> </ol> <p>という風にして<a class="keyword" href="http://d.hatena.ne.jp/keyword/CLI">CLI</a>上で <em>動くように</em> しました。</p> <p>diffは <a href="https://github.com/manaten/rpgclitest/compare/initial...master?w=">こんなかんじ</a> です。</p> <p>LTにあわせての突貫実装でいろいろ酷かったりしますが、ネタなので良しとします。</p> <h2>1. 各種Polyfillを用いて無理やり<a class="keyword" href="http://d.hatena.ne.jp/keyword/CLI">CLI</a>上で起動する</h2> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/RPG%A5%C4%A5%AF%A1%BC%A5%EB">RPGツクール</a>MVはPIXI.jsを用いて描画を行います。 当初はこいつをまるまる置き換えて<a class="keyword" href="http://d.hatena.ne.jp/keyword/CLI">CLI</a>に表示しようと思ったのですが、 <a class="keyword" href="http://d.hatena.ne.jp/keyword/RPG%A5%C4%A5%AF%A1%BC%A5%EB">RPGツクール</a>のプレイヤーが用いるSpriteやTileMapといったクラスがPIXI.jsのDisplayObjectを継承している上に ガッツリその性質を使ってたりでとても短い期間で切り離せる感じではなかったので(スプラトゥーンのやり過ぎで更に期間は短くなっていた) PIXI.jsごと<a class="keyword" href="http://d.hatena.ne.jp/keyword/CLI">CLI</a>で動かすことにしました。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/RPG%A5%C4%A5%AF%A1%BC%A5%EB">RPGツクール</a>MVのプレイヤーおよびPIXI.jsは大体ブラウザの以下の<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>に依存しているので、それぞれjs製のPolyfillを置いてあげます。</p> <ul> <li>DOM ・・・ <a href="https://github.com/tmpvar/jsdom">tmpvar/jsdom</a></li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/Canvas">Canvas</a> ・・・ <a href="https://github.com/Automattic/node-canvas">Automattic/node-canvas</a></li> <li>requestAnimationFrame ・・・ <a href="https://github.com/chrisdickinson/raf">chrisdickinson/raf</a></li> <li>localStorage ・・・ <a href="https://github.com/coolaj86/node-localStorage">coolaj86/node-localStorage</a></li> </ul> <p>また、<a class="keyword" href="http://d.hatena.ne.jp/keyword/XmlHttpRequest">XmlHttpRequest</a>を使ってる箇所が大きく分けて二種類あり、</p> <ol> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/json">json</a>形式で吐かれるゲームデータファイルの読み込み</li> <li>画像ファイルの読み込み</li> </ol> <p>2は<a class="keyword" href="http://d.hatena.ne.jp/keyword/CLI">CLI</a>で関係ないので放置し、1をrequireした後に読み込んだオブジェクトを <code>onLoad</code> に渡すように書き換えました。</p> <p>これでだいたい動く状態になり、あとは開始シーンをタイトルでなくマップに入れ替えたり、 <a class="keyword" href="http://d.hatena.ne.jp/keyword/canvas">canvas</a>のstyleをいじってる箇所などの細かい行を消したりして<a class="keyword" href="http://d.hatena.ne.jp/keyword/CLI">CLI</a>でエラーを出さずに起動するようになりました。</p> <p>一応、自動開始イベントに <code>console.log</code> だけさせて動作することも確認できました。</p> <h2>2. 描画を担当する関数を書き換えて、blessedによる<a class="keyword" href="http://d.hatena.ne.jp/keyword/CLI">CLI</a>描画に置き換える</h2> <p><a href="https://github.com/chjj/blessed">blessed</a> は前々から気になってたんですが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/CLI">CLI</a>アプリケーションを作るときに強力なライブラリで、 <a class="keyword" href="http://d.hatena.ne.jp/keyword/GUI">GUI</a>のパーツのようにリストやスクロー<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EB%A5%DC%A5%C3%A5%AF%A5%B9">ルボックス</a>が予め用意されています。 この内、box要素にゲームのタイルと同じ数の一文字のtext要素を詰め込み、ランタイムのPIXI.jsを使った<a class="keyword" href="http://d.hatena.ne.jp/keyword/Canvas">Canvas</a>への描画を行ってる箇所を書き換え、 このタイルへの描画に変えてやります。</p> <p>実際はそんな単純に書き換えられなかったので、結構無理やりですが、マップとキャラクターを表すクラスのinitialize<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E1%A5%BD%A5%C3%A5%C9">メソッド</a>でblessedの要素を作り、 update<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E1%A5%BD%A5%C3%A5%C9">メソッド</a>でその要素の情報を更新するように書き換えることでそれっぽく動くようにはできました。</p> <p>また、blessedは<a class="keyword" href="http://d.hatena.ne.jp/keyword/CLI">CLI</a>でのキー入力も扱うことができるので、ランタイムのDOM経由でキー入力をとってる箇所を書き換えて、blessed経由で入力を受け取るようにしました。</p> <p>これらの改修で冒頭の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A1%BC%A5%F3%A5%B7%A5%E7%A5%C3%A5%C8">スクリーンショット</a>のように、<a class="keyword" href="http://d.hatena.ne.jp/keyword/RPG%A5%C4%A5%AF%A1%BC%A5%EB">RPGツクール</a>で作ったゲームが<a class="keyword" href="http://d.hatena.ne.jp/keyword/CLI">CLI</a>で動くようになりました。</p> <p><img src="http://manaten.net/wp-content/uploads/2015/11/rpgcli.gif" alt="RPGツクールMVで作ったゲームをコマンドラインで動かしてみる" /></p> <h1>感想</h1> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/RPG%A5%C4%A5%AF%A1%BC%A5%EB">RPGツクール</a>が<a class="keyword" href="http://d.hatena.ne.jp/keyword/json">json</a>エディタと化し、ランタイムがjs+htmlとなったことでやれることが一気に増えたと感じます。 時間が経てばきっと<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B5%A1%BC%A5%C9%A5%D1%A1%BC%A5%C6%A5%A3">サードパーティ</a>のランタイムなども登場してきそう。 エディタ側も、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C6%A5%AD%A5%B9%A5%C8%A5%A8%A5%C7%A5%A3%A5%BF">テキストエディタ</a>を用いて高速に入力可能なものとか登場しそうな予感がします。</p> <p>標準のランタイムは全てグローバルに撒く設計で、ゲーム内イベントから<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>実行命令で呼ぶぶんには便利なのだろうと思います。 反面、今回のように手を入れるとなるとやや複雑だったり、npmのモジュールを使いたい場合に親和性も気になります(きっと凝ったことをしたくなったら使いたくなるはず)。 (とはいえ多くのツクールユーザーには無縁だという気もしつつ)</p> <p>今回はプレイヤー側を<a class="keyword" href="http://d.hatena.ne.jp/keyword/CLI">CLI</a>で実装するというネタですが、ツクールのプレイヤーのコードをなんとなく読めたし、 気になってたblessedも軽く触れたのでネタにしては実入りのあるものだったと思います。 実は画面外に描画が残るとか、会話ウィンドウ出すと固まるとかのバグが有りますが、多分これ以上いじらないでしょう。(ゲーム作るならドット絵のゲームを作りたいので(笑))</p> <p>ちなみに<a class="keyword" href="http://d.hatena.ne.jp/keyword/RPG%A5%C4%A5%AF%A1%BC%A5%EB">RPGツクール</a>MVは海外版であれば <a href="http://store.steampowered.com/app/363890/?l=japanese">Steam</a> からすでに購入可能です。</p> <h1>引用リンク</h1> <ul> <li><a href="https://tkool.jp/mv/">RPGツクールMV</a></li> <li><a href="http://store.steampowered.com/app/363890/?l=japanese">Steam:RPG Maker MV</a></li> <li><a href="https://github.com/chjj/blessed">chjj/blessed</a></li> <li><a href="https://github.com/tmpvar/jsdom">tmpvar/jsdom</a></li> <li><a href="https://github.com/Automattic/node-canvas">Automattic/node-canvas</a></li> <li><a href="https://github.com/chrisdickinson/raf">chrisdickinson/raf</a></li> <li><a href="https://github.com/coolaj86/node-localStorage">coolaj86/node-localStorage</a></li> </ul> manaten babelのasyncで遊んでみたメモ hatenablog://entry/6653458415127390247 2015-11-08T21:41:09+09:00 2015-11-08T21:43:54+09:00 ES7から利用可能な async/await は非同期プログラミングの際に非常に魅力的な構文です。 babelを用いることによりES5の環境でもコードを実行可能です。 babelで非同期処理がどのように変換されるのか興味があったので、いろいろ遊んでみました。 <p><img src="http://manaten.net/wp-content/uploads/2015/11/babel.png" alt="babelのasyncで遊んでみたメモ" /></p> <p>ES7から利用可能な <a href="https://tc39.github.io/ecmascript-asyncawait/">async/await</a> は非同期プログラミングの際に非常に魅力的な構文です。 babelを用いることによりES5の環境でもコードを実行可能です。</p> <p>babelで非同期処理がどのように変換されるのか興味があったので、いろいろ遊んでみました。</p> <h1>async function</h1> <p>簡単なPromiseを用いた非同期コードとして以下の様な例があります。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synIdentifier">function</span> wait(msec) <span class="synIdentifier">{</span> <span class="synStatement">return</span> <span class="synStatement">new</span> Promise((resolve) =&gt; setTimeout(resolve, msec)); <span class="synIdentifier">}</span> <span class="synIdentifier">function</span> main() <span class="synIdentifier">{</span> console.log(<span class="synConstant">'hoge'</span>); wait(2000).then(() =&gt; <span class="synIdentifier">{</span> console.log(<span class="synConstant">'fuga'</span>); <span class="synIdentifier">}</span>); <span class="synIdentifier">}</span> main(); </pre> <p>setTimeoutを用いた非同期なwait関数を呼ぶだけの例です。 async/awaitを用いると上のコードを次のように記述できます。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synIdentifier">function</span> wait(msec) <span class="synIdentifier">{</span> <span class="synStatement">return</span> <span class="synStatement">new</span> Promise((resolve) =&gt; setTimeout(resolve, msec)); <span class="synIdentifier">}</span> async <span class="synIdentifier">function</span> main() <span class="synIdentifier">{</span> console.log(<span class="synConstant">'hoge'</span>); await wait(2000); console.log(<span class="synConstant">'fuga'</span>); <span class="synIdentifier">}</span> main(); </pre> <p>ネストがなくなり、より人間に直感的な形で記述できるようになりました。</p> <h1>babelで変換してみる</h1> <p>babelを用いることで、上記async/awaitを利用したコードをES5の処理系で実行可能な形に変換できます。 babelは通常 <a href="https://www.npmjs.com/package/regenerator-babel">regenerator-babel</a> を用いて async/await を変換するようです。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synIdentifier">function</span> wait(msec) <span class="synIdentifier">{</span> <span class="synStatement">return</span> <span class="synStatement">new</span> Promise((resolve) =&gt; setTimeout(resolve, msec)); <span class="synIdentifier">}</span> async <span class="synIdentifier">function</span> main() <span class="synIdentifier">{</span> console.log(<span class="synConstant">'hoge'</span>); await wait(2000); console.log(<span class="synConstant">'fuga'</span>); <span class="synIdentifier">}</span> main(); </pre> <p>このコードは次のようになります。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synIdentifier">function</span> wait(msec) <span class="synIdentifier">{</span> <span class="synStatement">return</span> <span class="synStatement">new</span> Promise(<span class="synIdentifier">function</span> (resolve) <span class="synIdentifier">{</span> <span class="synStatement">return</span> setTimeout(resolve, msec); <span class="synIdentifier">}</span>); <span class="synIdentifier">}</span> <span class="synIdentifier">function</span> main() <span class="synIdentifier">{</span> <span class="synStatement">return</span> regeneratorRuntime.async(<span class="synIdentifier">function</span> main$(context$1$0) <span class="synIdentifier">{</span> <span class="synStatement">while</span> (1) <span class="synStatement">switch</span> (context$1$0.prev = context$1$0.next) <span class="synIdentifier">{</span> <span class="synStatement">case</span> 0: console.log(<span class="synConstant">'hoge'</span>); context$1$0.next = 3; <span class="synStatement">return</span> regeneratorRuntime.awrap(wait(2000)); <span class="synStatement">case</span> 3: console.log(<span class="synConstant">'fuga'</span>); <span class="synStatement">case</span> 4: <span class="synStatement">case</span> <span class="synConstant">'end'</span>: <span class="synStatement">return</span> context$1$0.stop(); <span class="synIdentifier">}</span> <span class="synIdentifier">}</span>, <span class="synStatement">null</span>, <span class="synIdentifier">this</span>); <span class="synIdentifier">}</span> main(); </pre> <p>regeneratorを用いたやや複雑なコードが吐き出されました。 awaitのタイミングで <code>main$</code> の実行が中断され、 <code>wait(2000)</code> が終了すると再度呼び出されるが、 <code>context$1$0.next</code> の値が変化してるため中断された箇所から実行されるのであろうことはなんとなく想像できます。</p> <h2>制御構文</h2> <p>上記のような単純なコードの変換は納得ができますが、if、whileなどの制御構文がどうなるかも気になります。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink>async <span class="synIdentifier">function</span> getWithRetry(retryCount) <span class="synIdentifier">{</span> <span class="synIdentifier">let</span> result = <span class="synConstant">false</span>; <span class="synStatement">for</span> (<span class="synIdentifier">let</span> i = 0; i &lt; retryCount; i++) <span class="synIdentifier">{</span> <span class="synStatement">try</span> <span class="synIdentifier">{</span> result = await getSomething(); <span class="synIdentifier">}</span> <span class="synStatement">catch</span>(e) <span class="synIdentifier">{}</span> <span class="synStatement">if</span> (result !== <span class="synConstant">false</span>) <span class="synIdentifier">{</span> <span class="synStatement">break</span>; <span class="synIdentifier">}</span> console.log(<span class="synConstant">'retry'</span>); <span class="synIdentifier">}</span> <span class="synStatement">if</span> (result) <span class="synIdentifier">{</span> <span class="synStatement">return</span> result; <span class="synIdentifier">}</span> <span class="synStatement">throw</span> <span class="synStatement">new</span> Error(<span class="synConstant">'fail!'</span>); <span class="synIdentifier">}</span> </pre> <p>例えば上記のような、取得できるまでリトライするコードを変換してみます。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synIdentifier">function</span> getWithRetry(retryCount) <span class="synIdentifier">{</span> <span class="synIdentifier">var</span> result, i; <span class="synStatement">return</span> regeneratorRuntime.async(<span class="synIdentifier">function</span> getWithRetry$(context$1$0) <span class="synIdentifier">{</span> <span class="synStatement">while</span> (1) <span class="synStatement">switch</span> (context$1$0.prev = context$1$0.next) <span class="synIdentifier">{</span> <span class="synStatement">case</span> 0: result = <span class="synConstant">false</span>; i = 0; <span class="synStatement">case</span> 2: <span class="synStatement">if</span> (!(i &lt; retryCount)) <span class="synIdentifier">{</span> context$1$0.next = 17; <span class="synStatement">break</span>; <span class="synIdentifier">}</span> context$1$0.prev = 3; context$1$0.next = 6; <span class="synStatement">return</span> regeneratorRuntime.awrap(getSomething()); <span class="synStatement">case</span> 6: result = context$1$0.sent; context$1$0.next = 11; <span class="synStatement">break</span>; <span class="synStatement">case</span> 9: context$1$0.prev = 9; context$1$0.t0 = context$1$0<span class="synIdentifier">[</span><span class="synConstant">'catch'</span><span class="synIdentifier">]</span>(3); <span class="synStatement">case</span> 11: <span class="synStatement">if</span> (!(result !== <span class="synConstant">false</span>)) <span class="synIdentifier">{</span> context$1$0.next = 13; <span class="synStatement">break</span>; <span class="synIdentifier">}</span> <span class="synStatement">return</span> context$1$0.abrupt(<span class="synConstant">'break'</span>, 17); <span class="synStatement">case</span> 13: console.log(<span class="synConstant">'retry'</span>); <span class="synStatement">case</span> 14: i++; context$1$0.next = 2; <span class="synStatement">break</span>; <span class="synStatement">case</span> 17: <span class="synStatement">if</span> (!result) <span class="synIdentifier">{</span> context$1$0.next = 19; <span class="synStatement">break</span>; <span class="synIdentifier">}</span> <span class="synStatement">return</span> context$1$0.abrupt(<span class="synConstant">'return'</span>, result); <span class="synStatement">case</span> 19: <span class="synStatement">throw</span> <span class="synStatement">new</span> Error(<span class="synConstant">'fail!'</span>); <span class="synStatement">case</span> 20: <span class="synStatement">case</span> <span class="synConstant">'end'</span>: <span class="synStatement">return</span> context$1$0.stop(); <span class="synIdentifier">}</span> <span class="synIdentifier">}</span>, <span class="synStatement">null</span>, <span class="synIdentifier">this</span>, <span class="synIdentifier">[[</span>3, 9<span class="synIdentifier">]]</span>); <span class="synIdentifier">}</span> </pre> <p>このコードを見ると、case説のそれぞれがラベルになってて <code>context$1$0.next</code> を指定して <code>break</code> するのが GOTO、 <code>context$1$0.next</code> に次の行を指定してPromise返すのがawaitなんだなとなんとなく理解できますね。 (breakで <code>switch</code> を抜けると直上に <code>while(1)</code> があるため、再度実行されて <code>context$1$0.next</code> で指定した箇所から再開される)</p> <p>気になるのが元のコードでtry-catchしてる箇所ですが、 <code>regeneratorRuntime.async</code> の第四引数が <code>tryLocsList</code> であり、そこに渡っている <code>[3,9]</code> が、 3番で例外が発生した場合のキャッチ節が9番であることを知らせているようで、 <code>getSomething</code> で例外が発生した場合は9番に入る模様ですね。</p> <p>case節を使ってラベル+GOTOを表現するのが面白いですね。</p> <h1>asyncToGenerator</h1> <p>ところで、<a href="https://babeljs.io/docs/advanced/transformers/other/async-to-generator/">asyncToGenerator</a> オプションを使用することで、regeneratorを使わずasync/awaitをES6 generatorを用いたコードを出力できるようです。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synIdentifier">function</span> wait(msec) <span class="synIdentifier">{</span> <span class="synStatement">return</span> <span class="synStatement">new</span> Promise((resolve) =&gt; setTimeout(resolve, msec)); <span class="synIdentifier">}</span> async <span class="synIdentifier">function</span> main() <span class="synIdentifier">{</span> console.log(<span class="synConstant">'hoge'</span>); await wait(2000); console.log(<span class="synConstant">'fuga'</span>); <span class="synIdentifier">}</span> main(); </pre> <p>このコードは次のように変換されます。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synIdentifier">var</span> main = _asyncToGenerator(<span class="synIdentifier">function</span>* () <span class="synIdentifier">{</span> console.log(<span class="synConstant">'hoge'</span>); yield wait(2000); console.log(<span class="synConstant">'fuga'</span>); <span class="synIdentifier">}</span>); <span class="synIdentifier">function</span> _asyncToGenerator(fn) <span class="synIdentifier">{</span> <span class="synStatement">return</span> <span class="synIdentifier">function</span> () <span class="synIdentifier">{</span> <span class="synIdentifier">var</span> gen = fn.apply(<span class="synIdentifier">this</span>, <span class="synIdentifier">arguments</span>); <span class="synStatement">return</span> <span class="synStatement">new</span> Promise(<span class="synIdentifier">function</span> (resolve, reject) <span class="synIdentifier">{</span> <span class="synIdentifier">var</span> callNext = step.bind(<span class="synStatement">null</span>, <span class="synConstant">'next'</span>); <span class="synIdentifier">var</span> callThrow = step.bind(<span class="synStatement">null</span>, <span class="synConstant">'throw'</span>); <span class="synIdentifier">function</span> step(key, arg) <span class="synIdentifier">{</span> <span class="synStatement">try</span> <span class="synIdentifier">{</span> <span class="synIdentifier">var</span> info = gen<span class="synIdentifier">[</span>key<span class="synIdentifier">]</span>(arg); <span class="synIdentifier">var</span> value = info.value; <span class="synIdentifier">}</span> <span class="synStatement">catch</span> (error) <span class="synIdentifier">{</span> reject(error); <span class="synStatement">return</span>; <span class="synIdentifier">}</span> <span class="synStatement">if</span> (info.done) <span class="synIdentifier">{</span> resolve(value); <span class="synIdentifier">}</span> <span class="synStatement">else</span> <span class="synIdentifier">{</span> Promise.resolve(value).then(callNext, callThrow); <span class="synIdentifier">}</span> <span class="synIdentifier">}</span> callNext(); <span class="synIdentifier">}</span>); <span class="synIdentifier">}</span>; <span class="synIdentifier">}</span> <span class="synIdentifier">function</span> wait(msec) <span class="synIdentifier">{</span> <span class="synStatement">return</span> <span class="synStatement">new</span> Promise(<span class="synIdentifier">function</span> (resolve) <span class="synIdentifier">{</span> <span class="synStatement">return</span> setTimeout(resolve, msec); <span class="synIdentifier">}</span>); <span class="synIdentifier">}</span> main(); </pre> <p><code>_asyncToGenerator</code> がやや複雑であるものの、main関数はregeneratorを用いた変換と比べ、かなり素直に変換されています。 この形は、<a href="https://tc39.github.io/ecmascript-asyncawait/#desugaring">Async Functions の Informative Desugaringの項</a>とほぼ同じですね。 <code>_asyncToGenerator</code> 関数は <code>spawn</code> 関数と形がやや違うもののやってることはほぼ同じです。 asyncToGeneratorオプションは上記desugaringの実装と言えそうです。</p> <h2>制御構文</h2> <pre class="code lang-javascript" data-lang="javascript" data-unlink>async <span class="synIdentifier">function</span> getWithRetry(retryCount) <span class="synIdentifier">{</span> <span class="synIdentifier">let</span> result = <span class="synConstant">false</span>; <span class="synStatement">for</span> (<span class="synIdentifier">let</span> i = 0; i &lt; retryCount; i++) <span class="synIdentifier">{</span> <span class="synStatement">try</span> <span class="synIdentifier">{</span> result = await getSomething(); <span class="synIdentifier">}</span> <span class="synStatement">catch</span>(e) <span class="synIdentifier">{}</span> <span class="synStatement">if</span> (result !== <span class="synConstant">false</span>) <span class="synIdentifier">{</span> <span class="synStatement">break</span>; <span class="synIdentifier">}</span> console.log(<span class="synConstant">'retry'</span>); <span class="synIdentifier">}</span> <span class="synStatement">if</span> (result) <span class="synIdentifier">{</span> <span class="synStatement">return</span> result; <span class="synIdentifier">}</span> <span class="synStatement">throw</span> <span class="synStatement">new</span> Error(<span class="synConstant">'fail!'</span>); <span class="synIdentifier">}</span> </pre> <p>先ほどの制御構文を用いたコードも変換してみます。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synIdentifier">var</span> getWithRetry = _asyncToGenerator(<span class="synIdentifier">function</span>* (retryCount) <span class="synIdentifier">{</span> <span class="synIdentifier">var</span> result = <span class="synConstant">false</span>; <span class="synStatement">for</span> (<span class="synIdentifier">var</span> i = 0; i &lt; retryCount; i++) <span class="synIdentifier">{</span> <span class="synStatement">try</span> <span class="synIdentifier">{</span> result = yield getSomething(); <span class="synIdentifier">}</span> <span class="synStatement">catch</span> (e) <span class="synIdentifier">{}</span> <span class="synStatement">if</span> (result !== <span class="synConstant">false</span>) <span class="synIdentifier">{</span> <span class="synStatement">break</span>; <span class="synIdentifier">}</span> console.log(<span class="synConstant">'retry'</span>); <span class="synIdentifier">}</span> <span class="synStatement">if</span> (result) <span class="synIdentifier">{</span> <span class="synStatement">return</span> result; <span class="synIdentifier">}</span> <span class="synStatement">throw</span> <span class="synStatement">new</span> Error(<span class="synConstant">'fail!'</span>); <span class="synIdentifier">}</span>); </pre> <p>こちらでも変わらず、素直に変換されました。</p> <h2>asyncToGenerator → regenerator</h2> <p>ちなみに、asyncToGeneratorで変換したコードをregeneratorで再変換すると次のようになります。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synIdentifier">var</span> main = _asyncToGenerator(regeneratorRuntime.mark(<span class="synIdentifier">function</span> callee$0$0() <span class="synIdentifier">{</span> <span class="synStatement">return</span> regeneratorRuntime.wrap(<span class="synIdentifier">function</span> callee$0$0$(context$1$0) <span class="synIdentifier">{</span> <span class="synStatement">while</span> (1) <span class="synStatement">switch</span> (context$1$0.prev = context$1$0.next) <span class="synIdentifier">{</span> <span class="synStatement">case</span> 0: console.log(<span class="synConstant">'hoge'</span>); context$1$0.next = 3; <span class="synStatement">return</span> wait(2000); <span class="synStatement">case</span> 3: console.log(<span class="synConstant">'fuga'</span>); <span class="synStatement">case</span> 4: <span class="synStatement">case</span> <span class="synConstant">'end'</span>: <span class="synStatement">return</span> context$1$0.stop(); <span class="synIdentifier">}</span> <span class="synIdentifier">}</span>, callee$0$0, <span class="synIdentifier">this</span>); <span class="synIdentifier">}</span>)); <span class="synIdentifier">function</span> _asyncToGenerator(fn) <span class="synIdentifier">{</span> <span class="synComment">/** 省略 */</span> <span class="synIdentifier">}</span> <span class="synIdentifier">function</span> wait(msec) <span class="synIdentifier">{</span> <span class="synStatement">return</span> <span class="synStatement">new</span> Promise(<span class="synIdentifier">function</span> (resolve) <span class="synIdentifier">{</span> <span class="synStatement">return</span> setTimeout(resolve, msec); <span class="synIdentifier">}</span>); <span class="synIdentifier">}</span> main(); </pre> <p>main関数の形が最初のregeneratorを用いた変換に近い形になりました。 <code>regeneratorRuntime.async</code> の <a href="https://github.com/rdy/regenerator-babel/blob/master/runtime.js#L96">実装</a> を見てみると、 <code>_asyncToGenerator</code> 相当の処理を <code>regeneratorRuntime.wrap</code> した関数に対して行っていることがわかり、変換→再変換したコードとだいたい一緒であることがわかります。面白いですね。</p> <h1>感想</h1> <p>regeneratorによる変換はbabelのほかのES6の変換と違い、もとの文脈をかなり破壊するのでちょっと怖いし、パフォーマンスも心配です。<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%D0%A5%C3%A5%B0">デバッグ</a>もやりづらそう。 遊びコードなら使うと楽しそうですけど、真面目コードで使うのは少し不安ですね。</p> <p>対してasyncToGeneratorによる変換はかなり素直な変換なので、 generatorを利用できる環境ならば、asyncToGeneratorを使うと結構安心して使えそうです。</p> <h1>参考リンク</h1> <ul> <li><a href="https://tc39.github.io/ecmascript-asyncawait/">Async Functions</a></li> <li><a href="https://babeljs.io/docs/advanced/transformers/other/regenerator/">regenerator · Babel</a></li> <li><a href="https://babeljs.io/docs/advanced/transformers/other/async-to-generator/">asyncToGenerator · Babel</a></li> </ul> manaten direnvを使って複数のgitコミッタ名を切り替える hatenablog://entry/8454420450095056381 2015-05-21T11:33:25+09:00 2015-05-21T11:33:25+09:00 例えば会社のPCでこっそり個人的なリポジトリで作業してgithubにpushする場合、 うっかり会社用のgitコミッタ名(本名@会社名.co.jp みたいなアドレスとか)で commit/pushしてしまい、紐付けるつもりのなかったネットの人格と本名/会社名が紐付いてしまう というのは皆が恐れるところであると思います。 そこで、direnv を利用するといい感じに切り替えられることができたので、共有いたします。 <p><img src="http://manaten.net/wp-content/uploads/2015/05/octcat.gif" alt="direnvを使って複数のgitコミッタ名を切り替える" /></p> <p>例えば会社のPCでこっそり個人的な<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>で作業して<a class="keyword" href="http://d.hatena.ne.jp/keyword/github">github</a>にpushする場合、 うっかり会社用のgitコミッタ名(本名@会社名.co.jp みたいなアドレスとか)で commit/pushしてしまい、紐付けるつもりのなかったネットの人格と本名/会社名が紐付いてしまう というのは皆が恐れるところであると思います。</p> <p>そこで、<a href="https://github.com/zimbatm/direnv">direnv</a> を利用するといい感じに切り替えられることができたので、共有いたします。</p> <h1>direnv</h1> <p><a href="https://github.com/zimbatm/direnv">zimbatm/direnv</a></p> <p>あるディレクトリに <code>.envrc</code> というファイルを置いておくと、そのディレクトリ以下に cd した時に <code>.envrc</code> の内容の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B4%C4%B6%AD%CA%D1%BF%F4">環境変数</a>が読み込まれるという代物です。</p> <p>例えば、</p> <pre class="code" data-lang="" data-unlink>- home ├ .envrc // export SOME_ENV=hogehoge ├ c └ a ├ .envrc // export SOME_ENV=fugafuga └ b</pre> <p>という構造の場合、 home直下やhome/cでは SOME_ENV=hogehogeとなり、 home/aやhome/a/bの下ではSOME_ENV=fugafugaとなります。</p> <h1>GIT <a class="keyword" href="http://d.hatena.ne.jp/keyword/%B4%C4%B6%AD%CA%D1%BF%F4">環境変数</a></h1> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%B4%C4%B6%AD%CA%D1%BF%F4">環境変数</a> <code>GIT_COMMITTER_NAME</code> 、<code>GIT_COMMITTER_EMAIL</code> 、<code>GIT_AUTHOR_NAME</code> 、<code>GIT_AUTHOR_EMAIL</code> を設定することで、.gitconfigで設定したコミッタ名を<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B4%C4%B6%AD%CA%D1%BF%F4">環境変数</a>で上書きすることができます。</p> <p><a href="http://git-scm.com/book/es/v2/Git-Internals-Environment-Variables#Committing">Git - Environment Variables</a></p> <blockquote><p>which uses these environment variables as its primary source of information, falling back to configuration values only if these aren’t present.</p></blockquote> <h1>運用</h1> <p>個人用の作業をするディレクトリを作り、以下の様な内容の .envrc を配置し、個人用の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>はそこ以下にのみcloneするようにします。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synStatement">export</span><span class="synIdentifier"> GIT_COMMITTER_NAME=</span><span class="synStatement">&quot;</span><span class="synConstant">manaten_kojin</span><span class="synStatement">&quot;</span> <span class="synStatement">export</span><span class="synIdentifier"> GIT_COMMITTER_EMAIL=</span><span class="synStatement">&quot;</span><span class="synConstant">kojin@manaten.net</span><span class="synStatement">&quot;</span> <span class="synStatement">export</span><span class="synIdentifier"> GIT_AUTHOR_NAME=</span><span class="synStatement">&quot;</span><span class="synConstant">manaten_kojin</span><span class="synStatement">&quot;</span> <span class="synStatement">export</span><span class="synIdentifier"> GIT_AUTHOR_EMAIL=</span><span class="synStatement">&quot;</span><span class="synConstant">kojin@manaten.net</span><span class="synStatement">&quot;</span> </pre> <p>こうすることで、個人用の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%DD%A5%B8%A5%C8%A5%EA">レポジトリ</a>では個人用のコミッタ名でcommitすることができます。</p> <p>こんな感じに動きます。</p> <p><img src="http://manaten.net/wp-content/uploads/2015/05/direnv_git.gif" alt="direnvを使って複数のgitコミッタ名を切り替える" /></p> <p>いい感じです。</p> <p>direnvは他にも応用効きそうですね。</p> <h1>参考リンク</h1> <ul> <li><a href="https://github.com/zimbatm/direnv">zimbatm/direnv</a></li> <li><a href="http://qiita.com/kompiro/items/5fc46089247a56243a62">direnvを使おう - Qiita</a></li> <li><a href="http://git-scm.com/book/es/v2/Git-Internals-Environment-Variables#Committing">Git - Environment Variables</a></li> </ul> manaten SublimeTextでgrep, sort, uniq, diff, sedっぽいテキスト操作 hatenablog://entry/8454420450091938917 2015-04-20T11:44:35+09:00 2015-04-20T11:44:35+09:00 SublimeTextでもコマンドラインで行うような、grep, sort, uniq, diff, sed といたコマンド相当の操作ができます。 ログファイルをSublimeTextで眺めてる時などに便利なのでご紹介。 <p><img src="http://manaten.net/wp-content/uploads/2015/04/sublime.gif" alt="SublimeTextでgrep, sort, uniq, diff, sedっぽいテキスト操作" /></p> <p>SublimeTextでも<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%DE%A5%F3%A5%C9%A5%E9%A5%A4%A5%F3">コマンドライン</a>で行うような、<a class="keyword" href="http://d.hatena.ne.jp/keyword/grep">grep</a>, sort, uniq, diff, <a class="keyword" href="http://d.hatena.ne.jp/keyword/sed">sed</a> といたコマンド相当の操作ができます。 ログファイルをSublimeTextで眺めてる時などに便利なのでご紹介。</p> <h1><a class="keyword" href="http://d.hatena.ne.jp/keyword/%C0%B5%B5%AC%C9%BD%B8%BD">正規表現</a>によるフィルタ</h1> <p><img src="http://manaten.net/wp-content/uploads/2015/04/sublime_filter.gif" alt="grep" /></p> <p><a href="https://github.com/davidpeckham/sublime-filterlines">FilterLines</a>というパッケージを入れることで、簡易<a class="keyword" href="http://d.hatena.ne.jp/keyword/grep">grep</a>のような<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C0%B5%B5%AC%C9%BD%B8%BD">正規表現</a>による行フィルタを行うことができます。 例では値段が3桁の行のみにフィルタしています。 <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%AF%A5%BB%A5%B9%A5%ED%A5%B0">アクセスログ</a>で日付や<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%D5%A5%A1%A5%E9">リファラ</a>などで絞るときに便利です。</p> <h1>行のソート</h1> <p><img src="http://manaten.net/wp-content/uploads/2015/04/sublime_sort.gif" alt="sort" /></p> <p>ソートはSublimeTextが<a href="http://www.sublimetext.com/docs/commands">標準機能として</a>持っています。 単純に行をソートすることしかできないため、<a href="http://itpro.nikkeibp.co.jp/article/COLUMN/20060227/230887/">sortコマンド</a>には劣ってしまいますが、閲覧中のファイルをその場でsortしたくなる時がまれにあるので、知っていると便利な機能です。</p> <h1>重複する行の削除</h1> <p><img src="http://manaten.net/wp-content/uploads/2015/04/sublime_uniq.gif" alt="uniq" /></p> <p>こちらも<a href="http://www.sublimetext.com/docs/commands">標準機能のpermuteLinesのメソッドとして</a>利用できます。他にもshuffleやreverseなども利用できるようですが、僕個人ではuniqueが圧倒的に利用シチュエーションが多いです。 置換をした後の重複除去を手軽に行えます。</p> <h1>ファイルの行比較</h1> <p><img src="http://manaten.net/wp-content/uploads/2015/04/sublime_diff.gif" alt="diff" /></p> <p><a href="http://www.sublimerge.com/">Sublimerge</a>というパッケージの一機能として利用できます。 他にもいろいろ機能があるようですが、僕はdiff<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C4%A1%BC%A5%EB">ツール</a>として主に利用しています。 開いてるタブと別のタブのdiff、開いてるタブと<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AF%A5%EA%A5%C3%A5%D7%A5%DC%A1%BC%A5%C9">クリップボード</a>のdiffを行うことができ、 わざわざファイルに保存する必要がなく、新規タブに貼り付ければ比較ができるため、簡単な比較を行いたい場合は<a href="http://itpro.nikkeibp.co.jp/article/COLUMN/20060228/231144/">diffコマンド</a>よりも使い勝手が良いです。</p> <h1>置換</h1> <p><img src="http://manaten.net/wp-content/uploads/2015/04/sublime_sed.gif" alt="sed" /></p> <p>SublimeTextの標準機能として、Ctrl+Hで置換バーを表示し、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C0%B5%B5%AC%C9%BD%B8%BD">正規表現</a>による置換も行えます。 これはSublimeTextに限らず、ほとんどの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C6%A5%AD%A5%B9%A5%C8%A5%A8%A5%C7%A5%A3%A5%BF">テキストエディタ</a>で利用可能な機能ですが、 <a class="keyword" href="http://d.hatena.ne.jp/keyword/%C0%B5%B5%AC%C9%BD%B8%BD">正規表現</a>にある程度慣れて、マッチした箇所を参照した置換を使えるようになると 使い勝手が一気に向上し、図のようなスペース区切りの列の除去などもでき頻繁に利用します。</p> <h1>まとめ</h1> <p>再利用性・オプションによるカスタマイズ性では<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%DE%A5%F3%A5%C9%A5%E9%A5%A4%A5%F3">コマンドライン</a>に軍配が上がりますが、 ファイル保存の必要がなく、エディタを眺めながら気軽に操作できるという点でSublimeTextが優っています。 SublimeTextは10万行程度のファイルであれば問題なく軽快に動作するため、なんだかよくわからないエラーログの調査などでいろいろ試してみたいときに知っていると便利です。</p> <p>最近<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C6%A5%AD%A5%B9%A5%C8%A5%A8%A5%C7%A5%A3%A5%BF">テキストエディタ</a>界隈だと<a href="https://atom.io/">Atom</a>の勢いが強く感じ、 JS好きな僕も何度か乗り換えようかと思ったのですが、 デカめのファイルを開いた時の軽快さはSublimeTextのほうが圧倒的で、 この記事で紹介した機能も相まってデフォルトのエディタはまだしばらくSublimeTextなのかなと思っている次第です。</p> manaten