Pythonプログラミングで

チェスを作る

Stage 7 入力を変換する

7-1 不要な文字を消去する

1-1 で決めたように、このゲームでは駒を動かすのに棋譜を使います。ですから棋譜が読めるようにならなければいけません。読めますよね?

UNAVAILABLE

ここでは棋譜の書き方なんてまともにやりませんから、しっかり勉強してきてください。

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

棋譜には、駒の動きを表すなくてはならない部分と、あってもなくても駒の動きがわかる部分とがあります。Q a x e6 ++ ! という場合を考えてみましょう。一番最後の "!" は記録している人が

UNAVAILABLE

これは妙手じゃ

と、うなった手であることを表しています。駒の動きには一切関係ありませんから、文字列からは削除します。

不要なのはこれだけではありません。Q a x e6 ++ ! の文字列をよくみてください。なんならプログラムみたいに配列に格納するところも想像してみてください。

UNAVAILABLE

いや、もういらないもんねーよ。

空白文字が格納されますよね。ぶっちゃけいらないでしょ。ですから空白も消します。

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

それではコーディングに入りましょう。board.py の s_analyze メソッドです。

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

        ### ADJUSTING s
        # removing spaces, !, ?
        self.s = self.s.replace(' ', '').replace('!', '').replace('?', '')

        # avoiding bugs
        if len(self.s) == 0:
            logger.debug('len(s) == 0')
            return False

        # the pattern of the normal format
        match = re.match(r'^[PRNBQK]?[a-h]?[1-8]?[x]?[a-h][1-8](=[RNBQ]|e.p.)?[\++#]?$', self.s)


        ### NORMAL FORMAT
        if match:
            line = match.group()    # line is the record matched into format
            logger.info('line = {}'.format(line))

            # what piece is moving
            if line[0] in ['P', 'R', 'N', 'B', 'Q', 'K']:
                piece = IO.ToggleType(line[0])
                # deleting the info of piece because we do not use it any more
                line = line.lstrip(line[0]) 
            else:
                # if not written the piece, it is PAWN's motion
                piece = PAWN
            logger.info('PIECE == {}'.format(piece))

            # written info of what rank the piece comes from; frRANK starts from 0, but written No. is from 1
            if line[0].isdecimal():
                frFILE = OVERSIZE
                frRANK = IO.ToggleType(line[0]) - 1
                # deleting the number so that the sentence seems simpler
                line = line.lstrip(line[0])
            # written info of what file the piece comes from; frFILE starts from 0, but written No. is from 1
            elif ord('a') <= ord(line[0]) <= ord('h') and ord('a') <= ord(line[1]) <= ord('x'):
                frFILE = IO.ToggleType(line[0]) - 1
                frRANK = OVERSIZE
                # deleting only the first character of line
                line = line[1:]
            # nothing is written about where the piece comes from
            else:
                frFILE = OVERSIZE
                frRANK = OVERSIZE
            logger.info('FR = {}, {}'.format(frFILE, frRANK))

            # whether the piece has captured one of the opponent's pieces
            if line[0] == 'x':
                CAPTURED = True
                line = line.lstrip(line[0])
            else:
                CAPTURED = False

            # where the piece goes to (certainly written); toFILE and toRANK starts from 0
            toFILE = IO.ToggleType(line[0]) - 1
            toRANK = IO.ToggleType(line[1]) - 1
            logger.info('TO = {}, {}'.format(toFILE, toRANK))

            # promotion
            if '=' in line:
                promote = IO.ToggleType(line[line.index('=') + 1])
            else:
                promote = EMPTY
            logger.info('promote = {}'.format(promote))

            # counting up all the available candidates searching all the squares
            candidates = []
            for fil in range(SIZE):
                # when frFILE is written
                if fundam.InSize(frFILE) and frFILE != fil:
                    continue

                for ran in range(SIZE):
                    # when frRANK is written
                    if fundam.InSize(frRANK) and frRANK != ran:
                        continue

                    # if the piece is not own, you cannot move it
                    if self.board[fil][ran] != self.player * piece:
                        continue

                    # in case of unavailable motion
                    if self.motionjudge(fil, ran, toFILE, toRANK, promote) == False:
                        continue

                    candidates.append([fil, ran])
            logger.info('candidates = {}'.format(candidates))

            # checking all the candidates
            for reference in range(len(candidates)):
                # 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)

                # 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  # at loop's head, reference increases
                        continue
                
                # 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

                # 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

                # 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

            # normal return
            if len(candidates) == 1:
                logger.info('NORMALLY RETURNED')
                return [candidates[0][FILE], candidates[0][RANK], toFILE, toRANK, promote]
            # when another candidate is available
            elif len(candidates) > 1:
                logger.warning('THERE IS ANOTHER MOVE')
                return [candidates[0][FILE], candidates[0][RANK], toFILE, toRANK, promote]
            # no candidates are available
            else:
                logger.info('THERE IS NO MOVE')
                return False


        # NOT IN NORMAL FORMAT
        else:
            # game set; take note that player themselves cannot win by inputting these codes
            if self.s == '1/2-1/2':
                logger.info('DRAW GAME')
                return EMPTY
            elif self.s == '1-0' and self.player == BLACK:
                logger.info('WHITE WINS')
                return WHITE
            elif self.s == '0-1' and self.player == WHITE:
                logger.info('BLACK WINS')
                return BLACK
            
            # check whether s represents castling
            # rank setting
            if self.player == WHITE:
                rank = 1 - 1
            elif self.player == BLACK:
                rank = 8 - 1
            else:
                logger.error('UNEXPECTED PLAYER VALUE in s_analyze')
                print('SYSTEM ERROR')
                sys.exit('SYSTEM ERROR')
            # Q-side
            if self.s in ['O-O-O', 'o-o-o', '0-0-0'] and self.board[e - 1][rank] == self.player * KING:
                logger.info('format is {}, castl is {}'.format(self.s, self.castl_q))
                return [e - 1, rank, c - 1, rank, EMPTY]
            # K-side
            elif self.s in ['O-O', 'o-o', '0-0'] and self.board[e - 1][rank] == self.player * KING:
                logger.info('format is {}, castl is {}'.format(self.s, self.castl_k))
                return [e - 1, rank, g - 1, rank, EMPTY]
            
            # invalid format
            else:
                logger.debug('INVALID FORMAT')
                return False
                        
                    
UNAVAILABLE

なっっっが...

ちょっと脅しただけですって。これを全 9 回に分けてご説明します。今回使うのはここだけです。一瞬でしょ?

                        
    def s_analyze(self, logger=None):
        ### LOGGER SETTING
        logger = logger or self.logger
                            
        ### ADJUSTING s
        # removing spaces
        self.s = self.s.replace(' ', '').replace('!', '').replace('?', '')
                    
        # avoiding bugs
        if len(self.s) == 0:
            logger.debug('len(s) == 0')
            return False  
                        
                    

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

いつも通りロガーを設定したら、空白 " ", エクスクラメーション "!", クエスチョン "?" を全て空文字 "" に置き換えます。

                        
    def s_analyze(self, logger=None):
        ### LOGGER SETTING
        logger = logger or self.logger
                            
        ### ADJUSTING s
        # removing spaces
        self.s = self.s.replace(' ', '').replace('!', '').replace('?', '')
                        
                    

replace メソッドを使っていますね。

なお、str 型はイミュータブル(書き換え不可能)ですから、del self.s[-1] のようなリスト操作はできません。まあこれくらいは常識ですね。

UNAVAILABLE

いみゅーたぶる...?

いや、リンク貼ってんだからそこで勉強せえよ。

次の if 文はバグを回避するために入れたものです。

                        
        # avoiding bugs
        if len(self.s) == 0:
            logger.debug('len(s) == 0')
            return False  
                        
                    

後で s の最後尾の文字を調べるんですが、文字列を全部消してしまって、仮に s に文字が入っていない状態 (len(s) == 0) になると、「インデックス無効」とエラーを吐き出してシステムが強制終了されてしまいますから。

NEXT 7-2 正規表現でふるいにかける