Pythonプログラミングで

チェスを作る

Stage 10 記録からゲームを進める

10-2 棋譜の内容を解読する

さて、構文解析の時間です。前回用意した棋譜の文字列 line から、駒を動かすのに役立つ情報だけをもれなくダブりなく解読していきます。

どうやるかって言いますと、for 文で 1 文字ずつ letter という str 型変数に line の文字を取り込んでいきます。

                        
        # DETECTING EACH LETTER IN LINE
        for letter in line:
            # when you come to the end of a sentence
            if letter in [' ', '\t', '\n', ',', '.']:
                        
                    

とりあえずこの文字を local_board.s の中にブチ込んでって、空白や改行が割り込んだら打ち切り、local_board.s に残った文字列が s_analyze にうまいこと引っかかってくれるかを見ます。フローチャートで表すとこんな感じ。

UNAVAILABLE

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

例えば

line = "1\ta4          c5          \n2\tg3          "

となっていれば、まず最初に letter に '1' が代入され、これが local_board.s に移ります。

local_board.s == "1"

ついで letter には line[1] つまり '\t' が入りますが、こいつは空白の文字なので local_board.s はここまでで棋譜として扱えるか検証されます。当然 "1" では棋譜となりませんから無視してしまいます。

次は letter に line[2] の 'a' が入って local_board.s に移行されますね。

local_board.s == "a"

その次は line[3] の '4' が入ります。

local_board.s == "a4"

その次が空白文字なので、local_board.s はここで打ち切り。棋譜として扱えるか検証します。"a4" は立派に棋譜として使えますので、影武者の盤面 local_board で実際に a4 の動きをさせ、

SUBRECADDRESS に

記録を取ります。

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

該当箇所のコードはおよそこんな感じ。

                        
        # detectong each letter in line
        for letter in line:
            # when you come to the end of a sentence
            if letter in [' ', '\t', '\n', ',', '.']:
                logger.info('local_s is {}'.format(local_board.s))
                motion = local_board.s_analyze()
                # normal motion
                if type(motion) is list:
                    ...
                # game set
                elif type(motion) is int:
                    ...
                # initializing the local_s
                local_board.s = ''
            # the sentence does not end yet
            else:
                local_board.s = ''.join([local_board.s, letter])
                logger.debug('local_s = {}'.format(local_board.s))
                        
                    

local_board.s を打ち切ったのが for 直下の if で条件分けしてるところ、local_board.s に文字入れてくのが else で除外してるところです。

                        
        # DETECTING EACH LETTER IN LINE
        for letter in line:
            # when you come to the end of a sentence
            if letter in [' ', '\t', '\n', ',', '.']:
                logger.info('local_board.s is {}'.format(local_board.s))
                motion = local_board.s_analyze()
                # normal motion
                if type(motion) is list:
                    ...
                # game set
                elif type(motion) is int:
                    ...
                # initializing local_board.s
                local_board.s = ''
            # the sentence does not end yet
            else:
                local_board.s = ''.join([local_board.s, letter])
                logger.debug('local_board.s = {}'.format(local_board.s))
                        
                    

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

各分岐の中身を見ていきましょう。今回は先に else の方、「letter が空白文字でない場合」を確認します。

                        
            # the sentence does not end yet
            else:
                local_board.s = ''.join([local_board.s, letter])
                logger.debug('local_s = {}'.format(local_board.s))
                        
                    

join メソッドを使って ''.join([local_board.s, letter]) としているのは、ただ単に local_board.s の後ろに letter の文字をくっつけてるだけ。これでおしまい。

for 直下の if で分けられた方、「letter が空白文字の場合」は

  • local_board.s が出来上がってる
  • どうあがいても棋譜としての体裁を成さない

のどちらかのはずですから、s_analyze を発動、motion に返り値を格納します。

                        
            # when you come to the end of a sentence
            if letter in [' ', '\t', '\n', ',', '.']:
                logger.info('local_s is {}'.format(local_board.s))
                motion = local_board.s_analyze()
                ...
                        
                    

駒を動かすときは list, 勝敗がついたときは int がリターンされますから、それによって場合分けしています。

UNAVAILABLE

意味を成さないときは何もしないの?

あとで local_board.s が使える場合と合流しますので、そのときに。

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

まずは駒を動かす場合です。実際に move メソッドで駒を動かし、記録を

SUBRECADDRESS

にとります。

                        
                # normal motion
                if type(motion) is list:
                    local_board.move(*motion)
                    local_board.record(SUBRECADDRESS)  # all the local moves are recorded on the sub file 
                    if local_board.player == BLACK:
                        local_board.turn += 1
                    local_board.player *= -1
                        
                    

パラメーターを変えるのを忘れないように。

                        
                # normal motion
                if type(motion) is list:
                    local_board.move(*motion)
                    local_board.record(SUBRECADDRESS)  # all the local moves are recorded on the sub file
                    if local_board.player == BLACK:
                        local_board.turn += 1
                    local_board.player *= -1
                        
                    
UNAVAILABLE

playmode でやったのとおんなじやないかい

8-6 でやりましたね。

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

引数で要請されたターン・プレーヤーにたどり着いていれば、リターン作業に入ります。

                        
                    # reaching destination
                    if local_board.turn == destination_turn and local_board.player == destination_player: 
                        logger.info('trace succeeded')
                        if isrecwrite:
                            # copying the file
                            f = open(MAINRECADDRESS, 'w')
                            g = open(SUBRECADDRESS, 'r')
                            f.write(g.read())
                            f.close()
                            g.close()
                        return local_board
                        
                    

isrecwrite が False つまり「MAINRECADDRESS を書き換えなくていい」場合はこのまま local_board をリターンします。

                        
                    # reaching destination
                    if local_board.turn == destination_turn and local_board.player == destination_player:
                        logger.info('trace succeeded')
                        if isrecwrite:
                            # copying the file
                            f = open(MAINRECADDRESS, 'w')
                            g = open(SUBRECADDRESS, 'r')
                            f.write(g.read())
                            f.close()
                            g.close()
                        return local_board 
                        
                    

一方 isrecwrite が True つまり「MAINRECADDRESS を書き換える」場合は SUBRECADDRESS の中身を MAINRECADDRESS に記録を転記しなければいけません。

UNAVAILABLE

サブをそのままメインに入れるのか

                        
                    # reaching destination
                    if local_board.turn == destination_turn and local_board.player == destination_player:
                        logger.info('trace succeeded')
                        if isrecwrite:
                            # copying the file
                            f = open(MAINRECADDRESS, 'w')
                            g = open(SUBRECADDRESS, 'r')
                            f.write(g.read())
                            f.close()
                            g.close() 
                        return local_board
                        
                    

そして MAINRECADDRESS を書き換えようが書き換えまいが、リターンするのは local_board です。

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

一方決着がついていれば駒を動かすことはしません(できません)し、SUBRECADDRESS に記録をとることもしません。isrecwrite で MAINRECADDRESS に転記が必要と判断されたら、サブの中身を転記します。

                        
                # game set
                elif type(motion) is int:
                    print('GAME SET')
                    if isrecwrite:
                        # copying the record
                        f = open(MAINRECADDRESS, 'w')
                        g = open(SUBRECADDRESS, 'r')
                        f.write(g.read())
                        f.close()
                        g.close()
                    return motion
                        
                    

そして

motion をリターン

します。

                        
                    return motion
                        
                    

つまり

返り値は勝ったプレーヤー番号、型は int

です。お間違えなきよう。

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

for 文を抜けた後にもう一度同じことをやります。リターンまで同じです。

                        
        # last one local_board.s; the same as in the for loop
        logger.info('local_board.s is {}'.format(local_board.s))
        motion = local_board.s_analyze()
        if type(motion) is list:
            local_board.move(*motion)
            local_board.record(SUBRECADDRESS)
            if local_board.player == BLACK:
                local_board.turn += 1
            local_board.player *= -1
            # reaching destination
            if local_board.turn == destination_turn and local_board.player == destination_player:
                logger.info('trace succeeded')
                if isrecwrite:
                    f = open(MAINRECADDRESS, 'w')
                    g = open(SUBRECADDRESS, 'r')
                    f.write(g.read())
                    f.close()
                    g.close()
                return local_board
        elif type(motion) is int:
            if isrecwrite:
                f = open(MAINRECADDRESS, 'w')
                g = open(SUBRECADDRESS, 'r')
                f.write(g.read())
                f.close()
                g.close()
            return motion
                        
                    
UNAVAILABLE

やらんでええやろ

これをやらないと、棋譜の最後の文が認識されない恐れがあります。嘘だと思うなら外して試してみな。

ここまででリターンされなかったら引数で要求されたところまで盤面の動きを再現できなかったことを意味しますから、self を返します。

                        
        # reaching here, you cannot trace
        logger.warning('FAILED TO BACK')
        return self
                        
                    

そう、

self を

返してるんです。False ではありません。いや、False でもいいのかもしれませんが、私がコーディングした時 False でバグった経緯がありますので self をリターンしています。

NEXT 10-3 1手戻る機能を実装する