Пример #1
0
        /// <summary>
        /// ユーザーによる対局中の2手戻し
        /// 受理できるかどうかは別
        /// </summary>
        public void UndoCommand()
        {
            AddCommand(
                () =>
            {
                if (InTheGame)
                {
                    var stm       = kifuManager.Position.sideToMove;
                    var stmPlayer = Player(stm);

                    // 人間の手番でなければ受理しない
                    if (stmPlayer.PlayerType == PlayerTypeEnum.Human)
                    {
                        // 棋譜を消すUndo()
                        kifuManager.UndoMoveInTheGame();
                        kifuManager.UndoMoveInTheGame();

                        // 時刻を巻き戻さないといけない
                        PlayTimers.SetKifuMoveTimes(kifuManager.Tree.GetKifuMoveTimes());

                        // これにより、2手目の局面などであれば1手しかundoできずに手番が変わりうるので手番の更新を通知。
                        NotifyTurnChanged();
                    }
                }
            });
        }
Пример #2
0
        /// <summary>
        /// 棋譜の選択行が変更になった。
        /// 対局中でなければ、現在局面をその棋譜の局面に変更する。
        ///
        /// このメソッドを直接呼び出さずに、this.KifuListSelectedIndexのsetterか、UpdateKifuSelectedIndex()を用いること。
        /// </summary>
        private void KifuListSelectedIndexChangedCommand(PropertyChangedEventArgs args)
        {
            AddCommand(
                () =>
            {
                if (GameMode.IsConsideration())
                {
                    // 現在の局面と違う行であるかを判定して、同じ行である場合は、
                    // このイベントを処理してはならない。

                    // 無理やりではあるが棋譜のN行目に移動出来るのであった…。
                    int selectedIndex = (int)args.value;
                    kifuManager.Tree.GotoSelectedIndex(selectedIndex);
                    PlayTimers.SetKifuMoveTimes(kifuManager.Tree.GetKifuMoveTimes());

                    // 検討モードのとき)
                    //   思考エンジンでの検討中       → 局面が変わったので思考しなおすためにNotifyTurnChanged()が必要。
                    //   思考エンジンで検討していない → 掴んでいる駒を離す必要があるためにNotifyTurnChanged()が必要。
                    if (GameMode.IsConsideration())
                    {
                        NotifyTurnChanged();
                    }
                }
            });
        }
Пример #3
0
        /// <summary>
        /// ユーザーによる対局中の2手戻し
        /// 受理できるかどうかは別
        /// </summary>
        public void UndoCommand()
        {
            AddCommand(
                () =>
            {
                if (InTheGame)
                {
                    var stm       = kifuManager.Position.sideToMove;
                    var stmPlayer = Player(stm);

                    // 人間の手番でなければ受理しない
                    if (stmPlayer.PlayerType == PlayerTypeEnum.Human)
                    {
                        // 棋譜を消すUndo()
                        kifuManager.UndoMoveInTheGame();
                        kifuManager.UndoMoveInTheGame();

                        // 残り持ち時間などを巻き戻さないといけない。
                        // ただし、再開局で開始局面以上に巻き戻すと、いまより持ち時間が減ってしまう可能性がある。
                        // 開始局面以上に巻き戻す場合は、再開時の時間にしてやる必要がある。
                        // しかし、中断局であるなら、遡れなければならない。難しい。

                        var nextTime = kifuManager.Tree.GetKifuMoveTimes();
                        PlayTimers.SetKifuMoveTimes(nextTime);

                        // これにより、2手目の局面などであれば1手しかundoできずに手番が変わりうるので手番の更新を通知。
                        NotifyTurnChanged();
                    }
                }
            });
        }
Пример #4
0
        /// <summary>
        /// 棋譜(string型)の読み込みコマンド
        /// ファイルから読み込むには、MainMenu.ReadKifuFile()のようなメソッドを用いること。
        /// </summary>
        /// <param name="kifuText"></param>
        public void KifuReadCommand(string kifuText)
        {
            AddCommand(
                () =>
            {
                if (GameMode.CanUserMove())
                {
                    // 対局中ではないので、EnableKifuList == falseになっているが、
                    // 一時的にこれをtrueにしないと、読み込んだ棋譜に対して、Tree.KifuListが同期しない。
                    // ゆえに、読み込みの瞬間だけtrueにして、そのあとfalseに戻す。
                    kifuManager.EnableKifuList = true;
                    var error = kifuManager.FromString(kifuText);
                    kifuManager.EnableKifuList = false;

                    if (error != null)
                    {
                        TheApp.app.MessageShow("棋譜の読み込みに失敗しました。\n" + error, MessageShowType.Error);

                        kifuManager.Init(); // 不正な局面のままになるとまずいので初期化。
                    }
                    else
                    {
                        // 読み込みが完了すれば自動的に末尾の局面に行っているはずだが、
                        // 棋譜ウィンドウを更新した結果、分岐局面などに戻ってしまうといけない。

                        // 棋譜に書かれていた持ち時間設定・残り時間を画面に反映させる。(GameSettingには反映させない)
                        PlayTimers.SetKifuTimeSettings(kifuManager.Tree.KifuTimeSettings);
                        PlayTimers.SetKifuMoveTimes(kifuManager.Tree.GetKifuMoveTimes());
                        UpdateTimeString();

                        // 末尾の局面に移動するコマンドを叩いておく。
                        UpdateKifuSelectedIndex(int.MaxValue);

                        // -- 棋譜上の名前を表示名に反映させる。

                        foreach (var c in All.Colors())
                        {
                            var name = kifuManager.KifuHeader.GetPlayerName(c);
                            SetPlayerName(c, name);
                        }
                    }

                    // 棋譜が綺麗になった扱いにする。(この棋譜はファイルなどに丸ごと保存されているはずであるから)
                    KifuDirty = false;

                    // 駒を持ち上げていたりしたらそれをリセットする必要があるので。
                    RaisePropertyChanged("TurnChanged");
                }
            });
        }
Пример #5
0
        /// <summary>
        /// 棋譜の選択行が変更になった。
        /// 対局中でなければ、現在局面をその棋譜の局面に変更する。
        /// </summary>
        public void KifuSelectedIndexChangedCommand(int selectedIndex)
        {
            AddCommand(
                () =>
            {
                if (GameMode.IsConsideration())
                {
                    // 現在の局面と違う行であるかを判定して、同じ行である場合は、
                    // このイベントを処理してはならない。

                    // 無理やりではあるが棋譜のN行目に移動出来るのであった…。
                    kifuManager.Tree.GotoSelectedIndex(selectedIndex);
                    PlayTimers.SetKifuMoveTimes(kifuManager.Tree.GetKifuMoveTimes());
                }
            });
        }
Пример #6
0
        /// <summary>
        /// 棋譜の選択行が変更になった。
        /// 対局中でなければ、現在局面をその棋譜の局面に変更する。
        ///
        /// このメソッドを直接呼び出さずに、this.KifuListSelectedIndexのsetterを使うこと。
        /// </summary>
        private void KifuListSelectedIndexChangedCommand(PropertyChangedEventArgs args)
        {
            AddCommand(
                () =>
            {
                if (GameMode.IsConsideration())
                {
                    // 現在の局面と違う行であるかを判定して、同じ行である場合は、
                    // このイベントを処理してはならない。

                    // 無理やりではあるが棋譜のN行目に移動出来るのであった…。
                    int selectedIndex = (int)args.value;
                    kifuManager.Tree.GotoSelectedIndex(selectedIndex);
                    PlayTimers.SetKifuMoveTimes(kifuManager.Tree.GetKifuMoveTimes());

                    // 局面が変わったので思考しなおす。
                    if (GameMode.IsConsiderationWithEngine())
                    {
                        NotifyTurnChanged();
                    }
                }
            });
        }
Пример #7
0
        /// <summary>
        /// ユーザーによる対局中の2手戻し
        /// 受理できるかどうかは別
        /// </summary>
        public void UndoCommand()
        {
            AddCommand(
                () =>
            {
                if (InTheGame)
                {
                    var stm       = kifuManager.Position.sideToMove;
                    var stmPlayer = Player(stm);

                    // 人間の手番でなければ受理しない
                    if (stmPlayer.PlayerType == PlayerTypeEnum.Human)
                    {
                        // 棋譜を消すUndo()
                        kifuManager.UndoMoveAndRemoveKifu();
                        kifuManager.UndoMoveAndRemoveKifu();

                        // 残り持ち時間などを巻き戻さないといけない。
                        // ただし、再開局で開始局面以上に巻き戻すと、いまより持ち時間が減ってしまう可能性がある。
                        // 開始局面以上に巻き戻す場合は、再開時の時間にしてやる必要がある。
                        // しかし、中断局であるなら、遡れなければならない。難しい。

                        var nextTime = kifuManager.Tree.GetKifuMoveTimes();
                        PlayTimers.SetKifuMoveTimes(nextTime);

                        // これにより、2手目の局面などであれば1手しかundoできずに手番が変わりうるので手番の更新を通知。
                        NotifyTurnChanged();

                        // TreeのUndoにより、KifuListの更新がかかり、選択行が変化して、
                        // 更新通知が来て、そのハンドラのなかでSetKifuMoveTimes()とNotifyTurnChanged()を
                        // 行っているので、本来、上の文は不要なのだが、そのハンドラでは、Consideration中しか
                        // その処理を行わない(ユーザーの棋譜選択でそこが変更されると困る)ので上記処理は必要。
                    }
                }
            });
        }
Пример #8
0
        /// <summary>
        /// 棋譜の読み込みコマンド
        /// </summary>
        /// <param name="kifuText"></param>
        public void KifuReadCommand(string kifuText)
        {
            AddCommand(
                () =>
            {
                if (GameMode.CanUserMove())
                {
                    // 対局中ではないので、EnableKifuList == falseになっているが、
                    // 一時的にこれをtrueにしないと、読み込んだ棋譜に対して、Tree.KifuListが同期しない。
                    // ゆえに、読み込みの瞬間だけtrueにして、そのあとfalseに戻す。
                    kifuManager.EnableKifuList = true;
                    var error = kifuManager.FromString(kifuText);
                    kifuManager.EnableKifuList = false;

                    if (error != null)
                    {
                        TheApp.app.MessageShow("棋譜の読み込みに失敗しました。\n" + error, "読み込みエラー");

                        kifuManager.Init(); // 不正な局面のままになるとまずいので初期化。
                    }
                    else
                    {
                        // 読み込みが完了すれば自動的に末尾の局面に行っているはずだが、
                        // 棋譜ウィンドウを更新した結果、分岐局面などに戻ってしまうといけない。

                        // 棋譜に書かれていた持ち時間設定・残り時間を画面に反映させる。(GameSettingには反映させない)
                        PlayTimers.SetKifuTimeSettings(kifuManager.Tree.KifuTimeSettings);
                        PlayTimers.SetKifuMoveTimes(kifuManager.Tree.GetKifuMoveTimes());
                        UpdateTimeString();

                        // 末尾の局面に移動するコマンドを叩いておく。
                        RaisePropertyChanged("SetKifuListIndex", kifuManager.KifuList.Count - 1);
                    }
                }
            });
        }
Пример #9
0
        /// <summary>
        /// 棋譜(string型)の読み込みコマンド
        /// ファイルから読み込むには、MainMenu.ReadKifuFile()のようなメソッドを用いること。
        ///
        /// merge == trueなら、棋譜を分岐棋譜としてマージする。
        /// dirtyは、このあと、KifuDirty(棋譜が汚れているかのフラグ)に設定される値。
        /// </summary>
        /// <param name="kifuText"></param>
        public void KifuReadCommand(string kifuText, bool merge = false, bool dirty = false)
        {
            AddCommand(
                () =>
            {
                if (GameMode.CanUserMove())
                {
                    // 対局中ではないので、EnableKifuList == falseになっているが、
                    // 一時的にこれをtrueにしないと、読み込んだ棋譜に対して、Tree.KifuListが同期しない。
                    // ゆえに、読み込みの瞬間だけtrueにして、そのあとfalseに戻す。
                    kifuManager.EnableKifuList = true;

                    var oldSelectedIndex = kifuManager.Tree.pliesFromRoot;

                    var error = merge ? kifuManager.MergeFromString(kifuText) : kifuManager.FromString(kifuText);
                    // マージした場合、その棋譜の末尾に行って欲しい気もするが…。

                    kifuManager.EnableKifuList = false;

                    if (error != null)
                    {
                        TheApp.app.MessageShow((merge ? "棋譜のマージに失敗しました。\n" : "棋譜の読み込みに失敗しました。\n") + error, MessageShowType.Error);

                        kifuManager.Init(); // 不正な局面のままになるとまずいので初期化。
                    }
                    else
                    {
                        // 読み込みが完了すれば自動的に末尾の局面に行っているはずだが、
                        // 棋譜ウィンドウを更新した結果、分岐局面などに戻ってしまうといけない。

                        // 棋譜に書かれていた持ち時間設定・残り時間を画面に反映させる。(GameSettingには反映させない)
                        PlayTimers.SetKifuTimeSettings(kifuManager.Tree.KifuTimeSettings);
                        PlayTimers.SetKifuMoveTimes(kifuManager.Tree.GetKifuMoveTimes());
                        UpdateTimeString();

                        // 末尾の局面に移動するコマンドを叩いておく。
                        // マージ時は、マージ起点に移動してやる。たぶん現在の選択していた行がそれ。
                        if (!merge)
                        {
                            UpdateKifuSelectedIndex(int.MaxValue);
                        }
                        else
                        {
                            UpdateKifuSelectedIndex(oldSelectedIndex + 1);
                        }

                        // -- 棋譜上の名前を表示名に反映させる。

                        foreach (var c in All.Colors())
                        {
                            var name = kifuManager.KifuHeader.GetPlayerName(c);
                            SetPlayerName(c, name);
                        }
                    }

                    // 棋譜が綺麗になった扱いにする。(この棋譜はファイルなどに丸ごと保存されているはずであるから)
                    // ただし、mergeのときは汚れたという扱いにする。(ファイルに残っていないので)
                    KifuDirty = dirty;

                    // 駒を持ち上げていたりしたらそれをリセットする必要があるので。
                    RaisePropertyChanged("TurnChanged");
                }
            });
        }
Пример #10
0
        /// <summary>
        /// 対局開始のためにGameSettingの設定に従い、ゲームを初期化する。
        /// </summary>
        /// <param name="gameSetting"></param>
        private void GameStart(GameSetting gameSetting)
        {
            // 音声:「よろしくお願いします。」
            TheApp.app.soundManager.Stop(); // 再生中の読み上げをすべて停止
            TheApp.app.soundManager.ReadOut(SoundEnum.Start);

            // 初期化中である。
            Initializing = true;

            // 初回の指し手で、「先手」「後手」と読み上げるためのフラグ
            sengo_read_out = new bool[2] {
                false, false
            };

            // プレイヤーの生成
            foreach (var c in All.Colors())
            {
                var playerType = gameSetting.Player(c).IsHuman ? PlayerTypeEnum.Human : PlayerTypeEnum.UsiEngine;
                Players[(int)c] = PlayerBuilder.Create(playerType);
            }

            // 局面の設定
            kifuManager.EnableKifuList = true;
            if (gameSetting.Board.BoardTypeCurrent)
            {
                // 現在の局面からなので、いま以降の局面を削除する。
                // ただし、いまの局面と棋譜ウィンドウとが同期しているとは限らない。
                // まず現在局面以降の棋譜を削除しなくてはならない。

                // 元nodeが、special moveであるなら、それを削除しておく。
                if (kifuManager.Tree.IsSpecialNode())
                {
                    kifuManager.Tree.UndoMove();
                }

                kifuManager.Tree.ClearForward();

                // 分岐棋譜かも知れないので、現在のものを本譜の手順にする。
                kifuManager.Tree.MakeCurrentNodeMainBranch();
            }
            else // if (gameSetting.Board.BordTypeEnable)
            {
                kifuManager.Init();
                kifuManager.InitBoard(gameSetting.Board.BoardType);
            }

            // 本譜の手順に変更したので現在局面と棋譜ウィンドウのカーソルとを同期させておく。
            UpdateKifuSelectedIndex();

            // 現在の時間設定を、KifuManager.Treeに反映させておく(棋譜保存時にこれが書き出される)
            kifuManager.Tree.KifuTimeSettings = gameSetting.KifuTimeSettings;

            // 対局者氏名の設定
            // 人間の時のみ有効。エンジンの時は、エンジン設定などから取得することにする。(TODO:あとで考える)
            foreach (var c in All.Colors())
            {
                var    player = Player(c);
                string name;
                switch (player.PlayerType)
                {
                case PlayerTypeEnum.Human:
                    name = gameSetting.Player(c).PlayerName;
                    break;

                default:
                    name = c.Pretty();
                    break;
                }

                kifuManager.KifuHeader.SetPlayerName(c, name);
            }

            // 持ち時間などの設定が必要なので、コピーしておく。
            GameSetting = gameSetting;

            // 消費時間計算用
            foreach (var c in All.Colors())
            {
                var pc = PlayTimer(c);
                pc.KifuTimeSetting = GameSetting.KifuTimeSettings.Player(c);
                pc.GameStart();
            }

            // rootの持ち時間設定をここに反映させておかないと待ったでrootまで持ち時間が戻せない。
            // 途中の局面からだとここではなく、現局面のところに書き出す必要がある。
            kifuManager.Tree.SetKifuMoveTimes(PlayTimers.GetKifuMoveTimes());

            // コンピュータ vs 人間である場合、人間側を手前にしてやる。
            foreach (var c in All.Colors())
            {
                if (gameSetting.Player(c).IsHuman&& gameSetting.Player(c.Not()).IsCpu)
                {
                    BoardReverse = (c == Color.WHITE);
                }
            }

            // 盤面編集中である可能性がある。リセットする。
            TheApp.app.config.InTheBoardEdit = false;

            InTheGame = true;
        }
Пример #11
0
        /// <summary>
        /// 指し手が指されたかのチェックを行う
        /// </summary>
        private void CheckPlayerMove()
        {
            // 現状の局面の手番側
            var stm       = Position.sideToMove;
            var stmPlayer = Player(stm);

            var config = TheApp.app.Config;

            // -- 指し手

            Move bestMove;

            if (GameMode.IsConsiderationWithEngine())
            {
                // 検討モードなのでエンジンから送られてきたbestMoveの指し手は無視。
                bestMove = stmPlayer.SpecialMove;
            }
            else
            {
                // 対局設定ダイアログの「コンピューターは1手に必ずこれだけ使う」が設定されていれば、
                // その時間になるまでbest moveを無視する。
                var stmBestMove = stmPlayer.BestMove;
                if (stmBestMove != Move.NONE &&
                    GameMode == GameModeEnum.InTheGame && /* 通常対局中 */
                    stmPlayer.PlayerType == PlayerTypeEnum.UsiEngine &&
                    GameSetting.MiscSettings.EnableBestMoveIgnoreTimeForEngine &&
                    PlayTimer(stm).ElapsedTime() < GameSetting.MiscSettings.BestMoveIgnoreTimeForEngine
                    )
                {
                    stmBestMove = Move.NONE;
                }

                // TIME_UPなどのSpecialMoveが積まれているなら、そちらを優先して解釈する。
                bestMove = stmPlayer.SpecialMove != Move.NONE ? stmPlayer.SpecialMove : stmBestMove;
            }

            if (bestMove != Move.NONE)
            {
                PlayTimer(stm).ChangeToThemTurn(bestMove == Move.TIME_UP);

                stmPlayer.SpecialMove = Move.NONE; // クリア

                // 駒が動かせる状況でかつ合法手であるなら、受理する。

                bool specialMove = false;
                if (GameMode == GameModeEnum.InTheGame)
                {
                    // 送信されうる特別な指し手であるか?
                    specialMove = bestMove.IsSpecial();

                    // エンジンから送られてきた文字列が、illigal moveであるならエラーとして表示する必要がある。

                    if (specialMove)
                    {
                        switch (bestMove)
                        {
                        // 入玉宣言勝ち
                        case Move.WIN:
                            var rule = (EnteringKingRule)GameSetting.MiscSettings.EnteringKingRule;
                            if (Position.DeclarationWin(rule) != Move.WIN)
                            {
                                // 入玉宣言条件を満たしていない入玉宣言
                                goto ILLEGAL_MOVE;
                            }
                            break;

                        // 中断
                        // コンピューター同士の対局の時にも人間判断で中断できなければならないので
                        // 対局中であればこれを無条件で受理する。
                        case Move.INTERRUPT:
                        // 時間切れ
                        // 時間切れになるとBestMoveに自動的にTIME_UPが積まれる。これも無条件で受理する。
                        case Move.TIME_UP:
                            break;

                        // 投了
                        case Move.RESIGN:
                            break;     // 手番側の投了は無条件で受理

                        // それ以外
                        default:
                            // それ以外は受理しない
                            goto ILLEGAL_MOVE;
                        }
                    }
                    else if (!Position.IsLegal(bestMove))
                    {
                        // 合法手ではない
                        goto ILLEGAL_MOVE;
                    }


                    // -- bestMoveを受理して、局面を更新する。

                    kifuManager.Tree.AddNode(bestMove, PlayTimers.GetKifuMoveTimes());

                    // 受理できる性質の指し手であることは検証済み
                    // special moveであってもDoMove()してしまう。
                    kifuManager.DoMove(bestMove);

                    KifuDirty = true; // 新しいnodeに到達したので棋譜は汚れた扱い。

                    // -- 音声の読み上げ

                    var soundManager = TheApp.app.SoundManager;

                    // special moveはMoveを直接渡して再生。
                    if (bestMove.IsSpecial())
                    {
                        soundManager.ReadOut(bestMove);
                    }
                    else
                    {
                        // -- 駒音

                        PlayPieceSound(GameMode, bestMove.To(), stm);

                        // -- 棋譜の読み上げ

                        // 「先手」と「後手」と読み上げる。
                        if (!sengo_read_out[(int)stm] || config.ReadOutSenteGoteEverytime != 0)
                        {
                            sengo_read_out[(int)stm] = true;

                            // 駒落ちの時は、「上手(うわて)」と「下手(したて)」
                            if (!Position.Handicapped)
                            {
                                soundManager.ReadOut(stm == Color.BLACK ? SoundEnum.Sente : SoundEnum.Gote);
                            }
                            else
                            {
                                soundManager.ReadOut(stm == Color.BLACK ? SoundEnum.Shitate : SoundEnum.Uwate);
                            }
                        }

                        // 棋譜文字列をそのまま頑張って読み上げる。
                        // ただし、棋譜ウィンドウの表示形式を変更できるので…。

                        var kif = kifuManager.Tree.LastKifuString;
                        soundManager.ReadOut(kif);
                    }
                }

                // -- 次のPlayerに、自分のturnであることを通知してやる。

                if (!specialMove)
                {
                    // 相手番になったので諸々通知。
                    NotifyTurnChanged();
                }
                else
                {
                    // 特殊な指し手だったので、これにてゲーム終了
                    GameEnd(bestMove);
                }
            }

            return;

ILLEGAL_MOVE:

            // これ、棋譜に記録すべき
            Move m = Move.ILLEGAL_MOVE;

            kifuManager.Tree.AddNode(m, PlayTimers.GetKifuMoveTimes());
            kifuManager.Tree.AddNodeComment(m, stmPlayer.BestMove.ToUsi() /* String あとでなおす*/ /* 元のテキスト */);
            kifuManager.Tree.DoMove(m);

            GameEnd(m); // これにてゲーム終了。
        }
Пример #12
0
        /// <summary>
        /// GameStart()とGameRestart()の共通部分
        /// </summary>
        private void GameStartInCommon(GameModeEnum nextGameMode)
        {
            var gameSetting = GameSetting;

            // 現在の時間設定を、KifuManager.Treeに反映させておく(棋譜保存時にこれが書き出される)
            kifuManager.Tree.KifuTimeSettings = gameSetting.KifuTimeSettings;

            // 対局者氏名の設定
            // 対局設定ダイアログの名前をそのまま引っ張ってくる。
            foreach (var c in All.Colors())
            {
                var name = gameSetting.PlayerSetting(c).PlayerName;
                SetPlayerName(c, name);
            }

            // 持ち時間設定の表示文字列の構築(最初に構築してしまい、対局中には変化させない)
            foreach (var c in All.Colors())
            {
                var pc = PlayTimer(c);
                pc.KifuTimeSetting = GameSetting.KifuTimeSettings.Player(c);
                pc.GameStart();

                var    left  = pc.KifuTimeSetting.ToShortString();
                var    right = PresetWithBoardTypeString(c);
                string total;
                if (right.Empty())
                {
                    total = left;
                }
                else if (left.Empty())
                {
                    total = right;                  // このとき連結のための"/"要らない
                }
                else if (left.UnicodeLength() + right.UnicodeLength() < 24)
                {
                    total = $"{left}/{right}";      // 1行で事足りる
                }
                else
                {
                    total = $"{left}\r\n{right}";   // 2行に分割する。
                }
                timeSettingStrings[(int)c] = total;
            }

            // rootの持ち時間設定をここに反映させておかないと待ったでrootまで持ち時間が戻せない。
            // 途中の局面からだとここではなく、現局面のところに書き出す必要がある。
            kifuManager.Tree.SetKifuMoveTimes(PlayTimers.GetKifuMoveTimes());

            // コンピュータ vs 人間である場合、人間側を手前にしてやる。
            // 人間 vs 人間の場合も最初の手番側を手前にしてやる。
            // ただし、人間 vs 人間で振り駒でこの手番が決まったのであれば、
            // それを反映しなければならない。

            var stm = kifuManager.Position.sideToMove;

            if (gameSetting.PlayerSetting(stm).IsHuman)
            {
                // 1. 両方が人間である場合、普通は手番側が手前だが、振り駒をしたなら
                //  対局設定の左側のプレイヤーがつねに手前。
                if (gameSetting.PlayerSetting(stm.Not()).IsHuman)
                {
                    BoardReverse = (stm == Color.WHITE) ^ continuousGame.Swapped;
                }

                // 2. 手番側が人間である場合(非手番側がCPU)
                else
                {
                    BoardReverse = (stm == Color.WHITE);
                }
            }
            // 3. 手番側がCPUで、非手番側が人間である場合。
            else if (gameSetting.PlayerSetting(stm).IsCpu&& gameSetting.PlayerSetting(stm.Not()).IsHuman)
            {
                BoardReverse = (stm == Color.BLACK);
            }

            // プレイヤー情報などを検討ダイアログに反映させる。
            InitEngineConsiderationInfo(nextGameMode);

            // 検討モードならそれを停止させる必要があるが、それはGameModeのsetterがやってくれる。
            GameMode = nextGameMode;
        }
Пример #13
0
        /// <summary>
        /// 対局開始のためにGameSettingの設定に従い、ゲームを初期化する。
        /// </summary>
        /// <param name="gameSetting"></param>
        private void GameStart(GameSetting gameSetting)
        {
            // 以下の初期化中に駒が動かされるの気持ち悪いのでユーザー操作を禁止しておく。
            CanUserMove  = false;
            Initializing = true;
            var nextGameMode = GameModeEnum.InTheGame;

            // 音声:「よろしくお願いします。」
            TheApp.app.soundManager.Stop(); // 再生中の読み上げをすべて停止
            TheApp.app.soundManager.ReadOut(SoundEnum.Start);

            // 初回の指し手で、「先手」「後手」と読み上げるためのフラグ
            sengo_read_out = new bool[2] {
                false, false
            };

            // プレイヤーの生成
            UsiEngineHashManager.Init();
            foreach (var c in All.Colors())
            {
                var gamePlayer = gameSetting.PlayerSetting(c);
                var playerType = gamePlayer.IsHuman ? PlayerTypeEnum.Human : PlayerTypeEnum.UsiEngine;
                Players[(int)c] = PlayerBuilder.Create(playerType);

                if (playerType == PlayerTypeEnum.UsiEngine)
                {
                    var engineDefineEx = TheApp.app.EngineDefines.Find(x => x.FolderPath == gamePlayer.EngineDefineFolderPath);
                    if (engineDefineEx == null)
                    {
                        TheApp.app.MessageShow("エンジンがありませんでした。" + gamePlayer.EngineDefineFolderPath, MessageShowType.Error);
                        return;
                    }
                    var usiEnginePlayer = Players[(int)c] as UsiEnginePlayer;
                    var ponder          = gamePlayer.Ponder;
                    InitUsiEnginePlayer(c, usiEnginePlayer, engineDefineEx, gamePlayer.SelectedEnginePreset, nextGameMode, ponder);
                }
            }

            // 局面の設定
            kifuManager.EnableKifuList = true;
            if (gameSetting.BoardSetting.BoardTypeCurrent)
            {
                // 現在の局面からなので、いま以降の局面を削除する。
                // ただし、いまの局面と棋譜ウィンドウとが同期しているとは限らない。
                // まず現在局面以降の棋譜を削除しなくてはならない。

                // 元nodeが、special moveであるなら、それを削除しておく。
                if (kifuManager.Tree.IsSpecialNode())
                {
                    kifuManager.Tree.UndoMove();
                }

                kifuManager.Tree.ClearForward();

                // 分岐棋譜かも知れないので、現在のものを本譜の手順にする。
                kifuManager.Tree.MakeCurrentNodeMainBranch();
            }
            else // if (gameSetting.Board.BoardTypeEnable)
            {
                kifuManager.Init();
                kifuManager.InitBoard(gameSetting.BoardSetting.BoardType);
            }

            // 本譜の手順に変更したので現在局面と棋譜ウィンドウのカーソルとを同期させておく。
            UpdateKifuSelectedIndex();

            // エンジンに与えるHashSize,Threadsの計算
            if (UsiEngineHashManager.CalcHashSize() != 0)
            {
                // Hash足りなくてダイアログ出した時にキャンセルボタン押されとる
                Disconnect();

                // ゲームが終局したことを通知するために音声があったほうがよさげ。
                TheApp.app.soundManager.ReadOut(SoundEnum.End);

                return;
            }

            // 現在の時間設定を、KifuManager.Treeに反映させておく(棋譜保存時にこれが書き出される)
            kifuManager.Tree.KifuTimeSettings = gameSetting.KifuTimeSettings;

            // 対局者氏名の設定
            // 人間の時のみ有効。エンジンの時は、エンジン設定などから取得することにする。
            foreach (var c in All.Colors())
            {
                var    player = Player(c);
                string name;
                switch (player.PlayerType)
                {
                case PlayerTypeEnum.Human:
                    name = gameSetting.PlayerSetting(c).PlayerName;
                    break;

                default:
                    name = c.Pretty();
                    break;
                }

                kifuManager.KifuHeader.SetPlayerName(c, name);
            }

            // 持ち時間などの設定が必要なので、コピーしておく。
            GameSetting = gameSetting;

            // 消費時間計算用
            foreach (var c in All.Colors())
            {
                var pc = PlayTimer(c);
                pc.KifuTimeSetting = GameSetting.KifuTimeSettings.Player(c);
                pc.GameStart();
                timeSettingStrings[(int)c] = pc.KifuTimeSetting.ToShortString();
            }

            // rootの持ち時間設定をここに反映させておかないと待ったでrootまで持ち時間が戻せない。
            // 途中の局面からだとここではなく、現局面のところに書き出す必要がある。
            kifuManager.Tree.SetKifuMoveTimes(PlayTimers.GetKifuMoveTimes());

            // コンピュータ vs 人間である場合、人間側を手前にしてやる。
            // 人間 vs 人間の場合も最初の手番側を手前にしてやる。
            var stm = kifuManager.Position.sideToMove;

            // 1. 手番側が人間である場合(非手番側が人間 or CPU)
            if (gameSetting.PlayerSetting(stm).IsHuman)
            {
                BoardReverse = (stm == Color.WHITE);
            }
            // 2. 手番側がCPUで、非手番側が人間である場合。
            else if (gameSetting.PlayerSetting(stm).IsCpu&& gameSetting.PlayerSetting(stm.Not()).IsHuman)
            {
                BoardReverse = (stm == Color.BLACK);
            }

            // プレイヤー情報などを検討ダイアログに反映させる。
            InitEngineConsiderationInfo(nextGameMode);

            // 検討モードならそれを停止させる必要があるが、それはGameModeのsetterがやってくれる。
            GameMode = nextGameMode;
        }
Пример #14
0
        /// <summary>
        /// 対局開始のためにGameSettingの設定に従い、ゲームを初期化する。
        /// </summary>
        /// <param name="gameSetting"></param>
        private void GameStart(GameSetting gameSetting)
        {
            // 以下の初期化中に駒が動かされるの気持ち悪いのでユーザー操作を禁止しておく。
            CanUserMove      = false;
            lastInitializing = true;

            // 音声:「よろしくお願いします。」
            TheApp.app.soundManager.Stop(); // 再生中の読み上げをすべて停止
            TheApp.app.soundManager.ReadOut(SoundEnum.Start);

            // 初回の指し手で、「先手」「後手」と読み上げるためのフラグ
            sengo_read_out = new bool[2] {
                false, false
            };

            // プレイヤーの生成
            foreach (var c in All.Colors())
            {
                var playerType = gameSetting.Player(c).IsHuman ? PlayerTypeEnum.Human : PlayerTypeEnum.UsiEngine;
                Players[(int)c] = PlayerBuilder.Create(playerType);
            }

            // 局面の設定
            kifuManager.EnableKifuList = true;
            if (gameSetting.Board.BoardTypeCurrent)
            {
                // 現在の局面からなので、いま以降の局面を削除する。
                // ただし、いまの局面と棋譜ウィンドウとが同期しているとは限らない。
                // まず現在局面以降の棋譜を削除しなくてはならない。

                // 元nodeが、special moveであるなら、それを削除しておく。
                if (kifuManager.Tree.IsSpecialNode())
                {
                    kifuManager.Tree.UndoMove();
                }

                kifuManager.Tree.ClearForward();

                // 分岐棋譜かも知れないので、現在のものを本譜の手順にする。
                kifuManager.Tree.MakeCurrentNodeMainBranch();
            }
            else // if (gameSetting.Board.BoardTypeEnable)
            {
                kifuManager.Init();
                kifuManager.InitBoard(gameSetting.Board.BoardType);
            }

            // 本譜の手順に変更したので現在局面と棋譜ウィンドウのカーソルとを同期させておく。
            UpdateKifuSelectedIndex();

            // 現在の時間設定を、KifuManager.Treeに反映させておく(棋譜保存時にこれが書き出される)
            kifuManager.Tree.KifuTimeSettings = gameSetting.KifuTimeSettings;

            // 対局者氏名の設定
            // 人間の時のみ有効。エンジンの時は、エンジン設定などから取得することにする。(TODO:あとで考える)
            foreach (var c in All.Colors())
            {
                var    player = Player(c);
                string name;
                switch (player.PlayerType)
                {
                case PlayerTypeEnum.Human:
                    name = gameSetting.Player(c).PlayerName;
                    break;

                default:
                    name = c.Pretty();
                    break;
                }

                kifuManager.KifuHeader.SetPlayerName(c, name);
            }

            // 持ち時間などの設定が必要なので、コピーしておく。
            GameSetting = gameSetting;

            // 消費時間計算用
            foreach (var c in All.Colors())
            {
                var pc = PlayTimer(c);
                pc.KifuTimeSetting = GameSetting.KifuTimeSettings.Player(c);
                pc.GameStart();
            }

            // rootの持ち時間設定をここに反映させておかないと待ったでrootまで持ち時間が戻せない。
            // 途中の局面からだとここではなく、現局面のところに書き出す必要がある。
            kifuManager.Tree.SetKifuMoveTimes(PlayTimers.GetKifuMoveTimes());

            // コンピュータ vs 人間である場合、人間側を手前にしてやる。
            // 人間 vs 人間の場合も最初の手番側を手前にしてやる。
            var stm = kifuManager.Position.sideToMove;

            // 1. 手番側が人間である場合(非手番側が人間 or CPU)
            if (gameSetting.Player(stm).IsHuman)
            {
                BoardReverse = (stm == Color.WHITE);
            }
            // 2. 手番側がCPUで、非手番側が人間である場合。
            else if (gameSetting.Player(stm).IsCpu&& gameSetting.Player(stm.Not()).IsHuman)
            {
                BoardReverse = (stm == Color.BLACK);
            }

            // プレイヤー情報などを検討ダイアログに反映させる。
            var nextGameMode = GameModeEnum.InTheGame;

            InitEngineConsiderationInfo(nextGameMode);

            // 検討モードならそれを停止させる必要があるが、それはGameModeのsetterがやってくれる。
            GameMode = nextGameMode;
        }
Пример #15
0
        /// <summary>
        /// 指し手が指されたかのチェックを行う
        /// </summary>
        private void CheckPlayerMove()
        {
            // 現状の局面の手番側
            var stm       = Position.sideToMove;
            var stmPlayer = Player(stm);

            var config = TheApp.app.config;

            // -- 指し手

            Move bestMove;

            if (GameMode.IsWithEngine())
            {
                // 検討モードなのでエンジンから送られてきたbestMoveの指し手は無視。
                bestMove = stmPlayer.SpecialMove;
            }
            else
            {
                // TIME_UPなどのSpecialMoveが積まれているなら、そちらを優先して解釈する。
                bestMove = stmPlayer.SpecialMove != Move.NONE ? stmPlayer.SpecialMove : stmPlayer.BestMove;
            }

            if (bestMove != Move.NONE)
            {
                PlayTimer(stm).ChageToThemTurn(bestMove == Move.TIME_UP);

                stmPlayer.SpecialMove = Move.NONE; // クリア

                // 駒が動かせる状況でかつ合法手であるなら、受理する。

                bool specialMove = false;
                if (GameMode == GameModeEnum.InTheGame)
                {
                    // 送信されうる特別な指し手であるか?
                    specialMove = bestMove.IsSpecial();

                    // エンジンから送られてきた文字列が、illigal moveであるならエラーとして表示する必要がある。

                    if (specialMove)
                    {
                        switch (bestMove)
                        {
                        // 入玉宣言勝ち
                        case Move.WIN:
                            if (Position.DeclarationWin(EnteringKingRule.POINT27) != Move.WIN)
                            {
                                // 入玉宣言条件を満たしていない入玉宣言
                                goto ILLEGAL_MOVE;
                            }
                            break;

                        // 中断
                        // コンピューター同士の対局の時にも人間判断で中断できなければならないので
                        // 対局中であればこれを無条件で受理する。
                        case Move.INTERRUPT:
                        // 時間切れ
                        // 時間切れになるとBestMoveに自動的にTIME_UPが積まれる。これも無条件で受理する。
                        case Move.TIME_UP:
                            break;

                        // 投了
                        case Move.RESIGN:
                            break;     // 手番側の投了は無条件で受理

                        // それ以外
                        default:
                            // それ以外は受理しない
                            goto ILLEGAL_MOVE;
                        }
                    }
                    else if (!Position.IsLegal(bestMove))
                    {
                        // 合法手ではない
                        goto ILLEGAL_MOVE;
                    }


                    // -- bestMoveを受理して、局面を更新する。

                    kifuManager.Tree.AddNode(bestMove, PlayTimers.GetKifuMoveTimes());

                    // 受理できる性質の指し手であることは検証済み
                    // special moveであってもDoMove()してしまう。
                    kifuManager.DoMove(bestMove);

                    // -- 音声の読み上げ

                    var soundManager = TheApp.app.soundManager;

                    var kif = kifuManager.KifuList[kifuManager.KifuList.Count - 1];
                    // special moveはMoveを直接渡して再生。
                    if (bestMove.IsSpecial())
                    {
                        soundManager.ReadOut(bestMove);
                    }
                    else
                    {
                        // -- 駒音

                        if (TheApp.app.config.PieceSoundInTheGame != 0)
                        {
                            // 移動先の升の下に別の駒があるときは、駒がぶつかる音になる。
                            var to    = bestMove.To();
                            var delta = stm == Color.BLACK ? Square.SQ_D : Square.SQ_U;
                            var to2   = to + (int)delta;
                            // to2が盤外であることがあるので、IsOk()を通すこと。
                            var e = (to2.IsOk() && Position.PieceOn(to2) != Piece.NO_PIECE)
                                ? SoundEnum.KOMA_B1 /*ぶつかる音*/: SoundEnum.KOMA_S1 /*ぶつからない音*/;

#if false
                            // あまりいい効果音作れなかったのでコメントアウトしとく。
                            if (TheApp.app.config.CrashPieceSoundInTheGame != 0)
                            {
                                // ただし、captureか捕獲する指し手であるなら、衝撃音に変更する。
                                if (Position.State().capturedPiece != Piece.NO_PIECE || Position.InCheck())
                                {
                                    e = SoundEnum.KOMA_C1;
                                }
                            }
#endif
                            soundManager.PlayPieceSound(e);
                        }

                        // -- 棋譜の読み上げ

                        // 「先手」と「後手」と読み上げる。
                        if (!sengo_read_out[(int)stm] || config.ReadOutSenteGoteEverytime != 0)
                        {
                            sengo_read_out[(int)stm] = true;

                            // 駒落ちの時は、「上手(うわて)」と「下手(したて)」
                            if (!Position.Handicapped)
                            {
                                soundManager.ReadOut(stm == Color.BLACK ? SoundEnum.Sente : SoundEnum.Gote);
                            }
                            else
                            {
                                soundManager.ReadOut(stm == Color.BLACK ? SoundEnum.Shitate : SoundEnum.Uwate);
                            }
                        }

                        // 棋譜文字列をそのまま頑張って読み上げる。
                        soundManager.ReadOut(kif);
                    }
                }

                // -- 次のPlayerに、自分のturnであることを通知してやる。

                if (!specialMove)
                {
                    NotifyTurnChanged();
                }
                else
                {
                    // 特殊な指し手だったので、これにてゲーム終了
                    GameEnd();
                }
            }

            return;

ILLEGAL_MOVE:

            // これ、棋譜に記録すべき
            Move m = Move.ILLEGAL_MOVE;
            kifuManager.Tree.AddNode(m, PlayTimers.GetKifuMoveTimes());
            kifuManager.Tree.AddNodeComment(m, stmPlayer.BestMove.ToUsi() /* String あとでなおす*/ /* 元のテキスト */);
            kifuManager.Tree.DoMove(m);

            GameEnd(); // これにてゲーム終了。
        }
Пример #16
0
 /// <summary>
 /// プレイヤーの消費時間を計測する用
 /// </summary>
 /// <param name="c"></param>
 /// <returns></returns>
 public PlayTimer PlayTimer(Color c)
 {
     return(PlayTimers.Player(c));
 }
Пример #17
0
        /// <summary>
        /// GameStart()とGameRestart()の共通部分
        /// </summary>
        private void GameStartInCommon(GameModeEnum nextGameMode)
        {
            var gameSetting = GameSetting;

            // 現在の時間設定を、KifuManager.Treeに反映させておく(棋譜保存時にこれが書き出される)
            kifuManager.Tree.KifuTimeSettings = gameSetting.KifuTimeSettings;

            // 対局者氏名の設定
            // 人間の時のみ有効。エンジンの時は、エンジン設定などから取得することにする。
            foreach (var c in All.Colors())
            {
                var    player = Player(c);
                string name;
                switch (player.PlayerType)
                {
                case PlayerTypeEnum.Human:
                    name = gameSetting.PlayerSetting(c).PlayerName;
                    break;

                default:
                    name = c.Pretty();
                    break;
                }

                kifuManager.KifuHeader.SetPlayerName(c, name);
            }

            // 消費時間計算用
            foreach (var c in All.Colors())
            {
                var pc = PlayTimer(c);
                pc.KifuTimeSetting = GameSetting.KifuTimeSettings.Player(c);
                pc.GameStart();
                timeSettingStrings[(int)c] = pc.KifuTimeSetting.ToShortString();
            }

            // rootの持ち時間設定をここに反映させておかないと待ったでrootまで持ち時間が戻せない。
            // 途中の局面からだとここではなく、現局面のところに書き出す必要がある。
            kifuManager.Tree.SetKifuMoveTimes(PlayTimers.GetKifuMoveTimes());

            // コンピュータ vs 人間である場合、人間側を手前にしてやる。
            // 人間 vs 人間の場合も最初の手番側を手前にしてやる。
            var stm = kifuManager.Position.sideToMove;

            // 1. 手番側が人間である場合(非手番側が人間 or CPU)
            if (gameSetting.PlayerSetting(stm).IsHuman)
            {
                BoardReverse = (stm == Color.WHITE);
            }
            // 2. 手番側がCPUで、非手番側が人間である場合。
            else if (gameSetting.PlayerSetting(stm).IsCpu&& gameSetting.PlayerSetting(stm.Not()).IsHuman)
            {
                BoardReverse = (stm == Color.BLACK);
            }

            // プレイヤー情報などを検討ダイアログに反映させる。
            InitEngineConsiderationInfo(nextGameMode);

            // 検討モードならそれを停止させる必要があるが、それはGameModeのsetterがやってくれる。
            GameMode = nextGameMode;
        }