今回はGGFファイルを対象にRubyで局面と評価値のセットをCSVファイルに抽出するスクリプトを作成するところまで。学習がうまくいったら続きを書きます。
GGSの棋譜
今回は大量のリバーシプログラム同士の対局が記録されているGGSの棋譜
https://www.skatgame.net/mburo/ggs/game-archive/Othello/?C=N;O=A
を使用しました。
これらはGGFというフォーマットのファイルになっているのですが、自分の目的に合ったデータを抽出するために既存のソフトウェアは使用せず読み込みスクリプトを書きます。GGFについては次章へ。
GGFファイル
Generic Game Format の略でリバーシに限らず様々な盤ゲームの棋譜を記述できるフォーマットです。
フォーマットの詳細は
https://skatgame.net/mburo/ggsa/ggf
に書かれています。
簡単にまとめると各対戦プレイヤーの情報と試合結果、盤の大きさ、盤の初期状態、終局までの指し手などを記録できます。
リバーシプログラム同士の対局なので、指し手の情報に局面の評価値が付属しています(評価値付きとそうでないものが混在)。
GGSファイルのひとつ Othello.01e4.ggf から選んだ1試合分の例を以下に示します。
わかりやすいように改行していますが、実際はこれが1行に繋がっていて1行1試合となっています。
(; GM[Othello]PC[GGS/os]DT[2000-6-25 06:30 EST] PB[scorpion]PW[stepmose] RB[1883.42]RW[1127.31] TI[15:00//02:00]TY[8]RE[+40.00] BO[8 -------- -------- -------- ---O*--- ---*O--- -------- -------- -------- *] B[E6/-1.00]W[F4//0.67]B[D3/-6.38/0.50]W[D6//0.78]B[E3/-20.64]W[F6//0.73]B[E7/-2.64]W[C4//2.61]B[F5/-2.70]W[F8//5.50]B[C3/11.59/0.01]W[F7//3.84]B[F3/10.12/0.02]W[C2//2.97]B[C5/12.37/0.02]W[C6//3.04] B[B4/11.11/0.04]W[A3//6.66]B[B3/13.95/0.04]W[A4//3.85]B[B5/18.65/0.03]W[G6//13.00]B[D2/20.50/0.04]W[E1//8.15]B[G5/25.84/0.04]W[E2//9.48]B[F2/26.27/0.04]W[F1//7.53]B[G4/30.60/0.05]W[H3//4.33]B[H4/30.56/0.05]W[H6//6.39] B[H2/28.59/0.04]W[G3//3.62]B[G2/30.05/0.03]W[A6//4.34]B[H5/32.95/0.03]W[B6//1.67]B[H7/34.04/0.03]W[H1//0.74]B[G7/33.89/0.02]W[H8//0.27]B[D8/34.76/0.03]W[D7//0.36]B[B2/37.34/0.03]W[A1//0.22]B[G8/39.52/0.03]W[pass//0.17] B[C8/34.89/0.02]W[E8//0.95]B[B1/38.86/0.02]W[D1//0.38]B[C1/40.92/0.01]W[B8//0.18]B[A8/41.68/0.01]W[pass//0.17]B[A7/40.00/0.01]W[B7//0.17]B[C7/40.00/0.01]W[pass//0.17]B[A5/40.00/0.01]W[pass//0.17]B[A2/40.00/0.01]W[pass//0.17]B[G1] ;)
読み込みスクリプト
ここから局面と評価値のセットをCSVファイルに抽出するスクリプトを書きます。
GGFファイルからは局面の状態まではわからないので、指し手の履歴を元に実際に局面を進めて評価値の情報があればそれを記録する、というようにします。学習自体はRubyで行わないので他の言語でも比較的読み込みやすいCSVファイルにしました。
局面をつくるためにRubyでリバーシ関連のもろもろができるgem、reversiを使用します。
手前味噌ですが、このgemは大昔に私がつくったものです。
今改めてみると肝心の局面を取り出すのがとても面倒であることに気が付きました。
(他にも直したい箇所がたくさん、、、)
局面は黒石、白石、空白のビットボードにしました。
評価値についてですが、GGFでは黒番も白番も勝勢であるほど正の評価値となっています。プレイヤーの色もセットで記録しなければいけないのは面倒なので、白番が勝っているほど負、黒番が勝っているほど正というように統一するために白番の評価値の符号を反転させています。
動作環境
- Windows 10 64ビット
- Ruby 2.1.5
- reversi 2.0.4
$ gem install reversi
Gistにも同じコードをアップしてあります。
https://gist.github.com/seinosuke/a481196230676e038e779fdd968feed5
require "reversi" # 盤面の状態を出力する際の見た目設定 Reversi.configure do |config| config.disk_color_b = 'cyan' config.disk_b = "O" config.disk_w = "O" end # 初期状態から始まっている8x8盤面のみ読み込む valid_board = /BO\[8 -------- -------- -------- ---O\*--- ---\*O--- -------- -------- -------- \*\]/ # 読み込むGGFファイル ggf_path = "./hoge.ggf" File.open(ggf_path, "r") do |ggf_file| # 同じ階層に読み込み結果のcsvファイルを生成 csv_path = ggf_path << ".csv" File.open(csv_path, "w") do |csv_file| ggf_file.each_line do |line| # 初期状態が不正なものは除く next unless line.match(valid_board) # 大文字に統一し着手履歴のみ抽出 moves_info = line .gsub("(;", "").gsub(";)", "").chomp .gsub("]/", "/").upcase .split("*]").last.split("]") # 着手履歴から試合を再生 game = Reversi::Game.new moves_info.each do |move_info| col, info = move_info.split("[") info = info.split("/") move = info.first # 一手進める unless ["PASS", "PA"].include?(move) move = move.tr("A-H", "1-8").split("")[0..1].map(&:to_i) case col when "B" then game.player_b.put_disk(*move) when "W" then game.player_w.put_disk(*move) end game.board.push_stack end # 評価値と局面のセットを記録 if (info.size > 1) && (!info[1].empty?) val = info[1].to_f val = -val if col == "W" b_bb = game.board.stack.last[:black] w_bb = game.board.stack.last[:white] e_bb = 0xFFFF_FFFF_FFFF_FFFF ^ (b_bb | w_bb) csv_file.puts "#{b_bb}, #{w_bb}, #{e_bb}, #{val}" end end # puts game.board end end end
これはあくまで自分用の一例なので、目的が異なる方などはご自分でスクリプトを書いてみるのもよいかもしれません。
参考までに着手履歴の部分で私が発見したパターンを以下に挙げておきます。
小文字と大文字が混在しており、パスの表現だけでも pass, pa, PAがあります。
手番[着手/評価値(任意)/経過時間(任意)] W[G5/-52.60/0.01] B[H3//4.17] B[pass//1.17] W[h4/-10.42] W[B2] B[pa/-2.00] W[PA//2.20] B[d6]//1.22]
おわりに
とりあえず150ほどあるファイル全てをエラーなく読み込めました。
次回は学習編でお会いしましょう。
参考
丁度同じサイトのGGFファイルの取り扱いに難儀していたので大変参考になりました。学習編も楽しみにしています。
返信削除コメントいただきありがとうございます。
削除私もネット上に情報が少ないと思ったので、お役にたててなによりです。