【Python】【将棋ネタ】複数の棋譜データを一括で局面データを列記したテキストファイルに変換する

はじめに

タイトルが分かりにくくなっています。
やりたいことは以下のことです。
PCソフト「激指」の棋譜データ(KIF)を局面データに変換します。

「激指」で保存したKIFファイル

# ----  激指 柿木形式棋譜ファイル  ----
手合割:平手
先手:あなた
後手:激指<居飛車> 初段
手数----指手---------消費時間--
   1 2六歩(27)   ( 0:14/00:00:14)
   2 8四歩(83)   ( 0:01/00:00:01)
*<fight>0 定跡手</fight>
   3 2五歩(26)   ( 0:02/00:00:16)
   4 8五歩(84)   ( 0:01/00:00:02)
*<fight>0 定跡手</fight>
   5 7八金(69)   ( 0:01/00:00:17)
   6 3二金(41)   ( 0:01/00:00:03)
*<fight>0 定跡手</fight>
   7 3八銀(39)   ( 0:01/00:00:18)
   8 8六歩(85)   ( 0:01/00:00:04)
*<fight>0 定跡手</fight>
   9 同 歩(87)   ( 0:03/00:00:21)
  10 同 飛(82)   ( 0:01/00:00:05)
*<fight>0 定跡手</fight>
  11 8七歩打     ( 0:01/00:00:22)
  12 8四飛(86)   ( 0:01/00:00:06)
*<fight>0 定跡手</fight>
  13 投了

変換後のファイル

先手:あなた
後手:激指<居飛車> 初段
1 26歩(27)
2 84歩(83)
3 25歩(26)
4 85歩(84)
5 78金(69)
6 32金(41)
7 38銀(39)
8 86歩(85)
9 86歩(87)
10 86飛(82)
11 87歩打
12 84飛(86)
13 投了
lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1
lnsgkgsnl/1r5b1/ppppppppp/9/9/7P1/PPPPPPP1P/1B5R1/LNSGKGSNL w - 2
lnsgkgsnl/1r5b1/p1ppppppp/1p7/9/7P1/PPPPPPP1P/1B5R1/LNSGKGSNL b - 3
lnsgkgsnl/1r5b1/p1ppppppp/1p7/7P1/9/PPPPPPP1P/1B5R1/LNSGKGSNL w - 4
lnsgkgsnl/1r5b1/p1ppppppp/9/1p5P1/9/PPPPPPP1P/1B5R1/LNSGKGSNL b - 5
lnsgkgsnl/1r5b1/p1ppppppp/9/1p5P1/9/PPPPPPP1P/1BG4R1/LNS1KGSNL w - 6
lnsgk1snl/1r4gb1/p1ppppppp/9/1p5P1/9/PPPPPPP1P/1BG4R1/LNS1KGSNL b - 7
lnsgk1snl/1r4gb1/p1ppppppp/9/1p5P1/9/PPPPPPP1P/1BG3SR1/LNS1KG1NL w - 8
lnsgk1snl/1r4gb1/p1ppppppp/9/7P1/1p7/PPPPPPP1P/1BG3SR1/LNS1KG1NL b - 9
lnsgk1snl/1r4gb1/p1ppppppp/9/7P1/1P7/P1PPPPP1P/1BG3SR1/LNS1KG1NL w P 10
lnsgk1snl/6gb1/p1ppppppp/9/7P1/1r7/P1PPPPP1P/1BG3SR1/LNS1KG1NL b Pp 11
lnsgk1snl/6gb1/p1ppppppp/9/7P1/1r7/PPPPPPP1P/1BG3SR1/LNS1KG1NL w p 12
lnsgk1snl/6gb1/p1ppppppp/1r7/7P1/9/PPPPPPP1P/1BG3SR1/LNS1KG1NL b p 13

やっていることは前回の記事の延長です。
touch-sp.hatenablog.com
今回は複数のKIFファイルに対して一括で変換できるように変更を加えました。

使い方

「kif」フォルダと「sfen」フォルダを同一フォルダに準備します。
「kif」フォルダにKIF形式で保存した棋譜データを保存します。
後述のPythonファイルを実行します。
これによってKIFファイルと同一名のSFENファイル(拡張子sfen、中身はただのテキストファイル)が作成されます。

これで何ができるか?

自身の棋譜データを用いて局面検索ができるようになります。そしてその局面でどういった指し手を選択したかが一目瞭然です。

Pythonファイル

import re
import collections
import glob, os

###########最初に設定しておくもの##################################
trans1 = ['1', '2', '3', '4', '5', '6', '7', '8', '9']
trans2 = ['一', '二', '三', '四', '五', '六', '七', '八', '九']

koma_moji = ['飛', '角', '金', '銀', '桂', '香', '歩']
koma_kigo = ['r', 'b', 'g', 's', 'n', 'l', 'p']

koma_kigo2 = [x.swapcase() for x in koma_kigo] + koma_kigo

def make_sfen(retu):    
    for i in range(9, 0, -1):
        retu = retu.replace('0'*i, str(i))
    return retu
#################################################################

files = glob.glob('kif/*.kif')
exist_kif_files = [os.path.splitext(os.path.basename(x))[0] for x in files]

files = glob.glob('sfen/*.sfen')
exist_sfen_files = [os.path.splitext(os.path.basename(x))[0] for x in files]

not_yet_files = list(set(exist_kif_files) - set(exist_sfen_files))

for each_file in not_yet_files:

    with open('kif/%s.kif'%each_file, 'r') as f:
        kifu = f.read()

    for x in range(9):
        kifu = kifu.replace(trans1[x], str(x+1))
        kifu = kifu.replace(trans2[x], str(x+1))

    kifu = kifu.replace('\u3000', '')

    m = re.search(r'^先手:.*$', kifu, flags = re.MULTILINE)
    sente = m.group()

    m = re.search(r'^後手:.*$', kifu, flags = re.MULTILINE)
    gote = m.group()

    m = re.finditer(r'^\s*[0-9]+\s+(\S+).*$', kifu, flags=re.MULTILINE)

    sashite = [x.groups()[0] for x in m]

    for x in range(1, len(sashite)):
        if ('同' in sashite[x]):
            sashite[x] = sashite[x].replace('同', sashite[x-1][:2])

    #SFENリスト
    sfen = []

    #持ち駒
    mochigoma = []

    #局面データをlistに変換
    kyokumen = 'lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL'
    sfen.append(kyokumen + ' ' + 'b' + ' ' + '-' + ' ' + '1')
    for i in range(1, 10):
        kyokumen = kyokumen.replace(str(i), '0'*i)
    kyokumen = [list(x) for x in kyokumen.split('/')]

    #駒を動かす
    for i, each_sashite in enumerate(sashite):

        if each_sashite == '投了':
            break

        if each_sashite[-1] != '打': #駒を動かすときの処理
            
            move = re.match(r'^(\d+)(\D+)\((\d+).*$', each_sashite).groups()

            after_x = 9 - int(move[0][0])
            after_y = int(move[0][1]) - 1 

            before_x = 9 - int(move[2][0])
            before_y = int(move[2][1]) - 1

            active_koma = kyokumen[before_y][before_x]
            
            #移動元は空きになる
            kyokumen[before_y][before_x] = '0'

            #「成」ならば「+」をつける
            if move[1][-1] == '成':
                active_koma = '+' + active_koma

            #移動先に駒があれば持ち駒とする
            #大文字、小文字は入れ替える必要がある
            if kyokumen[after_y][after_x] != '0':
                mochigoma.append(kyokumen[after_y][after_x][-1].swapcase())
            
            #移動先に駒をセットする
            kyokumen[after_y][after_x] = active_koma

        else: #駒を打つときの処理

            after_x = 9 - int(each_sashite[0])
            after_y = int(each_sashite[1]) -1

            active_koma = koma_kigo[koma_moji.index(each_sashite[2])]

            if i % 2 == 0: #先手が駒を打つ
                active_koma = active_koma.upper()

            kyokumen[after_y][after_x] = active_koma

            mochigoma.remove(active_koma)
        
        #SFENリストに保存    
        mochigoma_dict = collections.Counter(''.join(mochigoma))

        sfen_mochigoma = ''
        for x in koma_kigo2:
            if mochigoma_dict[x] == 1:
                sfen_mochigoma += x
            elif mochigoma_dict[x] > 1:
                sfen_mochigoma += (str(mochigoma_dict[x]) + x)
        
        if sfen_mochigoma =='':
            sfen_mochigoma = '-'

        sfen.append('/'.join([make_sfen(''.join(x)) for x in kyokumen]) 
                + ' ' + ('w' if i % 2 == 0 else 'b')
                + ' ' + sfen_mochigoma 
                + ' ' + str(i + 2))

    with open('sfen/%s.sfen'%each_file, "w") as f:
        f.write(sente + '\n')
        f.write(gote + '\n')
        f.write('\n'.join([str(i+1) + ' ' + x for i, x in enumerate(sashite)]))
        f.write('\n')
        f.write('\n'.join(sfen))
    
    print('convert %s.kif file'%each_file)

次にやるべきこと

検索してその結果を表示するスクリプトを書く必要があります。また今度やる予定です。

最後に

本当にやるべきことは純粋な将棋の勉強だと思います。初段をめざすと目標を立てたのですが今やっていることは将棋をネタとしてPythonの勉強をしているだけのような気がします(笑)。

2021年7月14日追記

局面検索を可能にするスクリプトを書きました。
良かったら読んでください。
touch-sp.hatenablog.com