/// <summary> /// CSA形式の棋譜ファイルのparser /// エラーがあった場合は、そのエラーの文字列が返る。 /// エラーがなければstring.Emptyが返る。 /// </summary> private string FromCsaString(string[] lines, KifuFileType kf) { // 消費時間、残り時間、消費時間を管理する。 var timeSettings = KifuTimeSettings.TimeLimitless; var times = timeSettings.GetInitialKifuMoveTimes(); var lineNo = 1; /* * 例) * * V2.2 * N+人間 * N-人間 * P1-KY-KE-GI-KI-OU-KI-GI-KE-KY * P2 * -HI * * * * * -KA * * P3-FU-FU-FU-FU-FU-FU-FU-FU-FU * P4 * * * * * * * * * * P5 * * * * * * * * * * P6 * * * * * * * * * * P7+FU+FU+FU+FU+FU+FU+FU+FU+FU * P8 * +KA * * * * * +HI * * P9+KY+KE+GI+KI+OU+KI+GI+KE+KY * P+ * P- + +7776FU,T3 + -8384FU,T1 */ string line = string.Empty; var posLines = new List <string>(); var headFlag = true; KifuMove lastKifuMove = null; var move = Move.NONE; var rTimeLimit = new Regex(@"([0-9]+):([0-9]+)\+([0-9]+)"); var rEvent = new Regex(@"^[0-9A-Za-z-_]+\+[0-9A-Za-z_]+-([0-9]+)-([0-9]+)(F?)\+"); for (; lineNo <= lines.Length; ++lineNo) { line = lines[lineNo - 1]; // コメント文 if (line.StartsWith("'")) { Tree.currentNode.comment += line.Substring(1).TrimEnd('\r', '\n') + "\n"; continue; } // セパレータ検出 if (line.StartsWith("/")) { // "/"だけの行を挟んで、複数の棋譜・局面を記述することができる。 // 現時点ではこの書式に対応せず、先頭の棋譜のみを読み込む。 // そもそも初期局面が異なる可能性もあり、Treeを構成できるとは限らないため。 break; } // マルチステートメント検出 string[] sublines = line.Split(','); foreach (var subline in sublines) { if (subline.StartsWith("$")) { // 棋譜ヘッダ var keyvalue = subline.Substring(1).Split(":".ToCharArray(), 2); if (string.IsNullOrWhiteSpace(keyvalue[0])) { continue; } var key = keyvalue[0]; var value = keyvalue[1] ?? ""; KifuHeader.header_dic[key] = value; switch (key) { case "TIME_LIMIT": var mTimeLimit = rTimeLimit.Match(value); if (mTimeLimit.Success) { int.TryParse(mTimeLimit.Groups[1].Value, out int hour); int.TryParse(mTimeLimit.Groups[2].Value, out int minute); int.TryParse(mTimeLimit.Groups[3].Value, out int byoyomi); timeSettings = new KifuTimeSettings( new KifuTimeSetting[] { new KifuTimeSetting() { Hour = hour, Minute = minute, Second = 0, Byoyomi = byoyomi, ByoyomiEnable = true, IncTime = 0 }, new KifuTimeSetting() { Hour = hour, Minute = minute, Second = 0, Byoyomi = byoyomi, ByoyomiEnable = true, IncTime = 0 }, }, false ); times = timeSettings.GetInitialKifuMoveTimes(); Tree.SetKifuMoveTimes(times.Clone()); // root局面での残り時間の設定 Tree.KifuTimeSettings = timeSettings.Clone(); } break; case "EVENT": // floodgate特例、TIME_LIMITが設定されていない時にEVENT文字列から時間設定を拾う if (KifuHeader.header_dic.ContainsKey("TIME_LIMIT")) { continue; } var mEvent = rEvent.Match(value); if (mEvent.Success) { int.TryParse(mEvent.Groups[1].Value, out int initial); int.TryParse(mEvent.Groups[2].Value, out int add); if (mEvent.Groups[3].Value == "F") { timeSettings = new KifuTimeSettings( new KifuTimeSetting[] { new KifuTimeSetting() { Hour = 0, Minute = 0, Second = initial, Byoyomi = 0, ByoyomiEnable = false, IncTime = add, IncTimeEnable = true }, new KifuTimeSetting() { Hour = 0, Minute = 0, Second = initial, Byoyomi = 0, ByoyomiEnable = false, IncTime = add, IncTimeEnable = true }, }, false ); } else { timeSettings = new KifuTimeSettings( new KifuTimeSetting[] { new KifuTimeSetting() { Hour = 0, Minute = 0, Second = initial, Byoyomi = add, ByoyomiEnable = true, IncTime = 0, IncTimeEnable = false }, new KifuTimeSetting() { Hour = 0, Minute = 0, Second = initial, Byoyomi = add, ByoyomiEnable = true, IncTime = 0, IncTimeEnable = false }, }, false ); } times = timeSettings.GetInitialKifuMoveTimes(); Tree.SetKifuMoveTimes(times.Clone()); // root局面での残り時間の設定 Tree.KifuTimeSettings = timeSettings.Clone(); } break; } continue; } if (subline.StartsWith("N+")) { KifuHeader.PlayerNameBlack = subline.Substring(2); continue; } if (subline.StartsWith("N-")) { KifuHeader.PlayerNameWhite = subline.Substring(2); continue; } if (subline.StartsWith("P")) { posLines.Add(subline); continue; } if (subline.StartsWith("+") || subline.StartsWith("-")) { if (headFlag) { // 1回目は局面の先後とみなす headFlag = false; posLines.Add(subline); Tree.rootSfen = CsaExtensions.CsaToSfen(posLines.ToArray()); Tree.position.SetSfen(Tree.rootSfen); continue; } // 2回目以降は指し手とみなす // 消費時間の情報がまだないが、取り敢えず追加 move = Tree.position.FromCSA(subline); Tree.AddNode(move, times.Clone()); lastKifuMove = Tree.currentNode.moves.FirstOrDefault((x) => x.nextMove == move); // 特殊な指し手や不正な指し手ならDoMove()しない if (move.IsSpecial() || !Tree.position.IsLegal(move)) { continue; } Tree.DoMove(move); continue; } if (subline.StartsWith("T")) { // 着手時間 var state = Tree.position.State(); if (state == null) { return($"line {lineNo}: 初期局面で着手時間が指定されました。"); } long.TryParse(subline.Substring(1), out long time); var lastMove = state.lastMove; if (move == lastMove && move == lastKifuMove.nextMove) { var turn = Tree.position.sideToMove.Not(); var thinking_time = TimeSpan.FromSeconds(time); times.Players[(int)turn] = times.Players[(int)turn].Create( timeSettings.Player(turn), thinking_time, thinking_time ); lastKifuMove.kifuMoveTimes = times.Clone(); } // 特殊な指し手や不正な指し手ならDoMove()しない if (move.IsSpecial() || !Tree.position.IsLegal(move)) { continue; } Tree.DoMove(move); continue; } if (subline.StartsWith("%")) { var match = new Regex("^%[A-Z_+-]+").Match(subline); if (!match.Success) { continue; } switch (match.Groups[0].Value) { case "%TORYO": move = Move.RESIGN; break; case "%CHUDAN": move = Move.INTERRUPT; break; case "%SENNICHITE": move = Move.REPETITION_DRAW; break; case "%TIME_UP": move = Move.TIME_UP; break; case "%JISHOGI": move = Move.MAX_MOVES_DRAW; break; case "%KACHI": move = Move.WIN; break; case "%TSUMI": move = Move.MATED; break; case "%ILLEGAL_MOVE": move = Move.ILLEGAL_MOVE; break; case "%+ILLEGAL_ACTION": move = Tree.position.sideToMove == Color.BLACK ? Move.ILLEGAL_ACTION_LOSE : Move.ILLEGAL_ACTION_WIN; break; case "%-ILLEGAL_ACTION": move = Tree.position.sideToMove == Color.BLACK ? Move.ILLEGAL_ACTION_WIN : Move.ILLEGAL_ACTION_LOSE; break; case "%HIKIWAKE": move = Move.DRAW; break; // 以下、適切な変換先不明 case "%FUZUMI": case "%MATTA": case "%ERROR": default: move = Move.NONE; break; } Tree.AddNode(move, times.Clone()); lastKifuMove = Tree.currentNode.moves.FirstOrDefault((x) => x.nextMove == move); continue; } } } if (headFlag) // まだ局面図が終わってない { return($"CSA形式の{lineNo}行目で局面図が来ずにファイルが終了しました。"); } return(null); }
// とりあえずJSON中継棋譜形式に部分対応 private string FromLiveJsonString(string content, KifuFileType kf) { try { var times = KifuMoveTimes.Zero; var timeSettings = KifuTimeSettings.TimeLimitless.Clone(); var epoch = new DateTime(1970, 1, 1, 0, 0, 0).ToLocalTime(); DateTime?lasttime = null; var jsonObj = LiveJsonUtil.FromString(content); if (jsonObj == null || jsonObj.data == null || jsonObj.data.Count == 0) { return("有効なデータが得られませんでした"); } // 先頭のデータのみ読み込む var data = jsonObj.data[0]; { // 対局者名 if (data.side != "後手") { KifuHeader.PlayerNameBlack = data.player1; KifuHeader.PlayerNameWhite = data.player2; } else { KifuHeader.PlayerNameBlack = data.player2; KifuHeader.PlayerNameWhite = data.player1; } } if (data.realstarttime != null) { var starttime = epoch.AddMilliseconds((double)data.realstarttime); lasttime = starttime; Tree.RootKifuLog.moveTime = starttime; Tree.rootNode.comment = starttime.ToString("o"); } else if (!string.IsNullOrWhiteSpace(data.starttime)) { Tree.RootKifuLog.moveTime = DateTime.ParseExact(data.starttime, "s", null); } if (!string.IsNullOrWhiteSpace(data.timelimit) && int.TryParse(data.timelimit, out int time_limit)) { timeSettings = new KifuTimeSettings( new KifuTimeSetting[] { new KifuTimeSetting() { Hour = 0, Minute = time_limit, Second = 0 }, new KifuTimeSetting() { Hour = 0, Minute = time_limit, Second = 0 }, }, false ); } if (!string.IsNullOrWhiteSpace(data.countdown) && int.TryParse(data.countdown, out int countdown)) { foreach (var players in timeSettings.Players) { if (players.TimeLimitless) { players.Hour = 0; players.Minute = 0; players.Second = 0; players.TimeLimitless = false; } players.Byoyomi = countdown; players.ByoyomiEnable = true; } } // 残り時間の計算用。 times = timeSettings.GetInitialKifuMoveTimes(); Tree.SetKifuMoveTimes(times.Clone()); // root局面での残り時間の設定 Tree.KifuTimeSettings = timeSettings.Clone(); foreach (var kif in data.kif) { Move move; DateTime?time = null; if (kif.time != null) { time = epoch.AddMilliseconds((double)kif.time); } // 特殊な着手 if (kif.frX == null || kif.frY == null || kif.toX == null || kif.toY == null || kif.prmt == null) { switch (kif.move) { case "投了": move = Move.RESIGN; break; case "中断": case "封じ手": move = Move.INTERRUPT; break; default: return(string.Empty); } } // varidation else if (kif.frX < 1 || kif.frX > 9 || kif.toY < 0 || kif.toY > 10) { return("無効な移動元座標を検出しました"); } else if (kif.toX < 1 || kif.toX > 9 || kif.toY < 1 || kif.toY > 9) { return("無効な移動先座標を検出しました"); } // 先手駒台からの着手 else if (kif.frY == 10) { Piece pc; switch (kif.frX) { case 1: pc = Piece.PAWN; break; case 2: pc = Piece.LANCE; break; case 3: pc = Piece.KNIGHT; break; case 4: pc = Piece.SILVER; break; case 5: pc = Piece.GOLD; break; case 6: pc = Piece.BISHOP; break; case 7: pc = Piece.ROOK; break; default: return("先手の無効な駒打ちを検出しました"); } var toSq = Util.MakeSquare((File)(kif.toX - 1), (Rank)(kif.toY - 1)); move = Util.MakeMoveDrop(pc, toSq); } // 後手駒台からの着手 else if (kif.frY == 0) { Piece pc; switch (kif.frX) { case 9: pc = Piece.PAWN; break; case 8: pc = Piece.LANCE; break; case 7: pc = Piece.KNIGHT; break; case 6: pc = Piece.SILVER; break; case 5: pc = Piece.GOLD; break; case 4: pc = Piece.BISHOP; break; case 3: pc = Piece.ROOK; break; default: return("後手の無効な駒打ちを検出しました"); } var toSq = Util.MakeSquare((File)(kif.toX - 1), (Rank)(kif.toY - 1)); move = Util.MakeMoveDrop(pc, toSq); } // 通常の着手 else { var frSq = Util.MakeSquare((File)(kif.frX - 1), (Rank)(kif.frY - 1)); var toSq = Util.MakeSquare((File)(kif.toX - 1), (Rank)(kif.toY - 1)); if (kif.prmt == 1) { move = Util.MakeMovePromote(frSq, toSq); } else { move = Util.MakeMove(frSq, toSq); } } TimeSpan thinking_time = TimeSpan.FromSeconds((double)(kif.spend ?? 0)); TimeSpan realthinking_time = (time != null && lasttime != null) ? time.GetValueOrDefault().Subtract(lasttime.GetValueOrDefault()) : thinking_time; var turn = Tree.position.sideToMove; times.Players[(int)turn] = times.Players[(int)turn].Create( timeSettings.Player(turn), thinking_time, realthinking_time ); // 棋譜ツリーへの追加処理 Tree.AddNode(move, times.Clone()); if (time != null) { lasttime = time; var kifumove = Tree.currentNode.moves.Find((x) => x.nextMove == move); kifumove.moveTime = (DateTime)time; Tree.currentNode.comment = ((DateTime)time).ToString("o"); } if (move.IsSpecial()) { return(string.Empty); } if (!Tree.position.IsLegal(move)) { return($"{Tree.gamePly}手目で不正着手を検出しました"); } Tree.DoMove(move); continue; } } catch (Exception e) { return(e.ToString()); } return(string.Empty); }
/// <summary> /// Kif/KI2形式の読み込み /// </summary> /// <param name="lines"></param> /// <param name="kf"></param> /// <returns></returns> private string FromKifString(string[] lines, KifuFileType kf) { // 消費時間、残り時間、消費時間を管理する。 // TimeLimitlessの設定を書き換えてしまう恐れがあるためCloneする KifuTimeSettings timeSettings = KifuTimeSettings.TimeLimitless.Clone(); KifuMoveTimes times = KifuMoveTimes.Zero; var lineNo = 1; try { // ヘッダ検出用正規表現 var rHead = new Regex(@"^([^:]+):(.*)"); // 変化手数用正規表現 var rHenka = new Regex(@"^([0-9]+)手?"); // KIF指し手検出用正規表現 var rKif = new Regex(@"^\s*([0-9]+)\s*(?:((?:[1-91-9][1-91-9一二三四五六七八九]|同\s?)成?[歩香桂銀金角飛と杏圭全馬竜龍玉王][打不成左直右上寄引]*(?:\([1-9][1-9]\))?)|(\S+))\s*(\(\s*([0-9]+):([0-9]+(?:\.[0-9]+)?)\s*\/\s*([0-9]+):([0-9]+):([0-9]+(?:\.[0-9]+)?)\))?"); // KI2指し手検出用正規表現 var rKi2 = new Regex(@"[-+▲△▼▽☗☖⛊⛉](?:[1-91-9][1-91-9一二三四五六七八九]|同\s?)成?[歩香桂銀金角飛と杏圭全馬竜龍玉王][打不成左直右上寄引]*"); // 終局検出用正規表現 var rSpecial = new Regex(@"^まで([0-9]+)手(.+)"); // 持ち時間/秒読み検出用正規表現 var rTime = new Regex(@"^各?(\d+)(時間|分|秒)"); var bod = new List <string>(); var isBody = false; KifuHeader.header_dic.Clear(); // 初期局面の遅延処理 Func <string> lazyHead = () => { isBody = true; if (bod.Count > 0) { // 柿木将棋IXでは、初期局面指定(詰将棋など)の時でも、KIF形式で書き出すと「手合割:平手」とヘッダ出力される。 // その場合の手合割の意味が理解出来ないが、エラーを出さずに黙って初期局面図の方で上書きする。 // if (KifuHeader.header_dic.ContainsKey("手合割")) return "手合割と初期局面文字列が同時に指定されています。"; var sfen = Converter.KifExtensions.BodToSfen(bod.ToArray()); Tree.SetRootSfen(sfen); } if (KifuHeader.header_dic.ContainsKey("持ち時間")) { var mTime = rTime.Match(KifuHeader.header_dic["持ち時間"]); if (mTime.Success) { var sb = new StringBuilder(); foreach (char c in mTime.Groups[1].Value) { sb.Append((c <'0' || c> '9') ? c : (char)(c - '0' + '0')); } if (int.TryParse(sb.ToString(), out int mTimeVal)) { //if (int.TryParse(Regex.Replace(mTime.Groups[1].Value, "[0-9]", p => ((char)(p.Value[0] - '0' + '0')).ToString())), out int mTimeVal); switch (mTime.Groups[2].Value) { case "時間": timeSettings = new KifuTimeSettings( new KifuTimeSetting[] { new KifuTimeSetting() { Hour = mTimeVal, Minute = 0, Second = 0 }, new KifuTimeSetting() { Hour = mTimeVal, Minute = 0, Second = 0 }, }, false ); break; case "分": timeSettings = new KifuTimeSettings( new KifuTimeSetting[] { new KifuTimeSetting() { Hour = 0, Minute = mTimeVal, Second = 0 }, new KifuTimeSetting() { Hour = 0, Minute = mTimeVal, Second = 0 }, }, false ); break; case "秒": timeSettings = new KifuTimeSettings( new KifuTimeSetting[] { new KifuTimeSetting() { Hour = 0, Minute = 0, Second = mTimeVal }, new KifuTimeSetting() { Hour = 0, Minute = 0, Second = mTimeVal }, }, false ); break; } } } } if (KifuHeader.header_dic.ContainsKey("秒読み")) { var mTime = rTime.Match(KifuHeader.header_dic["秒読み"]); if (mTime.Success) { var sb = new StringBuilder(); foreach (char c in mTime.Groups[1].Value) { sb.Append((c <'0' || c> '9') ? c : (char)(c - '0' + '0')); } if (int.TryParse(sb.ToString(), out int mTimeVal)) { //if (int.TryParse(Regex.Replace(mTime.Groups[1].Value, "[0-9]", p => ((char)(p.Value[0] - '0' + '0')).ToString())), out int mTimeVal); foreach (var players in timeSettings.Players) { if (players.TimeLimitless) { players.Hour = 0; players.Minute = 0; players.Second = 0; players.TimeLimitless = false; } switch (mTime.Groups[2].Value) { case "時間": players.Byoyomi = int.Parse(mTime.Groups[1].Value) * 3600; players.ByoyomiEnable = true; break; case "分": players.Byoyomi = int.Parse(mTime.Groups[1].Value) * 60; players.ByoyomiEnable = true; break; case "秒": players.Byoyomi = int.Parse(mTime.Groups[1].Value); players.ByoyomiEnable = true; break; } } } } } // 残り時間の計算用。 times = timeSettings.GetInitialKifuMoveTimes(); Tree.SetKifuMoveTimes(times.Clone()); // root局面での残り時間の設定 Tree.KifuTimeSettings = timeSettings.Clone(); return(string.Empty); }; // ブロック分割走査 for (; lineNo <= lines.Length; ++lineNo) { var line = lines[lineNo - 1].Trim('\r', '\n'); // 空文 if (string.IsNullOrWhiteSpace(line)) { continue; } var firstChar = line[0]; // 無効行 if (firstChar == '#') { continue; } if (firstChar == '&') { continue; // Kifu for Windows "しおり"機能 } // 棋譜コメント文 if (firstChar == '*') { Tree.currentNode.comment += line + "\n"; continue; } // 局面図 if (firstChar == '|') { bod.Add(line); continue; } // ヘッダ検出 var mHead = rHead.Match(line); if (mHead.Success) { var headerKey = mHead.Groups[1].Value; var headerValue = mHead.Groups[2].Value; switch (headerKey) { case "先手の持駒": case "下手の持駒": case "後手の持駒": case "上手の持駒": if (isBody) { throw new KifuException("対局開始後にヘッダが指定されました。", line); } bod.Add(line); goto nextline; case "変化": if (!isBody) { throw new KifuException("初期局面からは変化できません。"); } var mHenka = rHenka.Match(headerValue); if (!mHenka.Success) { throw new KifuException("変化する手数を検出できませんでした。"); } var ply = int.Parse(mHenka.Groups[1].Value); while (ply < Tree.gamePly) { Tree.UndoMove(); } // このnodeでの残り時間に戻す times = Tree.GetKifuMoveTimes(); goto nextline; case "手合割": if (isBody) { throw new KifuException("対局開始後にヘッダが指定されました。", line); } KifuHeader.header_dic.Add(headerKey, headerValue); // 局面を指定されたBoardTypeで初期化する。 void SetTree(BoardType bt) { Tree.SetRootBoardType(bt); } switch (headerValue) { case "平手": SetTree(BoardType.NoHandicap); goto nextline; case "香落ち": SetTree(BoardType.HandicapKyo); goto nextline; case "右香落ち": SetTree(BoardType.HandicapRightKyo); goto nextline; case "角落ち": SetTree(BoardType.HandicapKaku); goto nextline; case "飛車落ち": SetTree(BoardType.HandicapHisya); goto nextline; case "飛香落ち": SetTree(BoardType.HandicapHisyaKyo); goto nextline; case "二枚落ち": SetTree(BoardType.Handicap2); goto nextline; case "三枚落ち": SetTree(BoardType.Handicap3); goto nextline; case "四枚落ち": SetTree(BoardType.Handicap4); goto nextline; case "五枚落ち": SetTree(BoardType.Handicap5); goto nextline; case "左五枚落ち": SetTree(BoardType.HandicapLeft5); goto nextline; case "六枚落ち": SetTree(BoardType.Handicap6); goto nextline; case "八枚落ち": SetTree(BoardType.Handicap8); goto nextline; case "十枚落ち": SetTree(BoardType.Handicap10); goto nextline; default: // このときlazyHead()で設定される。 break; } goto nextline; case "先手": case "下手": if (isBody) { throw new KifuException("対局開始後にヘッダが指定されました。", line); } KifuHeader.header_dic.Add(headerKey, headerValue); KifuHeader.PlayerNameBlack = headerValue; goto nextline; case "後手": case "上手": if (isBody) { throw new KifuException("対局開始後にヘッダが指定されました。", line); } KifuHeader.header_dic.Add(headerKey, headerValue); KifuHeader.PlayerNameWhite = headerValue; goto nextline; default: if (isBody) { throw new KifuException("対局開始後にヘッダが指定されました。", line); } KifuHeader.header_dic.Add(headerKey, headerValue); goto nextline; } } foreach (var bodKey in new string[] { "先手番", "後手番", "上手番", "下手番", "手数=", }) { if (line.StartsWith(bodKey)) { if (isBody) { throw new KifuException("対局開始後にヘッダが指定されました。", line); } bod.Add(line); goto nextline; } } // KIF形式検出 var mKif = rKif.Match(line); if (mKif.Success) { if (!isBody) { var headRes = lazyHead(); if (headRes != string.Empty) { return(headRes); } } var ply = int.Parse(mKif.Groups[1].Value); if (Tree.gamePly != ply) { throw new KifuException($"手数({Tree.gamePly})が一致しません。", line); } Move move; if (mKif.Groups[2].Success) { move = Tree.position.FromKif(mKif.Groups[2].Value); if (!Tree.position.IsLegal(move)) { // これだと不正着手後の棋譜コメントを取れないがとりあえず解析を中止する throw new KifuException("不正着手を検出しました。", line); } } else { switch (mKif.Groups[3].Value) { case "投了": move = Move.RESIGN; break; case "中断": case "封じ手": move = Move.INTERRUPT; break; case "千日手": move = Move.REPETITION_DRAW; break; case "詰み": move = Move.MATED; break; case "時間切れ": case "切れ負け": move = Move.TIME_UP; break; case "パス": move = Move.NULL; break; case "持将棋": move = Move.MAX_MOVES_DRAW; break; case "勝ち宣言": move = Move.WIN; break; default: move = Move.NONE; break; } } if (move == Move.NONE) { throw new KifuException("指し手を解析できませんでした。", line); } TimeSpan thinking_time = TimeSpan.Zero; TimeSpan total_time = TimeSpan.Zero; if (mKif.Groups[4].Success) { // TimeSpan.TryParse 系では "80:00" とかを解釈しないので自前処理する thinking_time = TimeSpan.FromMinutes(double.Parse(mKif.Groups[5].Value)) + TimeSpan.FromSeconds(double.Parse(mKif.Groups[6].Value)); total_time = TimeSpan.FromHours(double.Parse(mKif.Groups[7].Value)) + TimeSpan.FromMinutes(double.Parse(mKif.Groups[8].Value)) + TimeSpan.FromSeconds(double.Parse(mKif.Groups[9].Value)); } var turn = Tree.position.sideToMove; times.Players[(int)turn] = times.Players[(int)turn].Create( timeSettings.Player(turn), thinking_time, thinking_time, total_time /*消費時間は棋譜に記録されているものをそのまま使用する*/ /*残り時間は棋譜上に記録されていない*/ ); Tree.AddNode(move, times.Clone()); if (move.IsOk()) { Tree.DoMove(move); } goto nextline; } // KI2形式検出 var mKi2 = rKi2.Matches(line); if (mKi2.Count > 0) { if (!isBody) { var headRes = lazyHead(); if (headRes != string.Empty) { return(headRes); } } foreach (Match m in mKi2) { var move = Tree.position.FromKif(m.Groups[0].Value); if (move == Move.NONE) { throw new KifuException("指し手を解析できませんでした。", line); } Tree.AddNode(move, KifuMoveTimes.Zero); if (!Tree.position.IsLegal(move)) { // これだと不正着手後の棋譜コメントを取れないがとりあえず解析を中止する throw new KifuException($"不正着手を検出しました。", line); } if (move.IsOk()) { Tree.DoMove(move); } } goto nextline; } var mSpecial = rSpecial.Match(line); if (mSpecial.Success) { var move = Move.NONE; var reason = mSpecial.Groups[2].Value; switch (reason) { case "で先手の勝ち": case "で下手の勝ち": move = Tree.position.sideToMove == Color.BLACK ? Move.ILLEGAL_ACTION_WIN: Move.RESIGN; break; case "で後手の勝ち": case "で上手の勝ち": move = Tree.position.sideToMove == Color.WHITE ? Move.ILLEGAL_ACTION_WIN: Move.RESIGN; break; case "で先手の反則勝ち": case "で下手の反則勝ち": case "で後手の反則負け": case "で上手の反則負け": move = Tree.position.sideToMove == Color.BLACK ? Move.ILLEGAL_ACTION_WIN: Move.ILLEGAL_ACTION_LOSE; break; case "で後手の反則勝ち": case "で上手の反則勝ち": case "で先手の反則負け": case "で下手の反則負け": move = Tree.position.sideToMove == Color.WHITE ? Move.ILLEGAL_ACTION_WIN: Move.ILLEGAL_ACTION_LOSE; break; case "で時間切れにより先手の勝ち": case "で時間切れにより後手の勝ち": case "で時間切れにより上手の勝ち": case "で時間切れにより下手の勝ち": move = Move.TIME_UP; break; case "で中断": move = Move.INTERRUPT; break; case "で持将棋": move = Move.MAX_MOVES_DRAW; break; case "で千日手": move = Move.REPETITION_DRAW; break; case "詰": case "詰み": case "で詰": case "で詰み": move = Move.MATED; break; } if (move != Move.NONE) { Tree.AddNode(move, KifuMoveTimes.Zero); } } nextline :; continue; } if (!isBody) { var headRes = lazyHead(); if (headRes != string.Empty) { return(headRes); } } } catch (Exception e) { return($"棋譜読み込みエラー : {lineNo}行目。\n{e.Message}"); } return(null); }