/// <summary> /// 打つ、成る、成らず、などを判定します。 /// </summary> /// <remarks> /// 駒が指定の場所に移動できない場合は自動的に打つが選ばれます。 /// (盤上に飛車がないのに、"32飛車"のときなど) /// </remarks> private static bool CheckActionType(Move bm, LiteralMove referenceMove, bool canMove) { if (referenceMove.ActionType == ActionType.None) { // 指し手一覧の中に移動できる駒があれば、 // 「打」と指定しなければ打つと判定されません。 if (canMove) { // 指定無しと成らずでおkとします。 return( bm.ActionType == ActionType.None || bm.ActionType == ActionType.Unpromote); } else { return( bm.ActionType == ActionType.None || bm.ActionType == ActionType.Unpromote || bm.ActionType == ActionType.Drop); } } else if (referenceMove.ActionType == ActionType.Unpromote) { // 「不成」の場合は無と成らずでおkとします。 return( bm.ActionType == ActionType.None || bm.ActionType == ActionType.Unpromote); } return(bm.ActionType == referenceMove.ActionType); }
/// <summary> /// 上、引、寄るの判定をします。 /// </summary> private static bool CheckRankMoveType(Move bm, LiteralMove referenceMove) { if (bm.ActionType == ActionType.Drop) { return(false); } var fileMove = bm.DstSquare.File - bm.SrcSquare.File; var rankMove = bm.DstSquare.Rank - bm.SrcSquare.Rank; switch (referenceMove.RankMoveType) { case RankMoveType.Back: return(bm.BWType == BWType.Black ? (rankMove > 0) : (rankMove < 0)); case RankMoveType.Up: return(bm.BWType == BWType.Black ? (rankMove < 0) : (rankMove > 0)); case RankMoveType.Sideways: return(fileMove != 0 && rankMove == 0); } return(false); }
/// <summary> /// 直前の相手の手を考慮して、与えられた指し手を修正します。 /// </summary> private LiteralMove ModifyMove(LiteralMove move) { /*if (move == null || !move.Validate()) * { * throw new Exception("与えられた指し手が正しくありません。"); * }*/ using (LazyLock()) { if (this.opponentMove == null) { return(move); } var newMove = move.Clone(); // 同○○なら、相手の直前の手と列段を合わせます。 if (newMove.SameAsOld) { newMove.File = this.opponentMove.File; newMove.Rank = this.opponentMove.Rank; } else if (newMove.File == this.opponentMove.File && newMove.Rank == this.opponentMove.Rank) { newMove.SameAsOld = true; } return(newMove); } }
/// <summary> /// 指し手に投票します。 /// </summary> public void Vote(ShogiPlayer player, LiteralMove move, DateTime timestamp) { if (move == null || player == null) { throw new ArgumentNullException(); } using (LazyLock()) { // プレイヤーの棋力が変わっている場合があるので、 // プレイヤーも必ず設定し直します。 if (this.moveDatas.ContainsKey(player)) { this.moveDatas.Remove(player); } this.moveDatas[player] = new RegistereredMove() { Move = move, Timestamp = timestamp, }; this.RaisePropertyChanged("MoveList"); } }
/// <summary> /// 複数ある指し手の中から、適切なひとつの指し手を選択します。 /// </summary> /// <remarks> /// <paramref name="referenceMove"/>にはXからYに移動したという情報 /// しかないため、これを52金右などの指し手に変換します。 /// </remarks> private static LiteralMove FilterLiteralFromMove(this Board board, List <Move> moveList, Move referenceMove, Piece fromPiece, bool useSrcSquare) { if (!moveList.Any()) { return(null); } var nextPos = referenceMove.DstSquare; var move = new LiteralMove { BWType = referenceMove.BWType, Piece = fromPiece, File = nextPos.File, Rank = nextPos.Rank, ActionType = ( // '打', '不成'は消える可能性があります。 referenceMove.ActionType == ActionType.Promote ? referenceMove.ActionType : ActionType.None), SameAsOld = (board.PrevMovedSquare == nextPos), }; // 移動元情報を使う場合は、これで終わりです。 // (.kifファイルからの読み込み時は、駒の移動の場合は移動前情報がつき、 // 駒打ちの場合は"打"が必ずつきます) if (useSrcSquare) { move.SrcSquare = referenceMove.SrcSquare; move.ActionType = referenceMove.ActionType; return(move); } // 駒打ち、成り、不成りなどでフィルターします。 var tmpMoveList = FilterAction(move, referenceMove, moveList); if (!tmpMoveList.Any()) { return(null); } // 段の位置でフィルターします。 tmpMoveList = FilterRank(move, referenceMove, tmpMoveList); if (!tmpMoveList.Any()) { return(null); } // 列の位置でフィルターします。 tmpMoveList = FilterFile(move, referenceMove, tmpMoveList); if (!tmpMoveList.Any()) { return(null); } return(move); }
/// <summary> /// 段で指し手をフィルターし、Moveに適切なRankMoveTypeを設定します。 /// </summary> private static List <Move> FilterRank(LiteralMove move, Move referenceMove, List <Move> boardMoveList) { // 駒の移動前情報が必要です。 var nextPos = referenceMove.DstSquare; var prevPos = referenceMove.SrcSquare; if (prevPos == null) { // 何もフィルターしません。 return(boardMoveList); } // 黒から見ると正の場合は引く or 左へ移動(右)で // 負の場合は上がる or 右へ移動(左)です。 var relRank = nextPos.Rank - prevPos.Rank; // 段の位置でフィルターします。 var tmpMoveList = boardMoveList.Where(mv => { var rel = nextPos.Rank - mv.SrcSquare.Rank; if (relRank < 0) { return(rel < 0); } else if (relRank > 0) { return(rel > 0); } else { return(rel == 0); } }).ToList(); // 段情報でフィルターされた場合は、段の移動情報を付加します。 if (tmpMoveList.Count() != boardMoveList.Count()) { var relRank2 = relRank * referenceMove.BWType.Sign(); if (relRank2 < 0) { move.RankMoveType = RankMoveType.Up; } else if (relRank2 > 0) { move.RankMoveType = RankMoveType.Back; } else { move.RankMoveType = RankMoveType.Sideways; } } return(tmpMoveList); }
/// <summary> /// 文字列から得られた指し手から、移動前の情報も含むような /// 指し手情報を取得します。 /// </summary> public static Move ConvertMoveFromLiteral(this Board board, LiteralMove move, BWType bwType, bool multipleIsNull = false) { if (board == null) { throw new ArgumentNullException("board"); } if (move == null) { return(null); } if (move.SameAsOld && board.PrevMovedSquare == null) { return(null); } if (move.IsSpecialMove) { return(Move.CreateSpecialMove( bwType, move.SpecialMoveType)); } // 移動後の位置を取得します。 // 同○○なら前回の位置を使います。 var dstSquare = move.DstSquare; if (move.SameAsOld) { move = move.Clone(); move.DstSquare = board.PrevMovedSquare; dstSquare = board.PrevMovedSquare; } var boardMoveList = board.ListupMoves( move.Piece, bwType, dstSquare) .ToList(); // 複数の指し手の中から適切な一つを選びます。 var boardMove = FilterMoveFromLiteral(boardMoveList, move, multipleIsNull); if (boardMove == null) { return(null); } return(boardMove); }
/// <summary> /// 与えられた指し手が着手可能か調べ、もし着手可能な場合はそれを正式な表記に変換します。 /// </summary> /// <remarks> /// 「正式な表記」にするとは、たとえば不要な「打」を削除したり、 /// 右や直などの表記を正確なものに修正することです。 /// </remarks> public static LiteralMove NormalizeMove(this Board board, LiteralMove lmove) { if (board == null) { throw new ArgumentNullException("board"); } if (!board.Validate()) { throw new ArgumentException("board"); } if (lmove == null) { throw new ArgumentNullException("lmove"); } if (!lmove.Validate()) { throw new ArgumentException("lmove"); } // 投了などの特殊な指し手は常にさせることにします。 /*if (move.IsSpecialMove) * { * return move; * }*/ // 一度、指し手の正規化を行います(打を消したり、左を追加するなど) // あり得る指し手が複数ある場合は失敗とします。 var move = board.ConvertMoveFromLiteral(lmove, true); if (move == null || !board.CanMove(move)) { return(null); } // 指し手を表記形式に再度変換します。 // 移動元の情報は使いません。("65銀(55)"という表記にはしません) var newLMove = board.ConvertLiteralFromMove(move, false); if (newLMove == null) { return(null); } // 最後に元の文字列を保存して返します。 newLMove.OriginalText = lmove.OriginalText; return(newLMove); }
/// <summary> /// 指し手の種類で手をフィルターし、Moveに適切なRankMoveTypeを設定します。 /// </summary> private static List <Move> FilterAction(LiteralMove move, Move referenceMove, List <Move> boardMoveList) { var tmpMoveList = boardMoveList.Where( mv => mv.ActionType == referenceMove.ActionType) .ToList(); if (tmpMoveList.Count() != boardMoveList.Count()) { move.ActionType = referenceMove.ActionType; } return(tmpMoveList); }
/// <summary> /// 複数ある差し手の中から、適切なひとつの差し手を選択します。 /// </summary> /// <remarks> /// BoardMoveは移動前と移動後の情報を持ったオブジェクトで /// Moveは同金右などの移動方向を持ったオブジェクトです。 /// /// 盤面から検索された複数の着手可能な手から /// <paramref name="referenceMove"/>が指示するような手を /// 一つだけ検索します。 /// /// 該当する手が複数見つかった場合(<paramref name="referenceMove"/>に /// 右や左の情報が無い場合など)は、最初に見つかった手を返します。 /// </remarks> private static Move FilterMoveFromLiteral(List <Move> boardMoveList, LiteralMove referenceMove, bool multipleIsNull = false) { IEnumerable <Move> boardMoveListTmp = boardMoveList; // 移動前の座標情報があれば、それを使います。 if (referenceMove.SrcSquare != null) { boardMoveListTmp = boardMoveListTmp.Where( bm => referenceMove.SrcSquare == bm.SrcSquare); } // 上、引、寄るの判定をします。 if (referenceMove.RankMoveType != RankMoveType.None) { boardMoveListTmp = boardMoveListTmp.Where( bm => CheckRankMoveType(bm, referenceMove)); } // 左、右、直の判定をします。 if (referenceMove.RelFileType != RelFileType.None) { boardMoveListTmp = boardMoveListTmp.Where( bm => CheckRelPosType(bm, referenceMove, boardMoveList)); } // 駒打ちなどの判定を行います。 // // 駒打ちの場合 // 1) 他に移動できる駒がない場合、「打」がなくても「打」と解釈 // 2) 他に移動できる駒がある場合、「打」がなければ「打」と解釈されない // という風に、解釈の仕方が少し複雑になっています。 // ここでは、canMoveというフラグを使って上記の場合分けを行っています。 var canMove = boardMoveListTmp.Any( _ => _.ActionType != ActionType.Drop); boardMoveListTmp = boardMoveListTmp.Where( bm => CheckActionType(bm, referenceMove, canMove)); // 適切な差し手が無い場合は、最初に見つかったものを返します。 var list = boardMoveListTmp.ToList(); return( multipleIsNull ? (list.Count() == 1 ? list.FirstOrDefault() : null) : list.FirstOrDefault()); }
/// <summary> /// コメントからPVを探します。 /// </summary> public void SetupPVInfo(Board board) { Move move = null; if (VariationNode != null) { VariationNode.SetupPVInfo(board); } // このノードの手を指して、変化の基準局面を進めます。 if (LiteralMove != null && LiteralMove.Validate()) { move = MakeMove(board, new List <Exception>()); if (move == null || !move.Validate()) { Log.Error("'{0}'が正しく着手できません。", move); return; } } for (var i = 0; i < CommentList.Count();) { var pvInfo = ParsePVInfo(CommentList[i], board); if (pvInfo != null) { PVInfoList.Add(pvInfo); CommentList.RemoveAt(i); } else { ++i; } } if (NextNode != null) { NextNode.SetupPVInfo(board); } if (move != null) { board.Undo(); } }
/// <summary> /// ノード全体を文字列化します。 /// </summary> private void MakeString(StringBuilder sb, int nmoves) { if (LiteralMove != null) { var str = LiteralMove.ToString(); var hanlen = str.HankakuLength(); sb.AppendFormat(" - {0}{1}", str, new string(' ', 8 - hanlen)); } if (NextNode != null) { NextNode.MakeString(sb, nmoves + 1); } if (VariationNode != null) { sb.AppendLine(); sb.Append(new string(' ', 11 * nmoves)); VariationNode.MakeString(sb, nmoves); } }
/// <summary> /// 左、右、直の判定をします。 /// </summary> private static bool CheckRelPosType(Move bm, LiteralMove referenceMove, List <Move> boardMoveList) { if (bm.ActionType == ActionType.Drop) { return(false); } if (bm.MovePiece == Piece.Ryu || bm.MovePiece == Piece.Uma) { // 竜、馬の場合、「直」は使わずに「右左」のみを使用します。 if (boardMoveList.Count() == 1) { return(referenceMove.RelFileType == RelFileType.None); } else { // 駒は二つしかないはずなので、相方に比べて自分が // 左にあれば「左」、右にあれば「右」となっているか // 判定します。 var other = ( ReferenceEquals(bm, boardMoveList[0]) ? boardMoveList[1] : boardMoveList[0]); var fileDif = bm.SrcSquare.File - other.SrcSquare.File; switch (referenceMove.RelFileType) { case RelFileType.Left: return(bm.BWType == BWType.Black ? (fileDif > 0) : (fileDif < 0)); case RelFileType.Right: return(bm.BWType == BWType.Black ? (fileDif < 0) : (fileDif > 0)); case RelFileType.None: return(fileDif == 0); } } } else { var fileMove = bm.DstSquare.File - bm.SrcSquare.File; var rankMove = bm.DstSquare.Rank - bm.SrcSquare.Rank; switch (referenceMove.RelFileType) { case RelFileType.Left: return(bm.BWType == BWType.Black ? (fileMove < 0) : (fileMove > 0)); case RelFileType.Right: return(bm.BWType == BWType.Black ? (fileMove > 0) : (fileMove < 0)); case RelFileType.Straight: return((bm.BWType == BWType.Black ? (rankMove < 0) : (rankMove > 0)) && (fileMove == 0)); } } return(false); }
/// <summary> /// 列で指し手をフィルターし、Moveに適切なRelPosTypeを設定します。 /// </summary> private static List <Move> FilterFile(LiteralMove move, Move referenceMove, List <Move> boardMoveList) { // 駒の移動前情報が必要です。 var nextPos = referenceMove.DstSquare; var prevPos = referenceMove.SrcSquare; if (prevPos == null) { // 何もフィルターしません。 return(boardMoveList); } if ((move.Piece == Piece.Ryu || move.Piece == Piece.Uma) && boardMoveList.Count() == 2) { // 馬と竜の場合は'直'ではなく、右と左しか使いません。 var other = ( referenceMove == boardMoveList[0] ? boardMoveList[1] : boardMoveList[0]); // 動かす前の駒が相方に比べて右にあるか左にあるか調べます。 // 先手の場合は筋が小さい方が右です。 var relFile = prevPos.File - other.SrcSquare.File; relFile *= (referenceMove.BWType == BWType.White ? -1 : +1); if (relFile == 0) { return(boardMoveList); } else if (relFile < 0) { move.RelFileType = RelFileType.Right; } else { move.RelFileType = RelFileType.Left; } return(new List <Move> { referenceMove }); } else { // 黒から見ると正の場合は引く or 左へ移動(右)で // 負の場合は上がる or 右へ移動(左)です。 var relFile = nextPos.File - prevPos.File; // 列の位置でフィルターします。 var tmpMoveList = boardMoveList.Where(mv => { var rel = nextPos.File - mv.SrcSquare.File; return( relFile < 0 ? (rel < 0) : relFile > 0 ? (rel > 0) : (rel == 0)); }).ToList(); // 列情報でフィルターされた場合は、列の移動情報を付加します。 if (tmpMoveList.Count() != boardMoveList.Count()) { var relFile2 = relFile * referenceMove.BWType.Sign(); if (relFile2 < 0) { move.RelFileType = RelFileType.Left; } else if (relFile2 > 0) { move.RelFileType = RelFileType.Right; } else { // 直の場合は、左右の動きはありません。 move.RankMoveType = RankMoveType.None; move.RelFileType = RelFileType.Straight; } } return(tmpMoveList); } }
/// <summary> /// 指し手を文字列に変換します。 /// </summary> /// <remarks> /// KifFileに使う指し手は同○○の場合、 /// 「同」と「○○」の間に空白を入れないと"kif for windows"では /// 正しく読み込めなくなります。 /// </remarks> public static string ToString(LiteralMove move, MoveTextStyle style = MoveTextStyle.Normal) { if (move == null) { return(null); } if (move.IsSpecialMove) { var turnStr = string.Empty; // 必要なら▲△を先頭に入れます。 if (style == MoveTextStyle.Normal) { turnStr = ToString(move.BWType); } return(turnStr + EnumEx.GetLabel(move.SpecialMoveType)); } var result = new StringBuilder(); result.Append(ToString(move.Piece)); result.Append(ToString(move.RelFileType)); result.Append(ToString(move.RankMoveType)); result.Append(ToString(move.ActionType)); if (move.SameAsOld) { var hasSpace = ( (style != MoveTextStyle.Simple) && (result.Length == 1 || style == MoveTextStyle.KifFile)); // 文字数によって、同の後の空白を入れるか決めます。 result.Insert(0, (hasSpace ? "同 " : "同")); } else { if (style == MoveTextStyle.Simple) { result.Insert(0, IntConverter.Convert(NumberType.Normal, move.File)); result.Insert(1, IntConverter.Convert(NumberType.Normal, move.Rank)); } else { result.Insert(0, IntConverter.Convert(NumberType.Big, move.File)); result.Insert(1, IntConverter.Convert(NumberType.Kanji, move.Rank)); } } // 必要なら▲△を先頭に入れます。 if (style == MoveTextStyle.Normal) { result.Insert(0, ToString(move.BWType)); } if (move.SrcSquare != null && style == MoveTextStyle.KifFile) { result.AppendFormat("({0}{1})", move.SrcSquare.File, move.SrcSquare.Rank); } return(result.ToString()); }
/// <summary> /// 指し手のパースを行います。 /// </summary> public static LiteralMove ParseMoveEx(string text, bool isNeedEnd, ref string parsedText) { if (string.IsNullOrEmpty(text)) { return(null); } var move = new LiteralMove() { OriginalText = text.Trim(), }; // 投了などの特殊な指し手の判断を行います。 var m = SpecialMoveRegex.Match(text); if (m.Success) { // 手番が指定されていればそれを取得します。 move.BWType = GetBWType(m.Groups[1].Value); // 投了などの指し手の種類を取得します。 var smoveType = GetSpecialMoveType(m.Groups[2].Value); if (smoveType == SpecialMoveType.None) { return(null); } move.SpecialMoveType = smoveType; } else { m = MoveRegex.Match(text); if (!m.Success) { return(null); } // 手番が指定されていれば、それを取得します。 move.BWType = GetBWType(m.Groups[1].Value); // 33同銀などを有効にします。 // 筋・段 var file = GetNumber(m.Groups[2].Value); var rank = GetNumber(m.Groups[3].Value); // 「同~」であるかどうかを特定します。 // 3同銀などの指し手を受理しないように、数字があれば、 // ちゃんと二つとも設定されているか調べます。 move.SameAsOld = m.Groups[4].Success; if (!move.SameAsOld || file != null || rank != null) { // 筋 if (file == null) { return(null); } move.File = file.Value; // 段 if (rank == null) { return(null); } move.Rank = rank.Value; } // 駒の種類 var piece = GetPiece(m.Groups[5].Value); if (piece == null) { return(null); } move.Piece = piece; // 相対位置 move.RelFileType = GetRelFileType(m.Groups[6].Value); // 駒の移動の種類 move.RankMoveType = GetRankMoveType(m.Groups[7].Value); // 駒打ちなどのアクション move.ActionType = GetActionType(m.Groups[8].Value); // 移動前の位置 if (m.Groups[9].Success) { move.SrcSquare = new Square( int.Parse(m.Groups[10].Value), int.Parse(m.Groups[11].Value)); } } // 解析文字列が正しく終わっているか調べます。 if (isNeedEnd && (m.Length < text.Length && !char.IsWhiteSpace(text[m.Length]))) { return(null); } if (!move.Validate()) { return(null); } parsedText = m.Value; return(move); }
/// <summary> /// 文字列から得られた指し手から、移動前の情報も含むような /// 指し手情報を取得します。 /// </summary> public static Move ConvertMoveFromLiteral(this Board board, LiteralMove move, bool multipleIsNull = false) { return(board.ConvertMoveFromLiteral(move, board.Turn, multipleIsNull)); }