入力の選択肢が多すぎて手動では網羅しきれない場合、Bot を使うと大幅な省力 化/最適化が期待できます。
Bot による最適化の簡単な流れ:
本来はきちんと逆アセンブル解析を行った方が効率的なんだろうと思いますが、 ゲームによってはそこまで詳しく解析しなくても何とかなります。ただし、終了判定 だけは最低限必要なので、そのためのメモリアドレスくらいは調べておく必要があり ます。
どうしても終了判定ができない場合は、最後の手段としてランダム入力のみを行 う似非 Bot を作成し、判定は目視で行うという手があります。これでも全て手動で やるよりはマシです。
BasicBot は低機能ですが手軽に使えます。ただし多少複雑な入力が必要な場合に は力不足。FCEU 0.98.26 で動作確認(FCEU 0.98.28 ではなぜか External Input が グレーアウトしているため動作しない。FCEU 0.98.27 は未確認)。
FCEU バイナリにドキュメント(bot.html)が同梱されていますが、わかりにくいの で補足しておきます。
まず、左上のボックスで各ボタンの入力確率を設定。値は 0~1000 で、大きいほ ど入力確率が上がります(0 なら入力なし、1000 なら押しっぱなし)。
次に終了条件(End when)の設定。ここは frame 変数や mem() 関数を使うことが
多いと思います。たとえば、50 フレーム後に終了するなら frame=50
,
メモリアドレス $025B の値が 0 以外になったときに終了するなら mem(x25B)
!= 0
のようにします。
最後に最大化したい値(Maximize)の設定。たとえば、メモリアドレス $025B の値
を最大にしたいなら mem(x25B)
とします。なお、値を最小化したい場
合は負符号を付ければOK。Tie break 項目で Maximize 値が同点だった場合の判定基
準を追加できるので、たとえば -frames
を設定しておけば同じ
Maximize 値で最短の入力が得られることになります(frames 変数は経過フレーム数
を表す)。
望ましい解が得られたら、"Play Best" でその入力を再生し、手動で ステートセーブすればOK。
ただし、BasicBot は論理演算子がない?(代替手段はあるが面倒)のと、演算子の 優先順位が怪しい(加算が乗算より先に処理されたりする)など、あまり完成度が高く ないので、これ一本だとちょっと苦しいと思います。
Lua スクリプトを使うとかなり柔軟な処理が可能になります。Lua 言語自体もそ れなりに完成されているので、BasicBot などに比べると記述性ははるかに高いです。 Lua スクリプト機能は FCEU 0.98.27 以降で使えます。
簡単な例を示しておきます:
-- 現在時刻で乱数を初期化 math.randomseed(os.time()) -- 描画は必要ないので最高速モードにする FCEU.speedmode("maximum") -- やり直し用ステートの作成 local state = savestate.create() savestate.save(state) -- 1000 回試行 for i = 1, 1000 do attempt() -- 解が求まったら 1 番にステートセーブ if success() then local state_good = savestate.create(1) savestate.save(state_good) break end -- 解が見つからなかったのでステートロードして再試行 savestate.load(state) end
attempt()
内では joypad.set()
,
FCEU.frameadvance()
などを使って入力を生成しつつフレームを進め
ます。success()
は終了判定で、memory.readbyte()
な
どを使って判定を行います。あまり複雑なことをしない Bot ならこの例の変形で作
成できると思います。
ただし、逆アセンブル解析を行わない場合、入力生成部
(attempt()
)をコーディングする前に手動での事前調査(コマ送りして
フレーム数を計算)が必要になるので、Bot を使う場面が多い場合は少々しんどいと
思います。逆アセンブルするまでもなく毎回同じ入力ルーチンが使い回せるとわかっ
ている場合は別ですが…。
FCEU は、特定フレームを終端として Bot を回すといきなりエミュレータの動作
が停止(画面が崩れ、memory.readbyte()
が必ず 0 を返すようになる)
したり、原因不明のリセットがかかったりするようです(FCEU 0.98.28, 0.98.26 で、
BasicBot/LuaBot 双方で確認。サウンドが鳴っている場面の特定フレームで繰り返し
ステートロードすると発生しやすい?)。この現象が起こった場合は、
savestate.load()
の前に数回 FCEU.frameadvance()
し
てやると改善されると思います。