経緯
今週から将棋AI作りを始めた。以前も取り組んでいた時期があったのだが、将棋盤GUIとの連携などでしんどくなってやめてしまった。
学習データを準備するべくデータベース(Shogi DB2 - 無料の棋譜サービス 将棋DB2)を見てみると、将棋AI界で標準とされているSFEN形式でダウンロードできないではないか。仕方がないので、KIF形式からSFEN形式への変換プログラムを作成した。
概要
要するに、「1 2六歩(27) (00:00/00:00:00)」→「2g2f」の変換を行う。
実装はC#で行った。Visual Basicなどをインストールすればすぐにビルドができるので、よければ使ってみてほしい。
ネットに公開したときに便利だろうと、最初はPowershellで作ろうと思っていた。しかし、意外と複雑な処理が求められたためC#に切り替えた。
ソースコードは長いので最後に載せる。
使い方
アプリを任意のフォルダに配置し、そのフォルダでコマンドプロンプトを開く。
コマンドプロンプト上で "KIF2SFEN > test.txt" を実行する。
アプリ実行中になると、こんな画面がでる。
棋譜を貼り付ける前に "s" と一文字打って実行(Enter)する。これによって、出力テキストファイルに "startpos" という文字が出力されるので、対局開始の表示になる。
将棋DB2(Shogi DB2 - 無料の棋譜サービス 将棋DB2)からKIF形式のソースコードをコピーし、そのまま貼り付けてEnterキーを押す。
続けて棋譜を入力したいときには、"s"コマンドを実行していったん区切ってから、次の棋譜を入力する。
使い終わったら"quit"コマンドでアプリを終了できる。
同じフォルダに出力されている test.txt を見てみると、棋譜がSFEN形式で出力されている。
コマンド一覧
- s : "startpos"を結果に出力する。棋譜と棋譜の間にはこれを実行して区切る。
- KIF表現 : 将棋DB2の表現に則り、先頭が数字である文字列をこれと認識する。
- quit : アプリを終了する。
- その他 : 上記以外の文字列はすべて無視される。
なぜKIF形式なのか
将棋DB2はKIF形式のほかにもCSA形式とKI2形式に対応している。これらの中からKIF形式を選んだ理由も記載しておく。
KIF形式で最も処理上優れていると思ったのは、駒打ちを明示してくれるところである。6五に移動できる銀があったとしても、「6五銀打」と表現してくれる。また、移動前の駒の位置を明示してくれる点も、SFENと通ずる。
ソースコード
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Xml.XPath;
using System.Text.RegularExpressions;
namespace KIF2SFEN
{
internal class Program
{
static void Main(string[] args)
{
string cmd;
// "同○○"の記法に対応するためループ外で定義
int destX = 0; //駒移動の終点X(1~9)
int destY = 0; //駒移動の終点Y(1~9)
while ((cmd = Console.ReadLine()) != null)
{
string[] kif = cmd.Split(' '); // KIFコマンド
// アプリを終了するコマンド
if (cmd == "quit")
{
break;
}
// 対局同士を区切るためのコマンド
else if (cmd == "s")
{
Console.WriteLine("startpos");
}
// メインとなる変換処理
// 先頭が数字でない行は無視する
else if (Regex.IsMatch(kif[0], @"^\d+$"))
{
int originX = 0; //駒移動の起点X(1~9)
int originY = 0; //駒移動の起点Y(1~9)
char piece = ' '; // 駒種
Boolean isPromoted; // 成り移動の場合のみTRUE
string result = ""; // 結果
// 投了の場合
if (kif[1] == "投了")
{
result = "resign";
}
// 駒打ちの場合
else if (kif[1].Contains("打"))
{
// 先手番の場合
if (int.Parse(kif[0]) % 2 == 1)
{
// 打たれた駒種の判別
string koma = new StringInfo(kif[1]).SubstringByTextElements(2, 1);
switch (koma)
{
case "歩":
piece = 'P'; break;
case "香":
piece = 'L'; break;
case "桂":
piece = 'N'; break;
case "銀":
piece = 'S'; break;
case "金":
piece = 'G'; break;
case "角":
piece = 'B'; break;
case "飛":
piece = 'R'; break;
}
}
// 後手番の場合
else
{
// 打たれた駒種の判別
string koma = new StringInfo(kif[1]).SubstringByTextElements(2, 1);
switch (koma)
{
case "歩":
piece = 'p'; break;
case "香":
piece = 'l'; break;
case "桂":
piece = 'n'; break;
case "銀":
piece = 's'; break;
case "金":
piece = 'g'; break;
case "角":
piece = 'b'; break;
case "飛":
piece = 'r'; break;
}
}
// 打たれた場所の特定
string firstChar = new StringInfo(kif[1]).SubstringByTextElements(0, 1);
string secondChar = new StringInfo(kif[1]).SubstringByTextElements(1, 1);
// 1文字目はそのまま数字に変換する
switch (firstChar)
{
case "1":
destX = 1; break;
case "2":
destX = 2; break;
case "3":
destX = 3; break;
case "4":
destX = 4; break;
case "5":
destX = 5; break;
case "6":
destX = 6; break;
case "7":
destX = 7; break;
case "8":
destX = 8; break;
case "9":
destX = 9; break;
}
// 2文字目の漢数字部分を変換する
switch (secondChar)
{
case "一":
destY = 1; break;
case "二":
destY = 2; break;
case "三":
destY = 3; break;
case "四":
destY = 4; break;
case "五":
destY = 5; break;
case "六":
destY = 6; break;
case "七":
destY = 7; break;
case "八":
destY = 8; break;
case "九":
destY = 9; break;
}
// 結果文字列の組み立て
result = piece.ToString() + "*" + destX.ToString() + ((char)(destY + 'a' - 1)).ToString();
}
// 駒移動の処理
else
{
// "同○○"でない場合は移動先を更新する
if (!kif[1].Contains("同"))
{
string firstChar = new StringInfo(kif[1]).SubstringByTextElements(0, 1);
string secondChar = new StringInfo(kif[1]).SubstringByTextElements(1, 1);
// 1文字目はそのまま数字に変換する
switch (firstChar)
{
case "1":
destX = 1; break;
case "2":
destX = 2; break;
case "3":
destX = 3; break;
case "4":
destX = 4; break;
case "5":
destX = 5; break;
case "6":
destX = 6; break;
case "7":
destX = 7; break;
case "8":
destX = 8; break;
case "9":
destX = 9; break;
}
// 2文字目の漢数字部分を変換する
switch (secondChar)
{
case "一":
destY = 1; break;
case "二":
destY = 2; break;
case "三":
destY = 3; break;
case "四":
destY = 4; break;
case "五":
destY = 5; break;
case "六":
destY = 6; break;
case "七":
destY = 7; break;
case "八":
destY = 8; break;
case "九":
destY = 9; break;
}
}
// 移動元の読み取り
int index = kif[1].IndexOf('(') + 1;
originX = kif[1][index] - '0';
originY = kif[1][index + 1] - '0';
// 成り移動かどうかを取得
isPromoted = new StringInfo(kif[1]).SubstringByTextElements(3, 1) == "成";
// 結果文字列の組み立て
result = originX.ToString() + ((char)(originY + 'a' - 1)).ToString() + destX.ToString() + ((char)(destY + 'a' - 1)).ToString() + (isPromoted ? "+" : "");
}
// 結果文字列の出力
Console.WriteLine(result);
}
}
}
}
}