マス目に建っていた建物を、座標と向きを持つものに作り替えた
AIの育て方が改修前と1棟も違わなかったとき、やっと安心した。
日本進化シミュレーション第17回 / 全17回目次
このゲームの建物は、ずっとマス目の上に立っていた。
1200×1365のマス目があって、1マスにつき建物は1つ。
置く場所はマスの中央に固定で、向きも変えられない。
「枡ではなく自由に配置できませんか」と言われたとき、正直そんなに難しくないだろうと思った。
座標を丸めるのをやめればいいだけに見えたからだ。
建物はマスの上にあるのではなく、マスそのものだった
コードを開いて気づいた。
このゲームの建物には、実体と呼べるものが存在していなかった。
あるのは、マスの数だけ用意された細長い配列が7本。
「そのマスの種類」「そのマスの建物の用途」「そのマスの見た目のばらつき」といった具合に、建物の情報がマスごとにバラバラに分解されて入っている。
建物という一つのまとまりは、コードのどこにも無かった。
だから座標を持たせるというのは、実体を持たない何かに座標を持たせるということで、つまり設計を作り直すことだった。
怖かったのは、街を育てるAIのほうだった
このゲームには、放っておいても街を育てるAIが入っている。
66キロバイトある。
中では「どこに建てられるか」を5通りの方法で探し、「建てられたかどうか」を16か所で判定している。
その判定が全部、同じ一行だった。
そのマスの種類が「建物」になっているか。
建物をマスから切り離すということは、この判定を全部書き換えるということでもある。
ここが壊れると、遊んでいて分かりやすいバグにはならない。
ただ街が育たなくなるだけだ。
画面は普通に動いて、なんとなく発展が遅い気がする、という形でしか表に出てこない。
いちばん見つけにくい壊れ方だと思った。
マスを捨てずに、マスを「影」にした
そこで決めたのは、建物の一覧を新しく作って、そちらを正しい情報とし、マス目の側は残すという方針だった。
建物の一覧から「このマスは埋まっている」という印をマスに押していく。
押した結果として、そのマスの種類は「建物」になる。
つまりマス目は、もう情報の持ち主ではなく、建物一覧から自動的に焼き直される影のようなものになる。
こうすると、AIの側は何も変わらない。
相変わらず「そのマスの種類は建物か」と訊いていて、相変わらず正しい答えが返ってくる。
ただ、その答えの出どころが変わっただけだ。
もともとの計画では、7本の配列を全部やめて、参照している135か所を書き換えるつもりだった。
やめた理由は単純で、書き込んでいる場所を数えてみたら20か所しかなかったからだ。
読むところが135、書くところが20。
なら書くほうだけ一本化して、読むほうは1行も触らないほうがいい。
危なかったのは、判定を「中心で」やろうとしたところ
建物が場所を取る範囲は、円で表すことにした。
四角にしなかったのは、回転させたときに当たり判定が変わってしまうからだ。
置けたのに回したら置けなくなる、という理不尽が起きようがない形にしておきたかった。
その円がどのマスにかかっているかを調べる部分で、最初は「マスの中心が円の中に入っているか」で判定しようとした。
これは間違いだった。
マスの角ちょうどに建物を置いた場合を考えてみてほしい。
そこから一番近いマスの中心までの距離は0.707マス分ある。
建物の半径は0.45くらいだから、届かない。
どのマスにも印が押されない。
印が押されない建物は、AIからも道路からも見えない。
画面にはちゃんと建物が立っているのに、システムから見ると何も無い場所になる。
幽霊みたいな建物ができるところだった。
正しくは「円とマスの四角が重なっているか」で判定する。
当たり前のことなのだけれど、実際に置いてみるまで気づけたか自信がない。
ここはテストを書いて、どんな座標に置いても必ず1マス以上に印が押されることを固定した。
火事の発生率が、66倍になるところだった
設計を詰めている段階で、もう一つ危ないものが見つかった。
このゲームの火災は、陸地22万マスの中から4000マスをランダムに選んで、その中に燃えやすい建物があれば低確率で火が出る、という作りになっている。
建物が3000棟なら、選んだ4000マスのうち建物に当たるのは1.5%くらい。
残りは空き地や森で、そこは何も起きずに素通りする。
建物の一覧ができたのだから、そこから直接選べば速い。
そう思って書き換えかけて、手が止まった。
一覧から選べば命中率は100%になる。
1.5%が100%になるということは、火事の起きる回数が66倍になるということだ。
速くしたつもりで、街が燃え続けるゲームに作り変えるところだった。
結局ここは書き換えなかった。
マス目を影として残す設計にしたおかげで、従来の抽選がそのまま動く。
代わりに、既に燃えている建物を回す部分だけを一覧に切り替えた。
こちらは確率に関係しないので、安全に速くなる。
直したのは、自分が壊した分だけではなかった
作業していると、前からあったバグにも行き当たる。
火事で焼けた家の住民データが消えていなかった。
家を壊す経路は「撤去」「火災」「災害」の3つあって、それぞれの場所で住民データの削除を書いていた。
火災のところだけ、書き忘れていた。
今回は建物を消す処理を1か所にまとめたので、どの経路から消えても必ず通るようになった。
セーブデータのほうにも、静かな爆弾が仕掛かっていた。
セーブの形式にはバージョン番号が振ってあって、3になっている。
そのコメントには「マス目を600から900に細かくしたので互換を切った」と書いてある。
ところが今のコードのマス目は1200だった。
つまり、バージョンを上げずにマス目を変えた形跡がある。
この状態で古いセーブを読むと、エラーは出ない。
マスの数が違う配列をそのまま流し込むので、街が別の場所に散らばるか、途中までしか復元されないかのどちらかになる。
しかも何も言わずに、だ。
今回のバージョンから、マス目の細かさを一緒に保存して必ず突き合わせるようにした。
向きは、道のほうを向かせた
回転を入れるにあたって、建物のモデルを全部読み直した。
扉、窓、鳥居、時計、暖簾。
どれも同じ側に付いていた。
正面の向きは最初から揃っていて、ただ誰も回していなかっただけだった。
例外は2つある。
港は桟橋のある側が正面で、これは海を向いていないと意味がない。
駅はホームの上屋が横に付いているので、線路の側を向かせる必要がある。
この2つだけ特別扱いにした。
あとは「一番近い道に正面を向ける」を既定にした。
プレイヤーが建てる建物も、AIが建てる建物も、ゾーンから自然に育った建物も、全部この判定を通る。
おかげで、曲がった道沿いの家並みが道なりに揃うようになった。
揃いすぎるとロボットが並んでいるように見えるので、ほんの少しだけ角度をばらつかせてある。
いちばん時間を溶かしたのは、デバッグの手つきだった
配置プレビューを作ったとき、どうやってもゴーストが出なかった。
ブラウザのコンソールから、モジュールを読み込んで関数を直接呼んでみる。
エラーは出ない。
なのに画面には何も出ない。
シーンの中身を数えても、何も増えていない。
30分ほど追いかけて、ようやく分かった。
コンソールから読み込んだモジュールが、ゲーム本体が使っているモジュールと別物だったのだ。
同じファイルなのに、実体が2つあった。
だから「シーンに追加した」のは本物のシーンではなく、コピーのほうのシーンだった。
試しに追加した内部状態を覗いてみたら、こちらのシーンには子要素が50個、コンソールから見えるシーンには32個。
数が違うことで、やっと二重読み込みに気づいた。
ゴーストは最初から正しく動いていた。
壊れていたのは僕の確認方法のほうだった。
それ以来、実機の確認はブラウザの操作か、ゲーム本体に仕込んである検証用の関数からしか触らないことにしている。
数字が同じになったときが、いちばん安心した
改修に入る前に、AIに200手ぶん街を育てさせて、建物が何棟増えるかを測っておいた。
37棟、34棟、9棟。
累計で81棟、人口464人。
作り替えたあと、同じことをやった。
37棟、34棟、9棟。
累計81棟、人口464人。
設計を全部入れ替えて、建物が座標と向きを持つようになって、それでもAIの育て方はぴったり同じだった。
こういうときの数字は、どんなテストより信用できる気がする。
ほかにも書いています
note・スタンプ※noteは運営者が個人で書いているものです。
