NHK 杯囲碁トーナメントの棋譜ファイルを SGF 形式で入手する:Part 2. SGF 形式への変換
前回は公式サイトから棋譜データを入手する方法を紹介した。今回は入手した棋譜データの構造と SGF 形式に変換する方法を述べる。
データ構造
例として 2013 年 1 月 27 日の棋譜データ http://cgi2.nhk.or.jp/goshogi/kifu/score.cgi?d=20130127&t=i を見てみよう。
NIP100; OnAir=2013/01/27; Title=第60回NHK杯; Stage=3回戦第7局; Start=2012/12/10; End=2012/12/10; Player1=山田 規三生 九段; Player2=山下 敬吾 名人; Result=1; p=0000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000pd; h1=0; h2=0; p=0000000000000000000000000000000000000000000000000000000000002000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dd; h1=0; h2=0; (中略) END
棋譜データは キー=値;
と表されるプロパティの集合から成る。プロパティのうち、SGF と対応するものを以下に示す。
Title
イベント名。SGF では EV[第60回NHK杯]
。
Stage
ゲーム名。SGF では GN[3回戦第7局]
。
Start
対局開始日。ほとんどの場合 End
と同じだろうから、対局日と見なしても良いだろう。この場合、SGF では DT[2012-12-10]
。
Player1
黒番の名前・ランク。値の書式は 姓 名 ランク
。SGF では PB[山田 規三生]
と BR[九段]
。
Player2
白番の名前・ランク。SGF では PW[山下 敬吾]
と WR[名人]
。
Result
結果。値の書式は x(:y)?
。
x
はどちらの棋士が勝ったか。1
ならば黒番の勝ち、2
ならば白番の勝ち。いずれでもなければ持碁。
y
は勝ち方。t
ならば時間切れ勝ち、0 以上の整数ならば y + 0.5 目勝ち。y
が省略されていれば中押し勝ち。
この場合、SGF では RE[B+R]
。
p
着手。値のうち最初の 361 桁の数字は盤面の状態を表す。後ろの小文字アルファベットは着手の座標で、SGF と互換。SGF では、最初は B[pd]
で、次は W[dd]
。以下同様に続く。
変換プログラム
以上の規則を基に SGF 形式への変換プログラムを書いた(score2sgf.rb)。使用法は
ruby score2sgf.rb ./score/score-60/20130127.txt
など。拡張子が .sgf
に変わった同名の SGF ファイルが、棋譜データファイルのあるディレクトリに出力される。
一括変換は、例えば bash の場合
for f in ./score/score-60/*.txt; do ruby score2sgf.rb $f done
のように行えばよい。
# coding: utf-8 unless ARGV.length == 1 puts "usage: ruby score2sgf.rb FILE" abort end file_name_score = ARGV[0] file_name_sgf = file_name_score.sub(/\.[^.]+$/, ".sgf") # SGF のルートノード root = { FF: 4, CA: "utf-8", GM: 1, SZ: 19, KM: 6.5 } # 棋譜ファイルの読み込み score = nil File.open(file_name_score) {|f| score = f.read.gsub(/[\r\n]/, "")} props_score = score.split(";").map {|p| p.split("=")} # 棋譜データの解析 moves = [] move_num = 0 props_score.each do |p| case p[0] when "Title" root[:EV] = p[1] when "Stage" root[:GN] = p[1] when "Start" root[:DT] = p[1].gsub("/", "-") when "Player1" # 黒の棋士 b_rank = "" b_name = p[1].sub(/ ([^ ]+)$/) do b_rank = $1 "" end root[:PB] = b_name root[:BR] = b_rank when "Player2" # 白の棋士 w_rank = "" w_name = p[1].sub(/ ([^ ]+)$/) do w_rank = $1 "" end root[:PW] = w_name root[:WR] = w_rank when "Result" result = p[1].split(":") case result[0] when "1" # 黒の勝ち if result[1] if result[1] == "t" # 時間切れ root[:RE] = "B+T" else # 作り碁 root[:RE] = "B+#{result[1].to_f + 0.5}" end else # 中押し勝ち root[:RE] = "B+R" end when "2" # 白の勝ち if result[1] if result[1] == "t" # 時間切れ root[:RE] = "W+T" else # 作り碁 root[:RE] = "W+#{result[1].to_f + 0.5}" end else # 中押し勝ち root[:RE] = "W+R" end else # 持碁 root[:RE] = 0 end when "p" move_num += 1 c = p[1].sub(/\d+/, "") if move_num.odd? moves << {B: c} else moves << {W: c} end end end # SGF の生成・書き出し def sgf_node(prop) s = ";" prop.each {|k, v| s << "#{k}[#{v}]"} s end sgf = "(" + sgf_node(root) moves.each {|p| sgf << sgf_node(p)} sgf << ")" File.open(file_name_sgf, "w:utf-8") {|f| f.print(sgf)} puts "converted #{file_name_score} to #{file_name_sgf}"