ナイト配置ゲームをつくる(1)の続きです。前回作成したものは着手可能点をランダムに選択して着手するだけだったので全然強くありませんでした。今回はアルゴリズムを改善して強くします。

必勝形かどうかの判定

現在の盤面が必勝形かどうかを判定する関数を定義します。合法手がひとつも存在しないのであれば負けが確定します。そのような形を相手に押し付けることができる合法手が存在するのであれば必勝形であると考えることができます。

Win関数を再帰呼び出ししています。同じ引数で何度も呼び出すことになるので、一度結果を取得したらこれをMapに保存して処理にかかる時間を短縮しています。

盤面が狭いとこれで動いてくれます。しかし8×8の盤だと合法手がたくさん存在するため処理に時間がかかります。以下のコードでは待てど暮らせど着手してくれません。

評価関数の実装

上記の方法で最善手を探すのは合法手が少なくなって短時間で処理が完了できるときに限定して、開始直後のように合法手がたくさんあるときは別に評価関数を実装することで最善手に近いものを探すことにします。

評価の方法ですが(自分の合法手数 – 相手の合法手数)で評価することにします。第二引数のdepthはあと何手先まで調べるかです(とりあえず4手先まで読む)。

AiMove関数を以下のように書き換えます。ただこれでもまだ時間がかかります。

αβ法【アルファベータ法】

上記の方法は、自分の手番における自分の利得の最大化と相手の手番における自分の利得の最小化を考えたとき、自分の利得が最も大きくなる選択肢がどれになるかを探索する方法でした。このような方法をMinMax法といいます。

MinMax法では、自分の手番か相手の手番かによって、最大化をするか最小化をするかが異なります。これを簡単にするために考案されたのがNegaMax法です。「自分の得点 = 相手の損失」なので相手の手番であれば符号を反転させて考えればどちらの手番でも最大化するようにすればよいことになります。

MinMax法もNegaMax法もすべての選択肢を最後まで探索しますが、これでは時間がかかります。そこで探索の途中で明らかに可能性のない枝を発見したときは「枝刈り」をして処理にかかる時間を短縮します。

α値を「この局面で自分がすでに確保できている最低スコア」、β値を「相手がこれ以上許さない上限スコア」とします。すると探索中の評価値はα値とベータ値の間の値となります。それ以外の場合はそこからさらに調べてもよい値はでてきません。このような枝刈りをいれるとかなり処理速度は速くなります。

現在の盤面を引数にして最善手を取得する関数を定義します。

これでよほど短気でない人なら普通に動作するようになりました。ただやはり最初は着手までに数秒かかります。

そこで時間がかかる処理になりますが(一度にやろうとせずにわけたほうがよさそう)、以下のような関数を実行してプレイヤーのすべての初手に対する応手と、それに対するプレイヤーの応手に対する応手をすべて取得してしまいます。そしてこの結果をコードに埋め込んでしまえばコンピュータ側の着手はすぐに行われます。またそれ以降の着手は合法手が少なくなっているので、前処理をしなくてもほとんど待たされることなくすぐに行われます。

出力されたものをコード内に埋め込みます。