/// <summary> /// ヘッダ部分を出力します。 /// </summary> private void WriteHeader(TextWriter writer, KifuObject kifu) { writer.WriteLine("' ---- Ragnarok 棋譜ファイル ----"); // 対局者名は別腹で行きます。 var value = kifu.Header[KifuHeaderType.BlackName]; if (value != null) { writer.WriteLine("N+{0}", value); } value = kifu.Header[KifuHeaderType.WhiteName]; if (value != null) { writer.WriteLine("N-{0}", value); } // 他のヘッダアイテムを書き出します。 foreach (var item in kifu.Header) { var name = CsaUtil.GetHeaderName(item.Key); if (name == null) { continue; } writer.WriteLine("${0}:{1}", name, item.Value); } }
/// <summary> /// 各段のCSA局面を返します。 /// </summary> private static IEnumerable <string> RankToCsa(Board board, int rank) { for (var file = Board.BoardSize; file >= 1; --file) { var piece = board[file, rank]; yield return(CsaUtil.BoardPieceToStr(piece)); } }
/// <summary> /// CSA形式の文字列から、局面を読み取ります。 /// </summary> public static Board Parse(string csa) { if (string.IsNullOrEmpty(csa)) { throw new ArgumentNullException("csa"); } var parser = new CsaBoardParser(); csa.Split( new char[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries) .Where(_ => !CsaUtil.IsCommentLine(_)) .ForEach(_ => parser.TryParse(_)); return(parser.Board); }
/// <summary> /// 一括表現形式の局面を読み取ります。 /// </summary> /// <example> /// P1-KY-KE-GI-KI-OU-KI-GI-KE-KY /// P2 * -HI * * * * * -KA * /// </example> private void ParseBoardPn(string line) { if (line[1] < '1' || '9' < line[1]) { throw new ShogiException( line + ": CSA形式の局面の段数が正しくありません。"); } var currentRank = (int)(line[1] - '0'); var rank = this.parsedRank + 1; if (currentRank != rank) { throw new ShogiException( line + ": CSA形式の局面を正しく読み込めませんでした。"); } var pieceList = line.Skip(2).TakeBy(3) .Select(_ => new string(_.ToArray())) .Select(_ => CsaUtil.StrToBoardPiece(_)) .Where(_ => _ != null) .ToList(); if (pieceList.Count() != Board.BoardSize || pieceList.Any(_ => _ == null)) { throw new ShogiException( line + ": CSA形式の局面を正しく読み込めませんでした。"); } // 局面は駒を全くおかない状態で初期化します。 if (this.board == null) { this.board = new Board(false); } pieceList.ForEachWithIndex((piece, index) => { var isNone = (piece.PieceType == PieceType.None); var file = Board.BoardSize - index; this.board[file, rank] = (isNone ? null : piece); }); this.parsedRank += 1; }
/// <summary> /// 棋戦情報などをパースします。 /// </summary> private void ParseHeader(string line) { var item = CsaUtil.ParseHeaderItem(line); if (item == null) { throw new FileFormatException( this.lineNumber, line + ": CSA形式のヘッダ行が正しくありません。"); } var type = CsaUtil.GetHeaderType(item.Key); if (type != null) { this.header[type.Value] = item.Value; } }
/// <summary> /// 落とす駒をパースします。 /// </summary> /// <remarks> /// 00OU など4文字形式の駒をパースします。 /// </remarks> private PieceSquare ParsePiece(string str) { var file = (int)(str[0] - '0'); var rank = (int)(str[1] - '0'); var pieceStr = str.Substring(2); var piece = CsaUtil.StrToPiece(pieceStr); if (piece == null) { throw new ShogiException( str + ": CSA形式の駒を正しく読み込めませんでした。"); } return(new PieceSquare { Square = new Square(file, rank), Piece = piece, }); }
/// <summary> /// 持ち駒をCSA形式に変換します。 /// </summary> /// <example> /// P+00KIOOFU /// </example> private static string HandToCsa(Board board, BWType turn) { var handList = from pieceType in EnumEx.GetValues <PieceType>() let count = board.GetCapturedPieceCount(pieceType, turn) let str = string.Format("00{0}", CsaUtil.PieceToStr(new Piece(pieceType))) let list = Enumerable.Range(1, count).Select(_ => str) select string.Join("", list.ToArray()); // ToArray()しないと、MONOでstring.Joinのコンパイルに失敗します。 var array = handList.ToArray(); if (!array.Any()) { return(string.Empty); } else { return(string.Format("P{0}{1}", turn == BWType.Black ? "+" : "-", string.Join("", array))); } }
/// <summary> /// <paramref name="move"/>をCSA形式に変換します。 /// </summary> public static string ToCsa(this Move move) { if (move == null) { throw new ArgumentNullException("move"); } if (!move.Validate()) { throw new ArgumentException("move"); } if (move.IsSpecialMove) { return(move.SpecialMoveType.ToCsa()); } var sb = new StringBuilder(); sb.Append( move.BWType == BWType.Black ? "+" : move.BWType == BWType.White ? "-" : ""); if (move.SrcSquare != null) { sb.Append(move.SrcSquare.File); sb.Append(move.SrcSquare.Rank); } else { // 駒打の場合 sb.Append("00"); } if (move.DstSquare != null) { sb.Append(move.DstSquare.File); sb.Append(move.DstSquare.Rank); } else { // ほんとはエラー sb.Append("00"); } var piece = ( move.ActionType == ActionType.Drop ? new Piece(move.DropPieceType) : move.MovePiece); if (move.IsPromote) { // 駒を成った場合は、なった後の駒を出力します。 piece = new Piece(piece.PieceType, true); } sb.Append(CsaUtil.PieceToStr(piece)); return(sb.ToString()); }
/// <summary> /// CSA形式の指し手を解析します。 /// </summary> public static Move CsaToMove(this Board board, string csa) { if (board == null) { throw new ArgumentNullException("board"); } if (string.IsNullOrEmpty(csa)) { throw new ArgumentNullException("csa"); } // 特殊な指し手 var smove = board.CsaToSpecialMove(csa); if (smove != null) { return(smove); } // 普通の指し手 var m = MoveRegex.Match(csa); if (!m.Success) { return(null); } var c = m.Groups[1].Value; var side = ( c == "+" ? BWType.Black : c == "-" ? BWType.White : BWType.None); // 移動前の位置 var srcFile = int.Parse(m.Groups[2].Value); var srcRank = int.Parse(m.Groups[3].Value); var srcSquare = (srcFile == 0 || srcRank == 0 ? (Square)null : new Square(srcFile, srcRank)); // 移動後の位置 var dstFile = int.Parse(m.Groups[4].Value); var dstRank = int.Parse(m.Groups[5].Value); var dstSquare = (dstFile == 0 || dstRank == 0 ? (Square)null : new Square(dstFile, dstRank)); // 駒 var piece = CsaUtil.StrToPiece(m.Groups[6].Value); if (piece == null || piece.PieceType == PieceType.None) { return(null); } if (srcSquare == null) { // 駒打ちの場合 return(Move.CreateDrop(side, dstSquare, piece.PieceType)); } else { // 駒の移動の場合、成りの判定を行います。 var srcPiece = board[srcSquare]; if (srcPiece == null || !srcPiece.Validate()) { return(null); } // CSA形式の場合、駒が成ると駒の種類が変わります。 var isPromote = (!srcPiece.IsPromoted && piece.IsPromoted); if (isPromote) { piece = new Piece(piece.PieceType, false); } return(Move.CreateMove(side, srcSquare, dstSquare, piece, isPromote)); } }
/// <summary> /// ヘッダー行をパースします。 /// </summary> private bool ParseLine(string line, CsaBoardParser parser) { if (line == null) { // ファイルの終了を意味します。 return(false); } if (CsaUtil.IsCommentLine(line)) { return(true); } // 局面の読み取りを試みます。 if (parser.TryParse(line)) { if (parser.HasBoard && !parser.IsBoardParsing) { this.startBoard = parser.Board.Clone(); this.board = this.startBoard.Clone(); } return(true); } switch (line[0]) { case 'V': return(true); case 'N': ParseName(line); return(true); case '$': ParseHeader(line); return(true); case '+': case '-': case '%': var move = ParseMove(line); var node = new MoveNode { Move = move, }; this.lastNode.AddNextNode(node); this.lastNode = node; return(true); case 'T': var seconds = ParseTime(line); if (this.lastNode != null) { this.lastNode.DurationSeconds = seconds; } return(true); } return(false); }