Lahの部屋

移植予定です→https://lah-blog.pages.dev/

【将棋AI】KIF→SFEN 変換プログラムを作った

経緯

 今週から将棋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" という文字が出力されるので、対局開始の表示になる。

 将棋DB2Shogi DB2 - 無料の棋譜サービス 将棋DB2)からKIF形式のソースコードをコピーし、そのまま貼り付けてEnterキーを押す。

 続けて棋譜を入力したいときには、"s"コマンドを実行していったん区切ってから、次の棋譜を入力する。

 使い終わったら"quit"コマンドでアプリを終了できる。

 同じフォルダに出力されている test.txt を見てみると、棋譜がSFEN形式で出力されている。

 

コマンド一覧

  • s : "startpos"を結果に出力する。棋譜棋譜の間にはこれを実行して区切る。
  • KIF表現 : 将棋DB2の表現に則り、先頭が数字である文字列をこれと認識する。
  • quit : アプリを終了する。
  • その他 : 上記以外の文字列はすべて無視される。

 

なぜKIF形式なのか

 将棋DB2KIF形式のほかにも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);
                }
            }
        }
    }
}