Google Gemini と「100回遊べるRPG」製作(後編)
100回遊べるRPGを目指してコツコツ作ってきたTiny Joypad用RPG。
しかしゲーム容量8KBという巨大な壁を前に、最初からやりなおすことにしました。
まず、アルゴリズムを見直します。
難しい課題はGeminiと言えど一発で最適解を見つけられません。
だから何回もチャットルームを作り直し、同じ質問を何回もGeminiに投げかけプログラムさせます。
その後、こちらの思い通りのプログラムであるか検証します。
これも複数のチャットルームで尋ねます。
ちなみにプログラムを作らせたチャットルームで、そのプログラムを検証させてはいけません。
Geminiは自分のプログラムは優秀であると認識し、まともに検証せずドヤ顔で問題ないと返してくることがあります。
鉄則5「コードの見直しは、複数のチャットルームでAIに確認させる」
この鉄則の通り複数のチャットルームで検証しましょう。
元のチャットルームでは見落としていた初歩的な間違いをアッサリ見つけてくれます。
検証が終わったら実機に実装します。
そしてコンパイル後のサイズが十分小さいか確認しましょう。
こればかりはGeminiに頼めません。
Geminiが提案できるのは「一般的にサイズが小さくなると思われるコード」まで。
実際にサイズが小さくなるかは、コンパイラがどう変換するかで変わります。
小さくなると思われたコードも、実装してみると逆に大きくなることがあります。
これを何度も何度も繰り返してやっと出来上がったのが以下の迷路生成アルゴリズムです。
uint8_t sramTileMap[11][17]; // SRAM上のマップデータ (17x11タイル = 187バイト)
uint8_t TILES_W,TILES_H; // マップのサイズ(タイル単位)
// 迷路掘り下げ関数 (sx, sy: 掘り下げ開始タイル座標(奇数))
void dig(uint8_t sx, uint8_t sy) {
// スタックサイズを迷路の通路数に合わせることで、完璧な迷路を生成
#define MAZE_STACK_SIZE ((TILES_W + 1) / 2) * ((TILES_H + 1) / 2)
uint8_t stack_x[MAZE_STACK_SIZE], stack_y[MAZE_STACK_SIZE];
uint8_t sp = 0;
// 開始点をスタックにプッシュし通路にする
stack_x[sp] = sx;
stack_y[sp] = sy;
sp++;
sramTileMap[sy][sx] = 0;
int8_t dirs[4][2] = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}};
while (sp > 0) {
// 現在地を取り出す
sp--;
uint8_t x = stack_x[sp];
uint8_t y = stack_y[sp];
// 方向をランダムにシャッフルする
uint8_t dir_indices[4] = {0, 1, 2, 3};
for (uint8_t i = 3; i > 0; --i) {
uint8_t j = random(i + 1);
uint8_t temp_idx = dir_indices[i];
dir_indices[i] = dir_indices[j];
dir_indices[j] = temp_idx;
}
// 各方向への掘り下げを試す
for (uint8_t k = 0; k < 4; k++) {
uint8_t dir_idx = dir_indices[k];
int8_t dx = dirs[dir_idx][0];
int8_t dy = dirs[dir_idx][1];
// 2マス先の座標
uint8_t nx = x + dx * 2;
uint8_t ny = y + dy * 2;
// 2マス先がマップ内で、かつ壁なら掘り進める
if (nx > 0 && nx < TILES_W - 1 && ny > 0 && ny < TILES_H - 1 && sramTileMap[ny][nx] == 1) {
sramTileMap[y + dy][x + dx] = 0; // 間の壁を通路にする
sramTileMap[ny][nx] = 0; // 2マス先を通路にする
stack_x[sp] = nx; // 2マス先をスタックにプッシュ
stack_y[sp] = ny;
sp++;
}
}
}
#undef MAZE_STACK_SIZE
}ご提示いただいた迷路生成アルゴリズムのコードについて、要件を満たしているか確認しました。
1、完全な迷路を生成できるか?
はい、生成できます。
このコードは、**「掘り下げ法(Iterative Backtracking)」**というアルゴリズムに基づいています。このアルゴリズムは、すべてのセルが通路になり、すべての行き止まりが一つしかない通路につながることを保証するため、行き止まりのない完全な迷路を生成できます。
2、生成の度に異なる迷路を生成できるか?
はい、生成できます。
以下の理由により、生成ごとに異なる迷路になります。
・random()関数の使用: 方向をシャッフルする際にrandom()関数が使われています。これにより、迷路を掘り進める方向が毎回ランダムに決定されます。
・spスタックポインタの管理: whileループ内でスタックから取り出されたセルの隣接セルをランダムに選び、スタックにプッシュします。このランダムな選択プロセスによって、迷路の形状が毎回変わります。
3、Attinyの仕様内で無理なく稼働できるか?
はい、無理なく稼働できる可能性が高いです。
・SRAM (512B) の制約 ご提示のコードには、マップデータ用のsramTileMap[11][17](187バイト)と、スタック用のstack_x/stack_y(合計で約100バイト)が使われています。これらを合計しても約300バイト程度であり、SRAMの512バイトの範囲内に収まっています。
・Flash (8KB) の制約 コード量は比較的少なく、ライブラリも使用していません。そのため、Flashメモリ8KBの制約も無理なく満たせるでしょう。
4、コード量が少なく、スケッチサイズが小さいか?
はい、小さいと言えます。
このコードは、ライブラリに依存せず、必要な機能だけをコンパクトに実装しています。特に、再帰呼び出しを避けてスタックを明示的に管理することで、オーバーヘッドを減らし、メモリ使用量も予測しやすい形になっています。これにより、コードの肥大化を防ぎ、スケッチサイズを小さく保っています。
結論として、ご提示いただいたコードは要件をすべて満たしている優れたアルゴリズムです。
こういった手順を、全プログラム対して行います。
ものすごーーーく大変で、気の遠くなる作業です。
あ、ちなみにファミコン時代でやってたような文字とか画像とかの削減はその前段階でさっさと完了させています。
プログラムの容量削減に比べれば簡単なので。
例えば階段に乗ると「NEXT?」と表示され、次の階に進むか確認されますが、普通に作るとこれだけで1文字8バイト x 5文字 = 40バイトです。
しかし上記のようにドット打ちすれば8バイト x 3文字分 = 24バイト。
16バイトも削減できます。
敵も前向き用アニメーションだけ。
なのでカニ歩きでも違和感のないカニが敵として登場します!
ちなみに死んだらDIE(8バイト)、ゲームクリア時はFin(8バイト)のみ。
GAME OVERとかGAME CLEARとか容量使いすぎ(文字数多すぎ)なので出しません。
看板となるゲームタイトルはTinyRoGですが、これも8 x 7 = 56バイトではなく24バイト、なんと32バイトも削減です。
ちなみに本当はTinyRogueにしたかったのですが容量がもったいないのでueは諦めました。
と、いう訳で、前回完成した部分まで容量削減のための創意工夫を凝らしながらガンガン作りこんでいきましょう。
まずはサクッとタイトル画面からプレイヤーが迷宮を探索するところまで作ります。もちろんアルゴリズムや使う関数、画像は全て容量削減しながらです。
鍵を取ってから降りることにします。
テキストメッセージを出せるようにします。
敵を表示し、自分も敵も攻撃できるようにします。
階層が深くなるにつれ迷宮サイズも大きくし、難易度も上がるようにします。
前回のような通路だけの迷路では敵を避けることができないので、壁を削り逃げ道も作ります。
よーし、前回+αの内容が実装できたぞ。
前回はこの前段階で既にスケッチサイズが94%だったけど今回は……
最大7616バイトのフラッシュメモリのうち、スケッチが6844バイト!
これだけ実装して7KBを切ってる!
すごい!
まだ772バイトもある!
これだけあれば、まだ色々実装できる!
ちなみに772バイトという容量、普通のに考えればゴミです。
現代のプログラマーは無駄だらけの 余裕を持ったプログラミングをするので こんな容量では何一つ実装できません。
しかし夏が始まって以来、既に1か月ほどスケッチサイズ削減に悩み続けている私からすれば772バイトの空き領域は広大です。
東京ドーム何個分かしらと思うほどです。自由研究とはいえ苦行すぎるわ。
よし、あと少しだ!
この広大(な気がするだけで実際は極小)な空き領域で、できるかぎりゲームが面白くなるための実装をするぞ!
まず敵の増加は必須だろう。
カニとかカニとかカニとか実装して動きや行動パターンも変えて面白味を出そう!
(実際はカニ以外も実装しています)
HPとダメージ計算も必要だろう。
というかコレないとゲームとして成り立たないし。
アイテムは……アアッー!!
4バイト!
4バイト削減できそうな場所は……
いや待てよ、ここをこうすればもしかして8バイトくらいの削減は……
おっ!いいぞ!
というかこれなら効果音も実装……アアッー!!
助けてGemini!
もっと……もっと削減……容量削減を……
え、ここ?ここの表現を変えるだけで?
おお!2バイト削減できたぞ!
え、ここ?ここの表現も変えるの?
いやいや多分それはダメだろ一応やってみるけど……ほら8バイトも増えた。
あ、でもその理論ならコッチを変えればもしかして……おお!逆に8バイト減った!
って言ってたら不具合だっ!
もう不具合を直す空き容量すら……アカン!しかももう締め切りや!
夏休みの自由研究提出日や……仕方ないこの部分は削って不具合は直して……
という訳で最終バージョンはこちら。
画像を押すとシミュレーターで遊べます。
(ただし動作遅いです実機の方が100倍快適です)
Tiny joypad実機用のソースコードはコチラから。
シミュレーターの100倍は快適に遊べます。
よーし!パッケージとかストーリーも作っちゃうぞ。
ゲームタイトル:TinyRoG
ストーリー:伝説の財宝が眠るという幻の洞窟。
古き言い伝えではこう詠われている。
~ 入る度に姿を変える深淵。スライム、カニ、異界の化物、宙に浮かぶ船、そこでは多くの脅威が待ち受ける。しかしそれを乗り越え見事30階に達した時、全てが手に入るであろう ~
そしてまた、新たな冒険者が深淵の口に……
なにこれカッコE!
さすがGeminiさんセンスありすぎですわ。
こんなんパッケージ買いしちゃいますわ。
ちなみに自分でいうのもアレですが、このゲーム結構遊べます。
とても8KBとは思えないほど奥が深い!(自画自賛)
というか今のところ製作者である私すらゲームクリアできていません!
渾身のエンディング(8バイトのFinの文字が出るだけ)に到達できません!
いやー渾身のエンディングなのに残念だなー。(棒読み)
とにかく……
(多分)100回遊べるRPGの完成です!
いやはや何とか終わりましたが、令和のこの時代に僅か数バイトと毎日戦い続ける夏を過ごすなんて思ってもみなかったですよ。
今年の夏の思い出コレしかないですよ。
でもさすがはGeminiです。
Geminiは予想外の提案することが度々ありました。
何度も助けられました。
でもその10倍以上、誤ったプログラムを提案してきました。「ここに変数を入れるとloopを気にせず値を保持できるよ」と言ってloopスコープ内に変数ぶっこんで来た時には「こいつ暑さで頭ヤラれたんか」と思いました。しかしそれ以上に驚いたのはGeminiと遊んでいるうちに自分のスキルがメキメキ上がっているのを感じられたことでした。
後半はGeminiが誤った提案をすると、実装前に「多分コレはダメだろうな」と分かるようになり、更にそこからヒントを得て別の実装を思い付いたり、Geminiが見つけられなかった不具合を見つけたりできました。
8KBでローグ系RPGを作るなんて完全に馬鹿の所業ですが、馬鹿やっているウチに賢くなった感じです。
もっとも、どれだけこのスキルを伸ばしても
現代の世の中では全く役に立たないんですけどね。
とにかく、一生遊べるゲーム機というのはこういうものです。
前人未到の世界だろうが、不可能と言われる領域だろうが、Geminiと一緒に遊んでいるうちになんとかなってしまう。
世間では異世界転生してチートで無双するアニメが流行ってたりしますが、チートは異世界転生しなくてもできるんですよ。


コメント