話題の9マス将棋

     テレビで9マス将棋が取り上げられたことでTwitterで将棋初心者の呟きをよく見かけます。その中でも打ち歩詰めというルールを知らない人のツイートを何回か見かけたので、3三将棋アプリを開発した側の視点で「打ち歩詰め」について書いてみようと思います。

    打ち歩詰め判定

     手を指す前に打ち歩詰めになるかどうかを判定する(打ち歩詰めならtrue、そうじゃなければfalseを返す)のですが、そのメソッド内でやっていることは以下のようなチェックです。

    1. 打ち歩(持ち駒の歩)による王手かどうか、そうでなければfalseを返す。
    2. 打たれた歩を玉以外の味方の駒で取ることが出来るかどうか、出来ればfalseを返す。
    3. 打たれた歩に相手の駒の利きがあるかどうか(玉で取ることができない)を見て、取ることが出来るか、または他の場所に玉が逃げることが出来ればfalseを返す。
    4. 上記の条件を満たさなければtrueを返す(=打ち歩詰め)。

     局面を読み進めていく毎に歩を打つ場合は常に一手毎にこのチェックをしなければならないので結構重たい処理です。

    上記の判定では不十分なケース

    打ち歩詰め後手

    上の局面で後手が3二に歩を打つと打ち歩詰めですが、上記の打ち歩詰め判定では相手の駒(2三の角)で打ち歩を取ることが出来るので打ち歩詰めではないと判定してしまいます。後手の飛車の利きが相手玉を睨んでいるにも関わらずです。

    打ち歩詰め先手

    次の例では先手が1二に歩を打つと角が利いているので後手は2二の飛車でその歩を取ることが出来ません。ゆえにこの「1二歩打ち」も打ち歩詰めですが、上記の打ち歩詰め判定ロジックでは飛車で歩が取れるから打ち歩詰めではないと判定してしまいます。

     味方の飛車や角の筋に相手玉がいるときは上記の打ち歩詰め判定ではうまくいかないことがわかります(同じ飛び道具でも香車の場合は歩と同じ方向にしか動けないので問題なし)。だからと言って、「飛車や角の利き筋に相手玉がいるときは〜」なんていう条件を追加していては、どんどん打ち歩詰め判定が複雑で重たい処理になるので避けたいところです。
     いっそのこと1手ごとに打ち歩詰めかどうか判定せずに、「持ち駒の歩を打って詰んだとき」が打ち歩詰めなので、局面の先読みをする過程では打ち歩詰め判定を省略して先読みし、直近の一手が打ち歩の時だけ打ち歩詰めチェックするという方法も考えられますが、3三将棋の場合は盤面が狭いため先読みする過程で打ち歩詰めの局面が頻発するので打ち歩詰めチェックを省略するわけにはいきません1
     また「打ち歩詰め判定」ではなく「詰み判定」のメソッドを用意して、一手毎に「打ち歩による詰みであればその手を避ける」とすればいいかもしれませんが、詰み局面かどうかを判定するのは打ち歩詰めかどうかを判定するより面倒そう?なので自分のアプリでは詰み判定ルーチンは使っていません2。局面の静的評価値を計算する時に先手側に玉が2枚あれば最大値(50000)後手側に玉が2枚あれば最小値(−50000)を返してゲームが終了したかどうかを判定しています。人間の感覚だと、詰みの局面でゲーム終了するのが当たり前とされている将棋ですが、ソフトで判定する場合は「相手の玉を取った時にゲーム終了」とするのが一番簡単で軽い処理で済むという事情があります3
     結局自分のアプリでは、最善手を検索する際に常に次善手を保存する(最善手を繰り下げる)ようにしておいて、AIが最終的に打ち歩詰めを選んでしまった場合は、次善手を実際の指し手に変えて打ち歩詰めを回避するようにしました4

    まだ問題があった

     AIの思考ルーチンを最善手と次善手の二つの候補手を返すようにしたのでとりあえず打ち歩詰めをしてしまうというバグは直ったと安心してアプリをリリースしていたのですが、人間側が指す場合は上記の打ち歩詰め判定のロジックを使っていたため、飛車や角の筋に相手玉がいる場合に人間側の打ち歩詰めを許してしまうケースが発生していたと思われます(ver0.9.1以前)。
     リリースしてから2週間以上経ってそのことに気がついて、人間が歩を打つ手を選んだ場合は、そこから二手読みをして勝負が着いたら打ち歩詰めと判定して局面を元に戻して指し手をキャンセルさせるという、やや強引なやり方ではありますが打ち歩詰めをさせないようにしました。古いバージョンをお使いの方は、バージョン0.9.1.1以降のものにアプリを更新していただきたいと思います。それにしても自分が動作確認する時は打ち歩詰めなんてしないし、AIの出来ばかり気にして人間側が打ち歩詰めをしてしまうことなんてすっかり頭の中から抜け落ちてました:sweat_smile:
     最新の将棋ソフトではおそらくBonanza由来のソース(データ構造含む)とかライブラリを使ってるのでしょうけど、自前で打ち歩詰め対策を実装するのは結構面倒だったというお話でした。


    1. 昔、本将棋のプログラムを作ったことがあって短時間で深く読むようにするために打ち歩詰めチェックを省いてテストしたことがありますが、本将棋の場合だと打ち歩詰めチェックを省いたからといってそれほど違和感のある指し手を選ぶことはありませんでした。 

    2. 本将棋ならほぼありえないのですが3三将棋の場合は「詰みではないが指す手が無い(玉が自殺する手しか無い)」時がよくあるので詰み判定ルーチンがあまり役に立たないと思います。 

    3. アプリでは相手玉を取るまで指さなくても詰みの局面になればゲーム終了とさせていますが、詰み判定ルーチンを使わずにどうやって詰み局面かどうか判断させているかというと、AIに二手読みをさせて最善手を選んでも負けになる(自玉が取られる)ならゲーム終了にするという方法で実現しています。 

    4. これは暫定的な解決方法で、最新版のアプリやgithubのコードでは次善手に置き換える処理は不要になっています。「CUIで9マス将棋を解く」「打ち歩詰め判定は必要なのか」参照。