Pythonプログラミングで

チェスを作る

Stage 6 勝敗を判定する

6-3 チェックメイト・ステイルメイトを判定する

UNAVAILABLE

チェックメイトで勝ちたい。あわよくば相手や機械に『チェックメイト』と言わせてチヤホヤされてみたい

チェスのゲームをやる人はみんなこういう頭をしています。その期待に応えてあげるのがゲームデザイナーの役目じゃないですか。応えてあげましょうよ。

ということでチェックメイトを判別する機能をこしらえます。それが board.py の checkmatejudge メソッドになります。

                        
    def checkmatejudge(self, matee, logger=None):
        ### LOGGER SETTING
        logger = logger or self.logger
        

        # COUNTING CHECK; if not checked, it is not checkmate
        if self.checkcounter(matee) in [False, 0]:
            logger.debug('NOT CHECKED; it is not checkmate')
            return False
        

        # SEARCHING ALL THE MOVES MATEE CAN
        for frFILE in range(SIZE):
            for frRANK in range(SIZE):
                if fundam.PosNeg(self.board[frFILE][frRANK]) == matee:
                    # searching all TO the piece can reach
                    for toFILE in range(SIZE):
                        for toRANK in range(SIZE):
                            # cloning the board
                            local_board = Board(board=self.board, target=self.ep_target, castl_k=self.castl_k, castl_q=self.castl_q, player=matee)
                            # moving the local board and counting up check
                            if local_board.move(frFILE, frRANK, toFILE, toRANK, Q) and local_board.checkcounter(matee) == 0:
                                logger.info('THERE IS {}, {} -> {}, {}'.format(frFILE,frRANK,toFILE,toRANK))
                                return False
                    logger.debug('motion from "FR = {}, {}" is unavailable'.format(frFILE, frRANK))

        # completing the loop, there is no way to flee
        return True
                        
                    

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

まずは引数です。

                        
    def checkmatejudge(self, matee, logger=None):
                        
                    

引数の matee はチェックメイト

される側

のプレーヤーです。お間違えなきよう。

まずはいつも通りロガーを設定しましょう。

                        
    def checkmatejudge(self, matee, logger=None):
        ### LOGGER SETTING
        logger = logger or self.logger
                        
                    
UNAVAILABLE

なんだよいつも通りって。しっかり説明せえよ

なら 4-1 でもみてみればいいんじゃないですか。

チェックメイトというのは「すでにチェックされていて、どの駒をどのように動かしてもチェックを逃れられない」状況ですよね。チェックされていなければそもそもチェックメイトではありません。

ですからメソッドの一番上ではチェックされていない状況に対して False をリターンしています。

                        
        # COUNTING CHECK; if not checked, it is not checkmate
        if self.checkcounter(matee) in [False, 0]:
            logger.debug('NOT CHECKED')
            return False
                        
                    

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

ここから先のフローチャートを先に上げておきます。確認してください。

UNAVAILABLE

幾重もの for ループは「matee のすべての駒の正しい動かし方」をシラミつぶしに見ていることを意味します。まず外側の 2 つで移動元 fr となるマスをおさえます。

                        
        # SEARCHING ALL THE MOVES MATEE CAN
        for frFILE in range(SIZE):
            for frRANK in range(SIZE):
                if fundam.PosNeg(self.board[frFILE][frRANK]) == matee:
                        
                    

それが matee の駒であれば、次の 2 重 for ループで駒の移動先 to をシラミつぶしに探します。

                        
        # SEARCHING ALL THE MOVES MATEE CAN
        for frFILE in range(SIZE):
            for frRANK in range(SIZE):
                if fundam.PosNeg(self.board[frFILE][frRANK]) == matee:
                    # searching all TO the piece can reach
                    for toFILE in range(SIZE):
                        for toRANK in range(SIZE):
                        
                    

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

さて、移動元 fr と移動先 to が決まったところで、この後どうしましょうか。プログラムする人間の心理としては

UNAVAILABLE

現状の self から駒を動かしてチェックを回避できるか見てみたい

と思っています。要はチェックを解消できる方法が一つでもあればチェックメイトじゃないわけですから。

ですが self で一度駒を動かしてしまうと再び元の状態に戻せない以上、この Board そのものに手をつけて駒を動かすのは禁忌です。言い換えると、このループの内側では

  • self を完璧に再現して駒を動かしてみたい
  • self 自体を触ることは許されない

というジレンマを抱えています。そこで local_board という、self を完コピした、まったく新しいインスタンスを作ります。

UNAVAILABLE

完コピ?どうやんの

local_board を宣言するときの引数に self の情報をありったけ突っ込むんです。

                        
                    # searching all TO the piece can reach
                    for toFILE in range(SIZE):
                        for toRANK in range(SIZE):
                            # cloning board
                            local_board = Board(board=self.board, target=self.ep_target, castl_k=self.castl_k, castl_q=self.castl_q, player=matee)
                        
                    
UNAVAILABLE

影武者みたいなもんやな。でも最後の player は matee で self とは違うな

ええ、ここだけは調節してください。駒を動かしてチェックを回避するのはチェックされている側ですから。

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

local_board を宣言したあとに local_board.move を発動して駒を動かします。移動元と移動先は for ループのパラメーターどおりです。

                        
                            # cloning board
                            local_board = Board(board=self.board, target=self.ep_target, castl_k=self.castl_k, castl_q=self.castl_q, player=matee)
                            # moving the local board and count up check
                            if local_board.move(frFILE, frRANK, toFILE, toRANK, Q) and local_board.checkcounter(matee) == 0:
                                logger.info('THERE IS {}, {} -> {}, {}'.format(frFILE,frRANK,toFILE,toRANK))
                                return False
                        
                    
UNAVAILABLE

if の中で move を発動してるけど、これで実際に local_board の駒動いてんのか?

大丈夫ですよ。move のリターン値「移動成功/失敗」というのを計算する過程で駒を動かしていますから。

ここで local_board について、

  • move から True (移動成功)
  • checkcounter から 0 (チェックされていない)

が得られれば「移動してチェックを回避できる」という意味になりますから、チェックメイトではないということになります。False をリターンしてください。

                        
                            # moving the local board and count up check
                            if local_board.move(frFILE, frRANK, toFILE, toRANK, Q) and local_board.checkcounter(matee) == 0:
                                logger.info('THERE IS {}, {} -> {}, {}'.format(frFILE,frRANK,toFILE,toRANK))
                                return False
                        
                    

移動元 FR の駒をどうやって動かしてもチェックを回避できない時はロガーでその旨知らせてあげます。

                        
                            logger.debug('"FR = {}, {}" was unavailable'.format(frFILE, frRANK))
                        
                    

ループを抜けきってしまいますと、「どのように動かしてもチェックを免れることはできない」ということになってしまいますので、「チェックメイトされてる」True をリターンしましょう。

                        
        # completing the loop, there is no way to flee
        return True
                        
                    

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

今度は決着のつき方3つ目, ステイルメイトです。stalematejudge メソッドで判別します。

                        
    def stalematejudge(self, matee, logger=None):
        ### LOGGER SETTING
        logger = logger or self.logger
        

        ### COUNTING CHECKS; checked, it is not stalemate
        if self.checkcounter(matee) not in [0, False]:
            logger.debug('CHECKED; it is not stalemate')
            return False


        ### SEARCHING ALL THE MOVES MATEE CAN
        for frFILE in range(SIZE):
            for frRANK in range(SIZE):
                if fundam.PosNeg(self.board[frFILE][frRANK]) == matee:
                    # searching all TO the piece can reach
                    for toFILE in range(SIZE):
                        for toRANK in range(SIZE):
                            # cloning the board
                            local_board = Board(board=self.board, target=self.ep_target, castl_k=self.castl_k, castl_q=self.castl_q, player=matee)
                            # moving the local board and counting up check
                            if local_board.move(frFILE, frRANK, toFILE, toRANK, Q) and local_board.checkcounter(matee) == 0:
                                logger.info('THERE IS {}, {} -> {}, {}'.format(frFILE,frRANK,toFILE,toRANK))
                                return False
                    logger.debug('motion from "FR = {}, {}" is unavailable'.format(frFILE, frRANK))

        # completing the loop, there is no way to avoid check when moving
        logger.info('STALEMATE. {} cannot move'.format(self.player))
        return True
                        
                    

相変わらず引数の matee は「ステイルメイトされる側」つまり動けなくなってしまったか確認したい方のプレーヤー番号です。

                        
    def stalematejudge(self, matee , logger=None):
                        
                    

もう強調しなくていいでしょ?しましょうか?引数はステイルメイト

される側

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

やることはほぼほぼチェックメイトと変わりません。唯一の違いは、「現状でチェックを受けていない」ことです。メソッドの頭で調べています。

                        
        ### LOGGER SETTING
        logger = logger or self.logger
        
        ### COUNTING CHECKS; checked, it is not stalemate
        if self.checkcounter(matee) not in [0, False]:
            logger.debug('CHECKED')
            return False 
                        
                    

6-2 を確認すればわかりますが、もし checkcounter が 0 でも False でもなければ間違いなくチェックされていますから、ステイルメイトとは言えません。

この第一関門を突破したものだけが次なるステップへ進むことができます。4 重の for ループです。そう、6-3 のチェックメイトとまったく同じことをします。コピペでいいですよ、コピペで。

                    
        ### SEARCHING ALL THE MOVES MATEE CAN
        for frFILE in range(SIZE):
            for frRANK in range(SIZE):
                if fundam.PosNeg(self.board[frFILE][frRANK]) == matee:
                    # searching all TO the piece can reach
                    for toFILE in range(SIZE):
                        for toRANK in range(SIZE):
                            # cloning board
                            local_board = Board(board=self.board, target=self.ep_target, castl_k=self.castl_k, castl_q=self.castl_q, player=matee)
                            # moving the local board and count up check
                            if local_board.move(frFILE, frRANK, toFILE, toRANK, Q) and local_board.checkcounter(matee) == 0:
                                logger.info('THERE IS {}, {} -> {}, {}'.format(frFILE,frRANK,toFILE,toRANK))
                                return False
                    logger.debug('"FR = {}, {}" was unavailable'.format(frFILE, frRANK))
        # completing the loop, there is no way to avoid check when moving
        logger.info('STALEMATE. {} cannot move'.format(self.player))
        return True
                    
                

NEXT Stage 7 入力の棋譜を駒の動きに変換する