【Python】【将棋ネタ】KIF形式の棋譜データから一括ですべての局面をSFEN形式で取得する

はじめに

前回KIF形式で保存された将棋の棋譜ファイルから指し手を抽出しました。
touch-sp.hatenablog.com
今回は抽出した指し手をPC上で再現し、それぞれの局面をSFEN形式で保存してみます。
棋譜データからすべての局面を一括で取得する方法になります。
これを可能にするフリーソフトが見つけられなかったので自分で作ることにしました。

SFEN形式について

以下の規則を知っておく必要があります。

p	歩	ポーン		(pawn)
l	香 	ランス		(lance)
n	桂	ナイト		(knight)
s	銀	シルバー	(silver)
g	金	ゴールド	(gold)
k	王、玉	キング		(king)
r	飛	ルック		(rook)
b	角	ビショップ	(bishop)
  • 先手側は大文字、後手側は小文字
  • 成っている場合は先頭に「+」をつける
  • 持ち駒を表記する順番は飛→角→金→銀→桂→香→歩
  • 同じ種類の持ち駒は先頭に個数をつける
  • 両者持ち駒がない場合には「-」と表記する

  • 横方向に連続する空きますは数字に変換

Pythonスクリプト

import re
import collections
import sys

###########最初に設定しておくもの##################################
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
#################################################################

args = sys.argv
input_file = args[1]
output_file = args[2]

with open(input_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', '')

sashite = [x.groups()[0] 
    for x in re.finditer('^\s*[0-9]+\s+(\S+).*$', 
    kifu, 
    flags=re.MULTILINE)]

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

if ('投了' in sashite):
    sashite.remove('投了')

#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[-1] != '打': #駒を動かすときの処理
        
        move = re.match('^(\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(output_file, "w") as f:
    f.write('\n'.join(sfen))

使い方

読み込む棋譜データと出力先を記述して実行します。
「sample.kif」ファイルを読み込んで「output.sfen」ファイルに出力する場合を示します。
上記スクリプトは今回「kifu.py」という名前になっています。

python kifu.py sample.kif output.sfen

「output.sfen」は拡張子をsfenとしていますがただのテキストファイルです。

2021年7月14日追記

続きを書きました。良かったら読んでください。
touch-sp.hatenablog.com