フロントエンド開発者的週刊アスキーへの道(ならず)
あらかじめおことわり
🙇割と週刊アスキーを持ち上げるような内容となっていますが私は週刊アスキーとの関係は一切ございません。
週刊アスキー、唯一のプログラマー向け連載
「週刊アスキー」と言えば、自作PCを始め、どちらかというとハードウェアについての話題が多いイメージがある、かも知れません。私自身そう思っていたのですが、実は何年間も「JavaScriptの部屋」という連載が続いています(リンク先の記事一覧は最新の日付が2017年になっていますが、本誌ではずっと続いています。かつてはウェブサイトと本誌両方で掲載していたのでしょうか)。「JavaScriptの部屋」では、HTMLファイル一つに収まる程度の大きさの、それでもなかなか面白くて役に立ちそうなプログラムを、毎週紹介してくれます。
「昔のベーマガかよ!」なんてツッコミを待っているかのごとく、ソースコード全体をそのまま紙面に載せてくれているので、「写経してJavaScriptの素振りをしたい!」なんて学習者にぴったりです:
ただし、ReactなりVueなりと言った、いわゆる「UIフレームワーク」と呼ばれそうなライブラリーは一切使ってくれないので、その辺を学習したい方には不向きです。TensorFlow.jsとかThree.jsとかもう少し用途が特化されたライブラリーはしばしば使っていますし、ReactやVueだってやろうと思えば単一のHTMLの中で使うこともできるはずですから、使っても良さそうな気はしますが[1]。
「JavaScriptの部屋」への道
そんな「JavaScriptの部屋」は、かつて読者からプログラムの投稿を募集していました。ASCII倶楽部に登録して間もない頃、バックナンバーを読み漁ってたまたま募集の知らせを目にした私は、これは面白そうだとネタは考えてみることにしました。
もう削除されてしまっているので私の記憶頼りなのですが、バックナンバーに書かれていたURLにある、詳細な募集要項はこんな感じでした:
- 外部のライブラリーは極力使用しないこと
- HTML・CSS・JavaScriptすべて合わせてなるべく200行以内に収めること
- などなど!
現代的なJavaScriptでの開発に慣れていると、一つ目の要件はなかなか厳しいことがわかるでしょう。もちろんTypeScriptは使えませんし、前の節でも触れましたがReactやVueなんてもってのほかです。
そして、実際に書いてみると二つ目の要件が最大の障壁になりました。タグの省略も使わずコメント付きで200行以内に収めつつ、しかもコードゴルフみたいな高度な技も使わず普通に読めて、それでいて楽しく便利なプログラムを毎週掲載できる、作者の松下浩則さんには脱帽です。
残念ながら、私がASCII倶楽部に登録した時点ですでに募集は終了してました。張り切って作り始めたもののなかなかうまく行かず、悶々としていた頃それに気づいた私は、止むなく投稿を諦め、ある程度の完成度に達したところで公開して終えよう、ということにしました。この記事はそのためのものです。
それでも、週刊アスキー編集部を唸らせるクオリティーのプログラムが作れれば、編集部に直接メールして持ち込めるかも、なんてことまで考えましたが、まぁそれはもっともっとよいネタが思いついたら改めて考えましょう...。そろそろ他のプログラムに集中したいですし。
そんなこんなでできたもの
と、いうわけで作ったのがこちらの「ナビつき!つくってわかる はじめてゲームプログラミング」風の何かです!
ソースコードやプログラムの例はこちら: https://github.com/igrep/vertons
タイトルのとおり、任天堂の「ナビつき! つくってわかる はじめてゲームプログラミング」をブラウザー上で真似たものです。開発し始めたのはこのゲームが発表されて間もない頃で、話題になるであろう時期に出せば編集部のウケもいいかも!なんて動機で取り組みました。元々こういうツールへの関心があったのも大きいですね。
当然、すべてを一つのHTMLファイルで作ると200行の制限に引っかかることが予想されたので、当初は、ノードを配置したりエッジを繋げたりする処理をWeb Componentsのライブラリーとして出し[2]、実際に入力されたノードやエッジを評価する部分を記事にしてもらおう、という作戦で挑みました。Web Componentsを使ってオリジナルのHTMLタグとして提供することで、より目新しくするという狙いもあったのです。
しかし完成した当該の処理のコアにあたる.ts
ファイルを見るとわかるとおり、(後で余分な機能を付け加えたとは言え)それだけですでに200行を超えており、そのほかに必要な処理やHTML・CSSも含めれば、どうあがいても200行には収まらなさそうでした😞。
それでも、ノードやエッジをぐにょぐにょ動かす処理だったりトポロジカルソートだったりを自前で実装できて勉強になりましたし、どうにか、辛うじて元ネタのように振る舞うものができて良かったです。次の節で紹介するとおり元ネタには遠く及ばない使いづらさですが...😨。
🐞既知の(もう直さない)問題
先ほど触れた通り、個人的にはそろそろ他の作りたいものに集中したいので、これから述べる問題を含め、申し訳なくもバグを報告いただいても対応しない予定です(Pull requestはものによってはマージするかも知れません)。
以下はご利用の前に目を通していただくと、戸惑うリスクが少なくなるでしょう。
ジャックからエッジを引く
エッジ、すなわちノードからノードに情報を伝える為に引く線は、「情報の出口」を表す「プラグ」からしか引くことができません。「情報の入り口」を表す「ジャック」からは引けませんがご容赦ください。
上記の「NOT」という名前のノードで言うところの、「入力」と書かれてへこんでいる箇所が「ジャック」で、「出力」と書かれて出っ張っているところが「プラグ」です。
エッジをジャックに繋げようとしてもうまく行かないことが多い
見出しの通りです。時折、連続してエッジを繋げるのに失敗することがあるのでなんとか直したかったのですが、いざ詳細を見ようとすると再現できず、止むなく諦めました。どなたか知見をくださると助かります!
ノードがスクロール領域から外れると、エッジの描画される位置がおかしい
特に小さめのディスプレイをご利用の方はご注意ください。座標系を一部間違えているようで、下記のように、ウィンドウの大きさがノードを配置するスペースより小さく、スクロールバーが表示されているとき、スクロールしないと見えない領域にあるノードからエッジを引こうとすると、エッジがおかしな位置に描画されてしまいます。他のノードに繋げると正しい位置に移動します。
例:
エッジがクリックしにくい
エッジやノードをクリックしてからマウスボタンを放すと(pointerup
すると)、削除ボタンが表示されるようになっています。
例:
こちらの灰色に白抜きで×マークが描かれたボタンを押すと、対象のエッジやノードが削除されるのですが、エッジにおけるクリックできる領域があまり大きくないため、エッジの削除が少しやりにくくなっています。
ループのあるグラフ
配置したノードやエッジを評価して実行する際、プラグやジャックの値を正しく評価するため、トポロジカルソートをします(そうしないとカウンターノードの結果を正しく伝達できない場合がある)。このトポロジカルソートは、原則としてループのあるグラフには使えないことが知られています。そのため、プラグ・ジャック間の依存関係にループがある場合はエラーを出すようにしています。
元ネタがループをどのように処理していたか、そもそもループのあるグラフを受理していたかは忘れてしまいましたが、ループがあった場合にどのように処理するべきか、ちょっとイメージしきれなかったので今回は諦めました。スプレッドシートなどでも同様の問題があるはずで、ループを検出した場合は評価を指定した回数で打ち切る、といった措置を執っていたように思います。今回も願わくば「1回で打ち切る」ようにすればよいのでしょうが、残念ながら実現方法がわかりませんでした😞。
Web Componentsの使い方が多分おかしい
※これはユーザビリティーに関係がないですが一応。
先ほど、「ノードを配置したりエッジを繋げたりする処理をWeb Componentsのライブラリー」として開発した、と申しました。しかし残念ながら、私のWeb Componentsへの理解不足と手間を惜しんだことにより、あまりWeb Componentsらしく使えるようにはなっていません。
理想的には、例えば次のように<verton-garage>
要素の下に<verton-vertex>
やら<verton-edge>
みたいな要素をユーザーが置くことで使えるようにしたいですが、実際にはこういう使い方はできず、VertonGarage
クラスのaddVertex
メソッドやstartDrawingEdge
メソッドを呼ぶことでしか、ノードやエッジの追加ができません。
<verton-garage>
<verton-vertex id="elapsed" label="時間の経過">
<verton-vertex id="dog" label="🐶"/>
<verton-edge from="elapsed/value" to="dog/x" />
</verton-garage>
APIを経由した利用しか認めていない分、その方がノードやエッジが満たすべき制約を強制しやすくはあるのですが、宣言的なAPIでないため他のフレームワークと組み合わせて使うのが難しいでしょう。
その他、元ネタよりも劣るもろもろ
当然キリがないので全部は挙げませんが、一番大きなものは物理エンジンがないことでしょうか。物理エンジンは愚か、一から物理エンジンを作る場合に必要となるであろう、平方根や三角関数なども使えないので、これだけで元ネタのようにゲームを作るのはほぼ無理かと思われます。
それでなくても、こんな簡単な例ですぐにコードがスパゲッティ化しちゃいますし(実際元ネタも、これほどではないとは言え結構スパゲッティ化しやすいですよね!)、もしゲームらしいゲームが作れたら教えてください!
おまけ: その前にちょっと作ったもの
実はこのアプリケーションを作る前にも、「JavaScriptの部屋」での採用を目指して作っていたものが一つありました。ついでなんでそれもこの場で供養します。
こちらです: https://the.igreque.info/works/fruchterman-reingold.html
これは、「Fruchterman-Reingoldのアルゴリズム」として知られる、グラフにあるノードを綺麗に配置するアルゴリズムを、配置を終えるまでのアニメーション付きで実装したアプリケーションです(参考, 元ネタ):
ノードは最初ランダムに配置されていますが、「きれいに配置する」というボタンを押すとグルグル~っと回るように動きながら中心に集まり、その後距離を空けると共に、きれいな多角形を描いたような位置に配置されます。最初に引力が強めに働いて、その後斥力が強く働いているみたいですね。
... と、こんな感じで作ったのは良かったのですが、アニメーションをさせない、などどんなに簡略化してもやはり200行に収まらなかったことに加え、動きが予想していたよりも単調で、一度使えばほぼ飽きてしまうようなものになってしまったからです。途中、実装がうまく行かず何日も悩んでteratailに質問したりしてなんとか仕上げたものだっただけに、残念でした😩(ご回答いただいたayanamizutaさん、改めてありがとうございました!)。
🙇最後に
週刊アスキーの「JavaScriptの部屋」はとてもよい連載だと思うので、JavaScriptのお勉強ネタを探している方は是非やってみてください!フレームワークでよりよく書けるか試してみるのもいいでしょう。過去の分をまとめて読みたい方にはASCII倶楽部もおすすめです!
それから、こんな素敵な企画を続けてくださっている作者の松下浩則さんや、編集部を始めとする関係者のみなさん、ならびに、私が「JavaScriptの部屋」と出会うきっかけを与えてくださったラジオ番組、『春野杏・依田菜津の「期待しないでください…。」』関係者のみなさん、ありがとうございました!
Discussion