子ども向けのボードゲームアプリに、強さを選べる相手を入れています。「つよい」をどのくらい強くするかは、前に一度決めたつもりでした。この記事では、その判断が間違っていたと気づき、4000局ずつの自動対戦で測り直すまでを書きます。
作りたかったもの
作っているのは、iOS のボードゲームアプリです。ふつうのまるばつのほかに、「きえるまるばつ」という遊び方があります。どちらも、対戦するCPUの強さを選んで遊べるようにしています。この記事では、遊び方そのものの違いには触れず、強さの決め方だけを書きます。
「つよい」を選んだときのCPUは、先読みで手を決めています。先読みとは、自分が打ったあと相手がどう打ち返すかを、決めた手数だけ順にたどって、いちばん損をしない手を選ぶやり方です。たどる手数のことを、ここでは「深さ」と呼びます。深さが大きいほど先まで読むので、CPUは強くなります。
困っていたのは、その深さの値でした。「きえるまるばつ」の「つよい」には深さ3が使われていたのですが、なぜ3なのかが、コードのどこにも書かれていませんでした。遊び方を選ぶところから、数字がそのまま渡されているだけです。これだと、あとから見直そうとしても、何を基準に決めた値なのか分かりません。
そこで、深さを変えたときに何が変わるかを実際に測り、採用する値と、その根拠をコードのコメントに残すことにしました。
AIへの頼み方
作業は Claude Code に頼みました。私が決めたのは「何を基準に強さを決めるか」で、測定と実装は Claude Code にさせています。
先に、終わったと言える条件を1行で書いて渡しました。「深さを変えた場合の勝率と思考時間を測り、採用値と根拠がコードのコメントに残っている」という条件です。条件を先に決めておくと、数字が出たところで満足して終わらずに、コメントを残すところまで進みます。
測り方も先に決めました。深さの候補ごとに自動で対戦させて勝率を出し、そのあとで1手にかかる時間を確かめる、という順です。強さが足りていても時間がかかりすぎるなら使えないので、2つとも見る必要がありました。
深さを決めるまでの流れを図にします。
flowchart TD A["深さの候補(2〜6)"] --> B["自動対戦で勝率を測る"] B --> C["1手あたりの思考時間を測る"] C --> D["ふつうのまるばつの『つよい』と比べる"] D --> E["採用する深さと理由をコメントに残す"]
つまずいたところ
最初につまずいたのは、私自身が前に出した結論でした。以前、この深さは6にすべきだと判断していたのです。今回、それが誤りだったと分かり、取り消しました。
根拠にしていたのは、「深さ6は深さ3に87%勝つ」という数字です。深く読むほうが強いのだから、いちばん強い値を選べばいい、と考えていました。
けれども、この87%はCPU同士を戦わせた結果です。プログラムどうしがどちらも最善に近い手を打ち合ったときの差であって、子どもがこのアプリで遊んだときの手ごたえではありません。人は毎回最善の手を選びませんし、選べる場面でも見落とします。CPU同士で測った数字を、そのまま人の体験に当てはめていたのが間違いでした。
もう1つのつまずきは、最初に書いた深さ3のほうです。値が直接書かれていたせいで、当時どう考えて3にしたのかをたどれませんでした。誤りだったと気づけたのは、今回あらためて測ったからで、記録が残っていたからではありません。
どう判断したか
基準を「CPU同士でどちらが強いか」から、「人が遊んだときに、どのくらい勝てるか」に置き換えました。
そのために、遊ぶ人の代わりに打つプログラムを用意しました。勝ち手と止め手だけが分かる打ち手です。つまり、自分が勝てる手があれば打ち、CPUが次に勝ちそうなら止める。それ以上は先を読みません。この人代わりのプログラムと「つよい」を、深さごとに各4000局ずつ戦わせました。先手と後手を入れ替え、その平均を取っています。
測ったのは、人代わりのプログラムが勝てた割合です。遊ぶ側がどのくらい勝てるか、と読み替えてください。この数字が小さいほど、CPUは手強いことになります。
- 深さ2:50.0%
- 深さ3:30.2%
- 深さ4:18.2%
- 深さ5:11.7%
- 深さ6:9.0%
深さ6は9.0%でした。10局に1局も勝てない相手です。子ども向けの「つよい」としては強すぎます。
そろえる先は、同じアプリの中にありました。ふつうのまるばつの「つよい」で同じ測り方をすると、遊ぶ側が勝てた割合は21.7%です。これに近いのは、18.2%の深さ4でした。遊び方が変わっても手ごたえがそろうので、深さ4を採用しました。
ここで決めたのは、あくまで「2つの遊び方で手ごたえをそろえる」ことです。21.7%という手ごたえ自体が子どもに合っているかは、この測定では分かりません。
思考時間も確かめました。いちばん読む手が多い、何も置かれていない盤面で、1手あたりにかかった時間です。深さ3が0.0004秒、深さ4が0.0024秒、深さ5が0.0152秒、深さ6が0.0627秒でした。
この数字は、速さを優先する設定でビルドしたアプリで測りました。ビルドとは、書いたコードを端末で動く形に変換することです。Swift には変換のしかたを選ぶ設定があり、速さを優先する -O と、開発中に使う最適化をほとんどしない設定があります。開発中のビルドはこれより遅くなりますが、そちらは測っていません。
このアプリでは、CPUが打つまでに0.6秒の待ちを置いています。測った数字は、どの深さもこの0.6秒に収まりました。今回は時間が決め手にはならず、手ごたえのほうで選べたことになります。
決めた値の残し方も変えました。数字を直接書くのをやめ、「手加減した深さ」という名前を付けた値にして、なぜ4なのかをコメントに書いています。テストも、この名前の付いた値を読むようにしました。同じ数字がコードとテストの2か所に書かれていると、片方だけ直したときに食い違うからです。
できたもの
「きえるまるばつ」の「つよい」は、深さ4になりました。遊ぶ側から見た勝率は18.2%、1手あたりの思考時間は0.0024秒です。次に見直したくなったときは、同じ測り方をやり直せば、前回との違いが分かります。
テストは、iPhone 17 Pro 向けにコマンドから走らせて、70件すべて通りました。新しく4件足しています。「手加減した深さ」のCPUが200局のうちには負けること(2つの遊び方それぞれで)と、「つよい」「やさしい」がそれぞれ意図した打ち手を返すことです。
1つ目は、CPUが強すぎないかを見るテストです。手加減したはずのCPUが200局のあいだ一度も負けないなら、手加減できていません。負けることを確かめる、という形にしました。
先読みの仕組みそのものを確かめる既存のテストは、深さをその場で指定して書かれています。設定値のほうを変えても、こちらのテストは影響を受けません。それを確かめたうえで、手を入れずに残しました。
まだやっていないことも書いておきます。実際に遊んで強さを体感する確認は、していません。数字はそろいましたが、深さ4の手ごたえを自分の手で確かめたわけではないのです。
まとめ
同じようにAIと一緒にゲームの強さを決める人に向けて、今回の要点を3つ書きます。
- CPU同士の勝率は、人が遊んだときの手ごたえとは別のものです。測る相手を、人の打ち方に近いものにします。
- 強さや速さの設定値は、直接書かずに名前を付け、なぜその値かをコメントに残します。テストからも同じ値を見るようにします。
- 前に自分が出した結論でも、根拠の取り方が違っていたら取り消します。今回は、AIに測り直させたことで気づけました。
次は、実際に遊んで、深さ4の手ごたえを自分で確かめます。
参考にした資料
- Writing High-Performance Swift Code(Swift 公式リポジトリの資料。
-Oと-Ononeの違い) - Running tests and interpreting results(Apple Developer Documentation)
この記事は、筆者の開発記録をもとにAI(Claude)が下書きし、筆者が確認して公開しています。