この記事は ひとりアドベントカレンダーRosettaCodeで楽しむプログラミング Advent Calendar 2025の7日めの記事です。
昔懐かしな、80x25くらいのテキスト画面をBASICで操っていた頃のゲームを作れ、というお題。
79文字22行のマスがフィールド。
ランダムに 1 から 9 の数字で満たされている。
プレイヤーは @ で表示される。
8方向に移動することで、なるべく多くの数字を消すのが目的。
ある方向に移動するとき、その方向の隣接マスの数字がNとして、N歩移動し、通過したマスの数字は「食べられる」。
スコアは食べた数字の個数。
食べられて空白になったマスに侵入したり通過したりすることはできない。フィールドから外れることもできない。
移動できなくなったらゲームオーバー。
何で作る?
Haskell + Curses で作っても疲れるだけでしょこれ。
いっそN-BASICが一番ふさわしい気がするけど、実行環境がないのでは話にならない。
あるいはファミリーベーシックV3とか、プチコン4とか…
完成品を誰でも触れるようにするという意味では、JavaScriptでWebベースにするのがよさそう。
Phaserとか色々あるけど、どれがいいのかな…
手許にPyxelの本があるけど、今から始めて完成までどれだけかかる?
昔取った杵柄なら enchant.js だけど、discon だし。
この程度の内容ならゲームエンジンなしでも行ける気はするけど、面倒そうだ。
面倒なことはAIにやらせよう
VSCode に入れてはみたけど使っていない Gemini Code Assist の出番じゃないこれ?
ということで、やってみました。
与えたプロンプトを順に。
- 固定幅フォントで、横79文字縦22行を表示する領域をもつHTMLファイルを作って。その領域は枠で囲って。
- JavaScriptを使って、この領域の任意の位置に任意の1文字を表示したい。 あらかじめ空白文字で満たしておいて、対応する位置を書きかえるというやり方が考えられる。別のアイデアはある?
- span要素を79x22個並べる方が自由度が高い、と提案されたのでそのまま了承した。
- 「ゲームスタート」ボタンを追加して。押されると、領域を1から9の数字でランダムに見たし、中央にプレイヤーを表す'@'を表示。
- 「満たし」の誤字も華麗にスルー。
- スコア表示欄を追加。初期値は0
- 操作は、'rogue'と同じ方向キー8文字で指示します。まず、そのようなガイド表示を領域の下に追加して
- これらのキーを押すと@がその方向に移動します。ただし、隣接するマスに書かれている数字のステップすうだけ一気に進んでしまいます。@が通り越えたマスは空白になります。@が進んだ歩数をスコアに足し込んでください。領域の外にはみ出たり、数字のない空白のマスに入るような移動はできないので、その場合は操作を無視してください。
- 空白文字のある位置を通過するような動きも禁止してください
- どうもありがとう。これはレトロゲーム Greed の完璧な復刻版です。
本当にこれだけで意図通りのものが出来てしまうとは、すごい時代になったもんだ…
できちゃった。
実際やってみたら横79文字は広すぎてダレる。
各方向に移動したらどこまで進むかをガイド表示するとかすると今風か。
(リンクされているYouTubeで既にそうなっていた。)
1から9の数字を、抵抗のカラーコードとかにしたら「実用的」かもと思ったけどやめた。
全角文字を使って、行単位で半角だけずらして、8方向じゃなくて6方向の動きにしたらどうだろう?
スパロボのグリッドじゃなくてボードゲームの伝統的なヘックスになる。
このお題なら7行テトリス的なものでも書かないと、Rosetta Codeの方に書き込みしにくいね。
楽しいことは人間がやろう
7行テトリスのコード、よく見たら読めるね。
ゲームが動いている間はY()関数がsetTimeout()を使って定期的に自分で起動して、落とすなどの動作を行う。
プレイヤーの操作に関しては onKeyDown= が巧妙で、 K=event.keyCode-38 で何をするかというと大域変数にキーコードの情報を書き込むだけで、操作によって関数が起動されるという一般的な仕組みではない。
定期的に動く Y() がこの大域変数を読んで、操作があったならそのように状態を変える。これはすごい。
ネットで検索すると、落ち物を回転させるロジックが巧妙すぎて理解不能らしいけど、Greedの実装には無関係なのでそこはスルーしておく。
あとは諸々、コードを詰め込むためのテクニックを拾い集めて…
<body onKeyDown=Y(event.key)><pre id=D><script>W=40;H=22;O=(c)=>{f=f[x](0
,p)+c+f[x](p+1)};V=()=>{O('@');D.innerHTML=(f+s).match(r).join("<BR>")};r
=RegExp(`.{1,${W}}`,'g');M=()=>{O(' ');p+=q;V(s++);--m&&setTimeout(M,200)
};E=(i)=>0|f[x](i,i+1);for(f="",i=0;i++<=H*W;)f+=(i<=W||W*H-W<=i||i%W<2)?
' ':0|(1+Math.random()*9);x="substring";p=W*(H+1)>>1;V(m=s=0);d={h:-1,j:W
,k:-W,l:1,y:-W-1,u:1-W,b:W-1,n:W+1};Y=(y)=>{if(!m&&y in d){q=d[y];if(k=E(
i=p+q)){for(;;i+=q){if(!E(i))return;if(i==p+k*q)break}M(m=k)}}}</script>
73文字×7行ー1文字になったので、規定まで43文字の余裕があります。
人間のプログラミングとAIのプログラミングの違いがこれで明確になりましたね。