Ejemplo n.º 1
0
        /// <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);
        }
Ejemplo n.º 2
0
        /// <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);
        }
Ejemplo n.º 3
0
        /// <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);
            }
        }
Ejemplo n.º 4
0
        /// <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");
            }
        }
Ejemplo n.º 5
0
        /// <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);
        }
Ejemplo n.º 6
0
        /// <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);
        }
Ejemplo n.º 7
0
        /// <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);
        }
Ejemplo n.º 8
0
        /// <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);
        }
Ejemplo n.º 9
0
        /// <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);
        }
Ejemplo n.º 10
0
        /// <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());
        }
Ejemplo n.º 11
0
        /// <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();
            }
        }
Ejemplo n.º 12
0
        /// <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);
            }
        }
Ejemplo n.º 13
0
        /// <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);
        }
Ejemplo n.º 14
0
        /// <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);
            }
        }
Ejemplo n.º 15
0
        /// <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());
        }
Ejemplo n.º 16
0
        /// <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);
        }
Ejemplo n.º 17
0
 /// <summary>
 /// 文字列から得られた指し手から、移動前の情報も含むような
 /// 指し手情報を取得します。
 /// </summary>
 public static Move ConvertMoveFromLiteral(this Board board, LiteralMove move,
                                           bool multipleIsNull = false)
 {
     return(board.ConvertMoveFromLiteral(move, board.Turn, multipleIsNull));
 }