Pythonプログラミングで

チェスを作る

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

7-7 移動元の候補を絞り込む

7-6 までの生ぬるい絞り込みで候補の選定は終わりません。

UNAVAILABLE

別にええやろそんなムキにならんで

プレーヤーは動かす駒の絞り込みのために「駒をとった」とか「チェックメイトした」などの情報を棋譜に載せてきます。それらすべての要求に応えることなくいたずらに候補を返しては、プレーヤーの思っていた動きとは違う動かし方をしてしまう恐れがあります。

そこで、今度は candidates に残っているすべての候補に基づいて local_board という仮想的な盤面上で実際に駒を動かしてみて、

を検証しています。これらのプレーヤーの指示にしたがっていない候補はすぐに candidates から下ろします。

UNAVAILABLE

なんか似たようなことやった気が

ええ、6-3 で使った手法をここでも用いますよ。

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

まずは 7-6 で取得した候補を for ループで一つ一つみていきます。

                        
            # checking all the candidates
            for reference in range(len(candidates)):
                        
                    

ループ内部では local_board というインスタンスに self を完璧に複製します。

                        
            # checking all the candidates
            for reference in range(len(candidates)):
                # copying and moving the board
                local_board = Board(board=self.board, target=self.ep_target, castl_k=self.castl_k, castl_q=self.castl_q, player=self.player, turn=self.turn, s=self.s)
                local_board.move(candidates[reference][FILE], candidates[reference][RANK], toFILE, toRANK, promote)
                        
                    

そしてこの影武者で候補の通りに駒を動かして、棋譜の要求通りになっているか確認します。

                        
                # copying the board
                local_board = Board(board=self.board, target=self.ep_target, castl_k=self.castl_k, castl_q=self.castl_q, player=self.player, turn=self.turn, s=self.s)
                # moving the candidate
                local_board.move(candidates[reference][FILE], candidates[reference][RANK], toFILE, toRANK, promote)
                        
                    

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

まずは駒をとっているか見ます。該当は CAPTURED が True もしくは e.p. が line に入っている場合です。

                        
                # capture; searching for the opponent's piece that has disappeared
                if CAPTURED or 'e.p.' in line:
                    # normal capturing; TO is opponent's
                    if fundam.PosNeg(self.board[toFILE][toRANK]) == -self.player:
                        pass
                    # en passan to Q-side
                    elif fundam.InSize(toRANK - 1) and fundam.PosNeg(self.board[toFILE][toRANK - 1]) == -self.player and fundam.PosNeg(local_board.board[toFILE][toRANK - 1]) == EMPTY:
                        pass
                    # en passan to K-side
                    elif fundam.InSize(toRANK + 1) and fundam.PosNeg(self.board[toFILE][toRANK + 1]) == -self.player and fundam.PosNeg(local_board.board[toFILE][toRANK + 1]) == EMPTY:
                        pass
                    # here no piece can capture a piece
                    else:
                        logger.info('{} does not capture any piece'.format(candidates[reference]))
                        del candidates[reference]
                        reference -= 1  # back to the for loop's head, reference increases
                        continue
                        
                    
UNAVAILABLE

なっっが

安心してください。全部やりますから。

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

まずは普通に相手の駒のいる位置に駒を動かして取ってしまうパターンです。動かす前の盤面での移動先には相手の駒がいるはずですね。したがって

                        
                # capture; searching for the opponent's piece that has disappeared
                if CAPTURED or 'e.p.' in line:
                    # normal capturing; TO is opponent's
                    if fundam.PosNeg(self.board[toFILE][toRANK]) == -self.player:
                        pass
                        
                    

で確認することができます。

相手の駒をとっていることが確認されたら pass すなわち何もしないで if を離脱します。残ったものは相手の駒をとっていませんから candidates から抹消します。ですがその前に。

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

相手の駒の位置に移動する場合だけでなくアンパッサンも忘れずに検証してください。

UNAVAILABLE
UNAVAILABLE

相手の駒の位置に行かない場合か

アンパッサンキングサイドクイーンサイドに分けて判定します。

                        
                    # en passan to Q-side
                    elif fundam.InSize(toRANK - 1) and fundam.PosNeg(self.board[toFILE][toRANK - 1]) == -self.player and fundam.PosNeg(local_board.board[toFILE][toRANK - 1]) == EMPTY:
                        pass
                    # en passan to K-side
                    elif fundam.InSize(toRANK + 1) and fundam.PosNeg(self.board[toFILE][toRANK + 1]) == -self.player and fundam.PosNeg(local_board.board[toFILE][toRANK + 1]) == EMPTY:
                        pass
                        
                    
UNAVAILABLE

いや、なんで分けんのさ

例えばこんな時は、キングサイドにアンパッサンしているか確認しようと board[toFILE - 1][toRANK] としてしまうと、インデックスが範囲を超えているのでエラーが出て強制終了させられてしまいます。

UNAVAILABLE

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

さて、ここまで残った候補は手のつけようがありません。candidates から削除します。

                        
                    # here no piece can capture a piece
                    else:
                        logger.info('{} does not capture any piece'.format(candidates[reference]))
                        del candidates[reference]
                        reference -= 1  # back to the for loop's head, reference increases
                        continue
                        
                    

reference を調整して for ループを続行させます。

UNAVAILABLE

なんで reference 調整せなあかんの

本来 candidates の中身はリストですが、簡単のためにこう書いてみましょう。

candidates == [0, 1, 2, 3, 4, ...]

この candidates を検証していって、reference == 2 のときに「この候補は不適合だ」ということが分かったとしましょう。del を使って candidates[reference] を削除すると、

candidates == [0, 1, 3, 4, ...]

となりますね。ところで、reference をこのままにして for ループの先頭に戻ると、1 が加えられて reference == 3 となります。次は candidates[3] == 4 を確認するんですよね?もう気付きました?

UNAVAILABLE

3 ってまだみてなくね?

ですから、for ループの先頭に戻る前に reference から 1 引いておく必要があるんです。

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

お次はチェックで候補を絞り込みます。流派によっては記譜に書いた "+" の数でダブルチェック・トリプルチェックを指定してきます。ですから line の中の "+" の数に checkcount の示す盤面上でのチェックの数が届かなかったら、

UNAVAILABLE

チェックの数が足りねえんだよ

と怒られないように、候補を candidates から削除、reference を調整、ループ続行という手筈です。

                        
                # check
                if line.count('+') > local_board.checkcounter(-self.player):
                    logger.info('{} is short of the number of check'.format(candidates[reference]))
                    del candidates[reference]
                    reference -= 1  # at loop's head, reference increases
                    continue
                        
                    
UNAVAILABLE

なんで checkcounter の引数は -self.player なの?

6-2 で散々強調したじゃないですか。checkcounter の引数はチェック

される側

のプレーヤー番号だって。

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

"#" で表されるチェックメイトは単純に checkmatejudge の真偽だけで判断できます。なんて簡単なんでしょう。

                        
                # checkmate
                if '#' in line and local_board.checkmatejudge(-self.player) == False:
                    logger.info('{} does not checkmate'.format(candidates[reference]))
                    del candidates[reference]
                    reference -= 1  # at loop's head, reference increases
                    continue
                        
                    

アンパッサンは「ポーンがナナメに移動しているのに、移動先に駒がない」という状態から判断します。これまた初心者に優しいねえ。

UNAVAILABLE

さっきから喧嘩売っとんのかテメェシャクに触るんだよ

いえいえ、そんな滅相もない。

                        
                # en passant
                if 'e.p.' in line and self.board[toFILE][toRANK] != EMPTY:
                    logger.info('{} does not en passant'.format(candidates[reference]))
                    del candidates[reference]
                    reference -= 1  # at loop's head, reference increases
                    continue
                        
                    


これで候補が絞り込まれました。

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

リターン作業に入ります。

まず残っている候補が 1 つだけの場合は、motionjudge や move の引数の形に合わせて残った候補をリターンします。あっ、ロガーは不要ですよ。

                        
            # normal return
            if len(candidates) == 1:
                logger.info('NORMALLY RETURNED')
                return [candidates[0][FILE], candidates[0][RANK], toFILE, toRANK, promote]
                        
                    

一方、候補が複数あるときは最初の候補だけをリターンします。これでプレーヤーの思い通りにならなくたって、それは駒の動き方を限定しなかったその人の落ち度ですからね。一応ログでは「他に候補がある」と警告します。

                        
            # when some candidates are available
            elif len(candidates) > 1:
                logger.warning('THERE IS ANOTHER MOVE')
                return [candidates[0][FILE], candidates[0][RANK], toFILE, toRANK, promote]
                        
                    

問題は候補がない場合です。プレーヤーの指定通りに駒を動かせませんので、「移動不可能」False をリターンすることになります。

                        
            # no candidates are available
            else:
                logger.info('THERE IS NO MOVE')
                return False
                        
                    

NEXT 7-8 勝敗の棋譜を処理する