Pythonプログラミングで

チェスを作る

Stage 5 駒の動きを判定する

4-4 キングの動きを判定する

UNAVAILABLE

キングなんてタテヨコナナメに 1 マスしか動かねーんだから書くことねーだろ

そう単純に思ってもらっちゃ困りますね。いや、半分はあたりなんですけど。ではその半分を先に片付けてしまいましょう。

                        
        # KING
        elif piece == KING:
            # normal motion (one step)
            if abs(toFILE - frFILE) <= 1 and abs(toRANK - frRANK) <= 1:
                logger.debug('KING NORMAL')
                return True
                        
                    

基本動作の場合は filerank の差が 1 以下であれば構いません。「以下」ですよ「以下」、これがないとナナメしかいけませんから。ほら、タテに進むとき file の差は 0 じゃないですか。

UNAVAILABLE

~~~~~~~~~~~~~~~

さて、簡単な方が終わりましたから厄介者をどうにかしましょう。

UNAVAILABLE

もう書くことねーよ。終わりだ終わり

キャスリングですよ。

UNAVAILABLE

こいつは基本的にキングの動きとて扱うのがいちばん楽ですからね。ルークで考えると、キャスリングの場合に加えて単純にルークを動かす場合がありますから、

UNAVAILABLE

あっ、今キング動かしたくないのに

ってなったりして面倒です。

UNAVAILABLE

この場合はキャスリングすべきかどうかわかりませんよね。

キャスリングの条件をもう一度確認してみましょうか。

  • キングと動かすルークを一度も動かしたことがない
  • キングと動かすルークの間に何もない
  • 移動前後、移動中にキングがチェックされない

UNAVAILABLE

めんどくせーのばっかだな

~~~~~~~~~~~~~~~

条件の一番上「キングとキャスリングするルークを一度も動かしたことがない」というのを判定します。そこで使っているのが castl_q/castl_k です。

UNAVAILABLE

どっかで見覚えがあるようなないような

コンストラクタをご覧ください。

                        
    def __init__(self, ...
        ...
        self.ep_target = copy.deepcopy(target) # for en passan
        self.castl_k = copy.deepcopy(castl_k)   # for castling
        self.castl_q = copy.deepcopy(castl_q)   # for castling
        self.turn = turn  # starts from 1
        ...
                        
                    

castl_k, castl_q というのを定義していますよね。それぞれキングサイドクイーンサイドキャスリングできるプレーヤーを格納しています。リスト型でミュータブルですので、引き込んだリストが変更を受けないように copy.deepcopy を使っていますね。

~~~~~~~~~~~~~~~

castl_k で具体的に見てみましょう。ゲーム開始時は両プレーヤーともキングサイドキャスリングする資格がありますから、

castl_k = [WHITE, BLACK]

となります。さて、白が h1 のルークを動かしたとしましょう。

UNAVAILABLE

こうなれば白はキングサイドキャスリングできませんから、

castl_k = [BLACK]

となりますよね。この後黒がキャスリングすれば、もう黒もこれ以上キングサイドキャスリングできませんから

castl_k = []

となります。castl_q についても同じようにします。

~~~~~~~~~~~~~~~

では motionjudge に戻ってコーディングしましょう。まず player ごとに変数 rank を設定します。

                        
            # preparing for castling; setting rank
            if player == WHITE:
                rank = 1 - 1
            elif player == BLACK:
                rank = 8 - 1
            else:
                logger.error('UNEXPECTED PLAYER VALUE in motionjudge')
                print('SYSTEM ERROR')
                sys.exit('SYSTEM ERROR')
                        
                    

白は第 1 rank で、黒は第 8 rank でしかキャスリングできませんよ。ここの rank はインデックスとして使いますから、- 1 をお忘れなく。

お次に if で条件分岐します。まずクイーンサイドキャスリングときの条件は

  • キングと a file のルークを一度も動かしたことがない
  • キングが [e - 1, rank] から [c - 1, rank] に移動する
  • キングと a file のルークの間に何もない
  • その相手の駒が [c - 1][rank], [d - 1][rank], [e - 1][rank] のどれにも動くことができない(=チェックされない)

ですね。

UNAVAILABLE

最後の条件は if 文では検証が難しいので、それ以外の 3 つを if でまず場合わけします。

                        
            # Q-side; adequate fr and to, all passing squares are EMPTY
            if player in self.castl_q and frFILE == e - 1 and frRANK == rank and toFILE == c - 1 and toRANK == rank and self.board[b - 1][rank] == self.board[c - 1][rank] == self.board[d - 1][rank] == EMPTY:
                        
                    

~~~~~~~~~~~~~~~

一番最初の条件は castl_q に player が格納されているか、その次の条件は frFILE や toRANK が正しい値になっているか見ればいい。

真ん中の条件は [b - 1, rank], [c - 1, rank], [d - 1, rank] が EMPTY であればいいですね。またキャスリングするときキングは [e - 1, rank] から [c - 1, rank] に移動するので、移動元や移動先もしっかり指定しなければいけません。

UNAVAILABLE

rank から 1 引かねーと... いや、元々 1 引いて宣言してるからいいのか

~~~~~~~~~~~~~~~

最後の条件を検証しましょう。まずキングを狙いにくる駒のいるマスの file を表す変数 fil と rank を表す ran を変数に 2 重の for ループで盤上全ての駒を網羅します。

                        
                # K must not be checked while castling
                for ran in range(SIZE):
                    for fil in range(SIZE):
                        # piece is opponent's and reaches a square through which K moves
                        if fundam.PosNeg(self.board[fil][ran]) == -player and (self.motionjudge(fil, ran, e - 1, rank, Q) or self.motionjudge(fil, ran, d - 1, rank, Q) or self.motionjudge(fil, ran, c - 1, rank, Q)):
                            logger.info('CHECKED IN THE WAY')
                            return False
                        
                    

if 分岐は

  • もし [fil, ran] のマスにいるのが相手の駒で
  • その相手の駒が [c - 1][rank], [d - 1][rank], [e - 1][rank] のどれか一つにでも動くことができるなら「動かせない」False を返す
  • すべてのマスについてクリア(相手はチェックできない)ならば True を返す

という形です。最初の条件、「相手の駒」というのは -player であらわせますね。2 番目の条件は motionjudge を使えば解決できます。

UNAVAILABLE

motionjudge?ここ motionjudge だよね?

そうです。motionjudge のなかで motionjudge を使います。ですがこのメソッドはただ単に「ここからここに駒は動かせるか」ということを検証するものにすぎません。

それにこの位置に相手のキングが来るとき、そのキングはキャスリングしてませんから、いわゆる再起関数のようなループはできませんよ。

~~~~~~~~~~~~~~~

キングサイドについても全く同様に確認できます。こちらはキャスリング可能なプレーヤーが castl_k に格納されていること、キングの行先が [g - 1][rank] になることに注意してください。

UNAVAILABLE

ここまでで動くことができず放置されたら、王様とはいえ動くことは許されません。すべて False をリターンしてしまいましょう。

これにて駒ごとの確認はすべて終わりです。長かったですね。最後に「piece の値がこれ以外の場合」をのぞいておきましょう。

                        
        # all other King's moves are invalid
        else:
            logger.error('UNEXPECTED VALUE of PIECE in motionjudge')
            print('SYSTEM ERROR')
            sys.exit('SYSTEM ERROR')
                        
                    

ポーンからキングまですべて場合分けしてんのに、まだ引っかからない得体の知れない駒は間引いておかないと。

NEXT 4-5 駒を飛び越える動きを除外する