/// <summary> /// 1局目開始時のプレイヤー名。プリセットつき。 /// </summary> /// <param name="c"></param> /// <returns></returns> private string PlayerName(Color c) { var playerName = GameSetting.PlayerSetting(c).PlayerName; var preset = presetNames2[(int)c]; return((preset == null) ? playerName : $"{playerName}({preset})"); }
/// <summary> /// 棋譜のファイルへの書き出しコマンド /// </summary> /// <param name="path"></param> /// <param name="type"></param> public void KifuWriteCommand(string path, Kifu.KifuFileType type) { AddCommand( () => { // ゲーム中でも書き出せる // (メニュー上、オフにはなっているが..) try { // プレイヤー名を棋譜上に反映させる。 foreach (var c in All.Colors()) { kifuManager.KifuHeader.SetPlayerName(c, GameSetting.PlayerSetting(c).PlayerName); } var content = kifuManager.ToString(type); FileIO.WriteFile(path, content); } catch (System.Exception e) { TheApp.app.MessageShow($"棋譜ファイルの書き出しに失敗しました。\n{e}", MessageShowType.Error); } }); }
/// <summary> /// 画面上に表示する名前を取得する。 /// 文字数制限はないので注意。 /// </summary> /// <param name="c"></param> /// <returns></returns> public string DisplayName(Color c) { // 棋譜上の名前 //return kifuManager.KifuHeader.GetPlayerName(c); // 対局ダイアログの設定を活かす return(GameSetting.PlayerSetting(c).PlayerName); }
/// <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, MessageShowType.Error); kifuManager.Init(); // 不正な局面のままになるとまずいので初期化。 } else { // 読み込みが完了すれば自動的に末尾の局面に行っているはずだが、 // 棋譜ウィンドウを更新した結果、分岐局面などに戻ってしまうといけない。 // 棋譜に書かれていた持ち時間設定・残り時間を画面に反映させる。(GameSettingには反映させない) PlayTimers.SetKifuTimeSettings(kifuManager.Tree.KifuTimeSettings); PlayTimers.SetKifuMoveTimes(kifuManager.Tree.GetKifuMoveTimes()); UpdateTimeString(); // 末尾の局面に移動するコマンドを叩いておく。 SetValueAndRaisePropertyChanged("KifuListSelectedIndex", kifuManager.KifuList.Count - 1); // -- 棋譜上の名前をプレイヤー名に反映させる。 // GameSetting、原則immutableだが、まあいいや…。 foreach (var c in All.Colors()) { GameSetting.PlayerSetting(c).PlayerName = kifuManager.KifuHeader.GetPlayerName(c); } } // 棋譜が綺麗になった扱いにする。(この棋譜はファイルなどに丸ごと保存されているはずであるから) KifuDirty = false; // 駒を持ち上げていたりしたらそれをリセットする必要があるので。 RaisePropertyChanged("TurnChanged"); } }); }
/// <summary> /// 現在の局面のクリップボードへの書き出しコマンド /// </summary> /// <param name="type"></param> public void PositionWriteClipboardCommand(Kifu.KifuFileType type) { AddCommand(() => { try { // プレイヤー名を棋譜上に反映させる。 foreach (var c in All.Colors()) { kifuManager.KifuHeader.SetPlayerName(c, GameSetting.PlayerSetting(c).PlayerName); } var content = kifuManager.ToPositionString(type); TheApp.app.UIThread(() => System.Windows.Forms.Clipboard.SetText(content)); } catch (System.Exception e) { TheApp.app.MessageShow($"局面のクリップボードへの書き出しに失敗しました。\n{e}", MessageShowType.Error); } }); }
/// <summary> /// 棋譜のクリップボードへの書き出しコマンド /// </summary> /// <param name="type"></param> public void KifuWriteClipboardCommand(Kifu.KifuFileType type) { AddCommand(() => { try { // プレイヤー名を棋譜上に反映させる。 foreach (var c in All.Colors()) { kifuManager.KifuHeader.SetPlayerName(c, GameSetting.PlayerSetting(c).PlayerName); } var content = kifuManager.ToString(type); // Clipboard.SetText() を実行するスレッドは Single Thread Apartment モードに設定されていなければならない TheApp.app.UIThread(() => System.Windows.Forms.Clipboard.SetText(content)); } catch (System.Exception e) { TheApp.app.MessageShow($"棋譜のクリップボードへの書き出しに失敗しました。\n{e}", MessageShowType.Error); } }); }
/// <summary> /// プレイヤー情報を検討ダイアログにリダイレクトする設定をする。 /// </summary> private void InitEngineConsiderationInfo(GameModeEnum nextGameMode) { // CPUの数をNumberOfEngineに反映。 int num = 0; if (nextGameMode.IsConsiderationWithEngine()) { num = 1; // エンジンによる検討モードなら出力は一つ。 } else { foreach (var c in All.Colors()) { if (GameSetting.PlayerSetting(c).IsCpu) { ++num; } } } NumberOfEngine = num; // エンジン数が確定したので、検討ウィンドウにNumberOfInstanceメッセージを送信してやる。 ThinkReport = new UsiThinkReportMessage() { type = UsiEngineReportMessageType.NumberOfInstance, number = NumberOfEngine, }; ThinkReport = new UsiThinkReportMessage() { type = UsiEngineReportMessageType.SetGameMode, data = nextGameMode }; // 各エンジンの情報を検討ウィンドウにリダイレクトするようにハンドラを設定 num = 0; foreach (var c in All.Colors()) { if ((nextGameMode == GameModeEnum.InTheGame && GameSetting.PlayerSetting(c).IsCpu) || (nextGameMode.IsConsiderationWithEngine() && c == Color.BLACK) // // 検討用エンジンがぶら下がっていると考えられる。 ) { var num_ = num; // copy for capturing var engineName = GetEngineDefine(c).EngineDefine.DisplayName; var engineName2 = continuousGame.PresetName(c) == null ? engineName : $"{engineName} { continuousGame.PresetName(c)}"; var playerName = (nextGameMode.IsConsiderationWithEngine() || DisplayName(c) == engineName) ? // 検討時には、エンジンの名前をそのまま表示。 engineName2 : // 通常対局モードなら対局者名に括弧でエンジン名を表記。 $"{DisplayName(c)}({engineName2})"; ThinkReport = new UsiThinkReportMessage() { type = UsiEngineReportMessageType.SetEngineName, number = num_, // is captured data = playerName, }; // UsiEngineのThinkReportプロパティを捕捉して、それを転送してやるためのハンドラをセットしておく。 var engine_player = Player(c) as UsiEnginePlayer; engine_player.Engine.AddPropertyChangedHandler("ThinkReport", (args) => { //// 1) 読み筋の抑制条件その1 //// 人間対CPUで、メニューの「ウィンドウ」のところで表示するになっていない場合。 //var surpress1 = NumberOfEngine == 1 && !TheApp.app.config.EngineConsiderationWindowEnableWhenVsHuman; if (ThinkReportEnable /* && !(surpress1) */) { var report = args.value as UsiThinkReport; // このクラスのpropertyのsetterを呼び出してメッセージを移譲してやる。 ThinkReport = new UsiThinkReportMessage() { type = UsiEngineReportMessageType.UsiThinkReport, number = num_, // is captrued data = report, }; } }); num++; } } }
/// <summary> /// 対局開始のためにGameSettingの設定に従い、ゲームを初期化する。 /// </summary> /// <param name="gameSetting"></param> private void GameStart(GameSetting gameSetting) { // エンジン検討中であるなら、まずそれを停止させる。(通常検討モードに移行する) // これは、GameModeへの代入によって自動的に処理がなされる。 if (GameMode.IsConsiderationWithEngine()) { GameMode = GameModeEnum.ConsiderationWithoutEngine; } // 持ち時間などの設定が必要なので、 // GameStart()時点のGameSettingをこのクラスのpropertyとしてコピーしておく。 GameSetting = gameSetting; var nextGameMode = GameModeEnum.InTheGame; // -- 連続対局の回数をセット var misc = gameSetting.MiscSettings; // CPU同士の時のみ連続対局が有効である。 // ContinuousGameの初期化 { continuousGame.SetPlayLimit( gameSetting.PlayerSetting(Color.BLACK).IsCpu&& gameSetting.PlayerSetting(Color.WHITE).IsCpu&& misc.ContinuousGameEnable ? misc.ContinuousGame : 0 ); // 連続対局時にはプレイヤー入れ替えなどで壊す可能性があるのでClone()して保存しておく。 continuousGame.GameSetting = gameSetting.Clone(); // 対局開始時の振り駒のアニメーションのため、こちらにコピーして使う。 continuousGame.EnablePieceToss = gameSetting.MiscSettings.EnablePieceToss; // 振り駒をするのかのチェック CheckPieceToss(nextGameMode); } // 以下の初期化中に駒が動かされるの気持ち悪いのでユーザー操作を禁止しておく。 CanUserMove = false; Initializing = true; var config = TheApp.app.Config; // 音声:「よろしくお願いします。」 TheApp.app.SoundManager.Stop(); // 再生中の読み上げをすべて停止 if (config.ReadOutGreeting != 0) { 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); } // Players[]の生成が終わったので、必要ならば画面に「エンジン初期化中」の画像を描画する。 UpdateEngineInitializing(); foreach (var c in All.Colors()) { // これ書くの2度目だが、まあ、しゃーない。 var gamePlayer = gameSetting.PlayerSetting(c); var playerType = gamePlayer.IsHuman ? PlayerTypeEnum.Human : PlayerTypeEnum.UsiEngine; 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(); // View側の都合により選択行が移動してしまう可能性がある。 // 連続対局が設定されているなら、現在の局面を棋譜文字列として保存しておく。 if (continuousGame.IsContinuousGameSet()) { continuousGame.Kif = kifuManager.ToString(KifuFileType.KIF); } } else // if (gameSetting.Board.BoardTypeEnable) { kifuManager.Init(); kifuManager.InitBoard(gameSetting.BoardSetting.BoardType); } // 本譜の手順に変更したので現在局面と棋譜ウィンドウのカーソルとを同期させておく。 UpdateKifuSelectedIndex(int.MaxValue /* 末尾に移動 */); // エンジンに与えるHashSize,Threadsの計算 var firstOfContinuousGame = continuousGame.PlayCount == 0; // 連続対局の初回局である if (UsiEngineHashManager.CalcHashSize(firstOfContinuousGame) != 0) { // Hash足りなくてダイアログ出した時にキャンセルボタン押されとる Disconnect(); // ゲームが終局したことを通知するために音声があったほうがよさげ。 TheApp.app.SoundManager.ReadOut(SoundEnum.End); return; } // エンジンを開始させることが確定したので実際に子プロセスとして起動する。 // 1) このタイミングにしないと、Hashが足りなくてユーザーがキャンセルする可能性があって、 // それまでにエンジンがタイムアウトになりかねない。 // 2) エンジンを起動させてから、Hashの計算をするのでは、エンジンを起動させる時間が無駄である。 foreach (var c in All.Colors()) { // これ書くの3度目だが、まあしゃーない…。 var gamePlayer = gameSetting.PlayerSetting(c); var playerType = gamePlayer.IsHuman ? PlayerTypeEnum.Human : PlayerTypeEnum.UsiEngine; if (playerType == PlayerTypeEnum.UsiEngine) { var engineDefineEx = TheApp.app.EngineDefines.Find(x => x.FolderPath == gamePlayer.EngineDefineFolderPath); var usiEnginePlayer = Players[(int)c] as UsiEnginePlayer; // これで子プロセスとして起動する。 StartEngine(usiEnginePlayer, engineDefineEx); } } // Restart処理との共通部分 GameStartInCommon(nextGameMode); // 新規対局で手番が変わった。 //NotifyTurnChanged(); // → エンジンの初期化が現時点では終わっていない。 // ゆえに、UpdateInitializing()のなかで初期化が終わったタイミングでNotifyTurnChanged()を呼ぶ。 // それまではTimeUpの処理をしてはならない。 }
/// <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; }
/// <summary> /// 対局開始のためにGameSettingの設定に従い、ゲームを初期化する。 /// </summary> /// <param name="gameSetting"></param> private void GameStart(GameSetting gameSetting) { // -- 連続対局の回数をセット var misc = gameSetting.MiscSettings; // CPU同士の時のみ連続対局が有効である。 ContinuousGame = gameSetting.PlayerSetting(Color.BLACK).IsCpu&& gameSetting.PlayerSetting(Color.WHITE).IsCpu&& misc.ContinuousGameEnable ? misc.ContinuousGame : 0; ContinuousGameCount = 0; // 以下の初期化中に駒が動かされるの気持ち悪いのでユーザー操作を禁止しておく。 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); } // Players[]の生成が終わったので、必要ならば画面に「エンジン初期化中」の画像を描画する。 UpdateInitializing(); foreach (var c in All.Colors()) { // これ書くの2度目だが、まあ、しゃーない。 var gamePlayer = gameSetting.PlayerSetting(c); var playerType = gamePlayer.IsHuman ? PlayerTypeEnum.Human : PlayerTypeEnum.UsiEngine; 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の計算 var firstOfContinuousGame = ContinuousGameCount == 0; // 連続対局の初回局である if (UsiEngineHashManager.CalcHashSize(firstOfContinuousGame) != 0) { // Hash足りなくてダイアログ出した時にキャンセルボタン押されとる Disconnect(); // ゲームが終局したことを通知するために音声があったほうがよさげ。 TheApp.app.SoundManager.ReadOut(SoundEnum.End); return; } // 持ち時間などの設定が必要なので、コピーしておく。 GameSetting = gameSetting; // Restart処理との共通部分 GameStartInCommon(nextGameMode); }