10年ぶりのプログラム「引退したプログラマーがPythonでAIプログラミングに挑戦する」-10:開発_チェック

前回までで、コマの移動可能先についてのコーディングは終わりました。

しかし、すでに「07:開発_コマを動かす準備」でお伝えましたように、この移動可能先リストの結果は正しくありません。

チェスでは、キングがチェックされているときには、そのチェックを防ぐ手だけが有効です。それゆえに、チェックを防げない手があれば移動可能先リストから除外する必要があります。

今回はこの対応をします。

そのためにはチェックされているかどうかを確認することが必要です。


余談

わたし個人の話になりますが、今から約1年前にも、このチェスプログラムを作ろうと思ったことがあります。ちなみに今は2022年です。

そのとき、自分がチェックした後の相手のコマの動かし方のコーディング方法がわからなかったので、途中であきらめました。
チェックをかけたときの相手の反応としては、
1)チェックをかけたコマをとる
2)チェックをかけたコマとキングの間に別のコマを移動させる
3)キングが逃げる
の3パターンだけだと思います。

この1つ1つに対応したとしても、その対応が、別のチェックを誘発する可能性がありますので、その確認も必要になります。

たとえばチェックをかけたコマをとる場合について考えてみます。
チェックをかけたコマをとれるコマを見つけることは容易だと思います。しかし、そのコマが別の王手を引き起こさないようにしないといけません。つまり「ピン」されていない場合にのみ、そのコマをとれることになります。この「ピン」という判断をしないといけません。

ちなみにピンというは、下記のような状態です。釘付け(ピン付け?)されているということです。

ピン

さらに、ダブルチェックもありますので、対応は簡単ではありません。ダブルチェックとはたとえばこのようなものです。

ダブルチェック

このダブルチェックでは、キングが逃げるしかありません。しかし逃げたとしても、このときに逃げた先が安全かどうかをどうやって判断するかがわからなかったのです。

チェックをかけたコマ以外のコマが影響をしてきますので、キングの四方八方を常に潜在的なチェックという情報をもっておく必要があると思いました。つまりチェックをかけられていない状態でも、ピンをしている状態のコマをすべて把握していないといけないということです。その場合ポーンのプロモーションの結果も考慮にいれなくてはいけないので、保持しておく情報は膨大だと考えました。

もしかしたらコマをオブジェクトとして扱えば相手のキングとの距離と自分のコマの動かせる範囲から潜在的チェックについての情報をとれるのかもしれませんが、、、などと考えているうちにあきらめました。

やればできるのでしょうが、少なくとも自分では仕様書(自分が開発するための仕様書)を書けないと思いました。もちろん中には、この問題を超えることができる人がいるのかもしれませんが、少なくとも現役を退いたプログラマーである自分には、超えることができませんでした。

みなさんも、もしもなんの予備知識もなく、たとえば将棋ソフトを作ろうと思ったときには、「王手をかけたコマ」という、能動的なスタンスでプログラムを組むのだろうと思います。おそらくそれが普通なんだろうと想像しています。そこが私の当時の失敗でした。

しかし、今回もう一度挑戦をしたいと思いました。

チェスは無理かもしれないけども、せめてAIのことだけでも理解したいと思ったからです。しかしドキュメントを読むだけでは覚えないので、何かのコーディングをしたいと思いました。そのとき、思いついたのが「リバーシ」です。どちらも本質的には同じ(二人零和有限完全情報ゲーム)でしょうし、チェスよりかは容易だろうと考えました。そのように考えて、とりあえずAI文献をあさりました。

そのときに、「先手・後手を入れ替えて深読みをする」という記述を読んだのです。この瞬間に、頓挫した「チェック」に対応できるとわかったのです。

どうやればいいと思ったのかというと、
チェックをしたという「行動」ではなく、チェックをされているか?という「状態」を判断する
ということです。

チェックされているか否かの判断であれば、チェックをかけられたときに、
逃げようが、
コマをとろうが、
別のコマで防ごうが、
ダブルチェックだろうが、
その行動の直後に、「チェックされていますか?」という確認だけで解決できます。

チェックをかけるという能動的なやりかただと、チェックしたコマのことに重きを起きがちですが、それは考える必要がないということがわかったのです。誰がチェックしていてもいい、チェックされているか否かという事実だけに注目すればいいと思ったのです。

このように考えることにより、チェスプログラムができるようになりました。

しかし、若干無駄な動きがないわけでもありません。たとえば下記です。

チェック後の無駄な動き

これはクイーンでチェックをかけられている状態です。斜めからチェックされているので、そのラインに逃げても意味がありません。しかし、わたしのコーディングでは、すべての動きを一旦確認しますので、矢印のマスにも一旦移動を試みてしまいます。

しかし、そこまで無駄なのかな?とも思います。
もともと自分のコマは16個しかありません。その16個の動きを1つ1つ潰して行っても、そこまで負荷がかかるとは思っていません。数十手の「無駄」な読みをするかもしれません。しかし、たかだか数十手です。許容範囲だろうと考えています。

もっとスマートなやり方もあるかもしれません。そこはみなさんにお任せします。しかしチェックというのは、チェスでは大事なプロセスです。そこでバグを出すわけにはいきません。無駄に複雑に組んで、この部分でバグ出すよりも、数十手の「無駄」な確認をするかもしれないが「安全」なコーディングの方が重要であると考えます。

余談終わりです。


コーディングの方針について

コーディングの方針について説明します。

自分の手番になったときに、
「チェックをかけられているかどうか」
を確認します。

get_valid_movesの中で、get_all_possible_movesを呼んでいますが、その関数を呼んだ後に、チェックがかけられているかどうかを確認する関数を呼びます。その関数を、remove_invalid_movesとします。引数として、移動可能リスト渡します。その関数の中で、チェックを防げない手をすべて無効とし、移動可能リストから削除していきます。

配列をLoopで読み込んで、その手を仮想的に動かしてみて、そのときにチェックされていないかどうかを確認していきます。

チェックされているとは
「自分のキングの位置に到達できる相手のコマが存在する」
ということです。

これを確認するためには、相手のコマを仮想的に動かして、自分のキングの位置に到達できるコマがあるか否かを調べることになります。

これを配列の要素すべてに対して繰り返していきます。

ここまでをまとめますと下記になります。

自分のコマを動かす。
相手の手番に変更し、相手のコマが自分のキングに到達できるかどうかを探す。
到達できたらその動きは無効とし、配列から削除する。
手番を戻す。
動かした自分のコマを戻す。
次の配列要素に移動する。
これを繰り返す。

じつはこの動きは、後で触れるAIのMinMax法のコーディングに似ています。少なくとも考え方はほとんど同じです。私の余談で伝えたことがよくお分かりいただけるかと思います。。。AIのやり方に向けたいい練習にもなると思いますので、少し面倒ですが、ぜひ最後までお付き合いください。

コーディング

ではコーディングに入ります。

get_valid_moves

get_valid_movesの中で、get_all_possible_movesを呼び出したあとに、今回作成するremove_invalid_movesを呼び出します。

get_valid_moves

remove_invalid_moves

remove_invalid_movesのコーディングは下記になります。

remove_invalid_moves

移動可能リストの中の動きを実際に行い、その結果チェックされているか否かを確認します。チェックされていたらその動きをリストから削除します。実際に動かしたので、undoで戻します。
これを最後まで繰り返します。

上記について、2点補足があります。

1つは手番についてです。
この中で、手番を2回変更しています。
1つ目はmake_moveの直後です。make_moveを実行すると、相手の手番に移動します。しかし、今自分がチェックされているか?という状態を調べるためには、自分の手番に戻す必要があります。それゆえに、ここで手番を戻しています。
2つ目は、undo_moveの直前です。undo_moveのときには、手番が相手である前提です。それゆえに現在の自分の手番を相手に変更しています。

もう1つは、配列の削除についてです。
これはGoogleで検索してもヒットすると思いますが、Pythonで配列をLoopで削除するときには、後方検索の方が確実です。わたしも実際に前方検索&削除を行ったのですが、不可思議な動きをした場面がありました。それゆえに後方検索&削除に切り替えました。

confirm_check

上記の関数の中で、confirm_checkという関数を呼んでいます。この関数は下記になりますが、キングの位置を指定して、confirm_attackを呼び出しているだけです。

confirm_check

ゆえに、キングの位置を把握しておく必要があります。

キングの位置

キングの位置を把握するためには3箇所の修正が必要です。
1)コンストラクターで値を定義する。
2)make_moveで実際に動いた情報を更新する。
3)undo_moveで元に戻す。

それぞれ以下のようになります。

1)コンストラクターの設定
2)make_moveの設定
3)undo_moveの設定

confirm_attack

この情報を使って、自分の手番のキングの位置を指定し、confirm_attackを呼び出します。confirm_attackは下記のようになります。

confirm_attack

confirm_attackの中では、引数として受け取ったマスを相手が攻撃しているか?つまり、相手のコマの中でそのマスに移動できるコマがあるか?を確認します。そのために、手番を変更してから、移動可能なリストを出し、手番を戻します。
作成した移動可能リストの移動先が引数と一致するか否かを確認します。1つでも一致するものがあればTrueです。1つもなければFalseになります。

実行

実行してみてください。
チェックが入っている場合には、動きが制限されていることを確認してください。

次回に向けて

今回のプログラムでは、配列から要素を削除しました。もしも配列の中身がゼロになったらどうなるのでしょうか?それについては次回に説明します。

今回は以上となります。


使用しているチェスのコマは、下記からダウンロードしました。

By jurgenwesterhof (adapted from work of Cburnett) - http://commons.wikimedia.org/wiki/Template:SVG_chess_pieces, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=35634436

この記事が気に入ったらサポートをしてみませんか?