/// <summary> /// [UI Thread] : ヘッダー情報のところに反映させる。 /// info.XXXの値がnullになっている項目は書き換えない。 /// 書き換えたいならば、string.Emptyを設定すること。 /// </summary> /// <param name="info"></param> private void UpdateHeader(UsiThinkReport info) { // .NET FrameworkのTextBox、右端にスペースをpaddingしていて、TextAlignをcenterに設定してもそのスペースを // わざわざ除去してからセンタリングするので(余計なお世話)、TextAlignをLeftに設定して、自前でpaddingする。 // MS UI Gothicは等幅ではないのでスペースでpaddingするとずれる。 // TextBoxのフォントは、MS ゴシックに設定する。 //textBox1.Text = info.PlayerName; if (info.PonderMove != null) { textBox2.Text = $" 予想手 : { info.PonderMove.PadLeftUnicode(6)}"; } //textBox3.Text = $"探索手:{info.SearchingMove}"; // 探索手、エンジン側、まともに出力してると出力だけで時間すごくロスするので // 出力してくるエンジン少なめだから、これ不要だと思う。 //textBox4.Text = $"深さ:{info.Depth}/{info.SelDepth}"; // 深さも各iterationごとにPVを出力しているわけで、こんなものは不要である。 if (info.NodesString != null) { textBox3.Text = $"探索局面数 : { info.NodesString.PadLeftUnicode(12) }"; } if (info.NpsString != null) { textBox4.Text = $" NPS : { info.NpsString.PadLeftUnicode(12) }"; } if (info.HashPercentageString != null) { textBox5.Text = $"HASH使用率 : { info.HashPercentageString.PadLeftUnicode(6) }"; // 50%以上なら赤文字にして、HASHが足りないことをアピール。 textBox5.ForeColor = info.HashPercentage < 50 ? Draw.Color.Black : Draw.Color.Red; } }
/// <summary> /// [UI Thread] : 読み筋を1行追加する。 /// </summary> /// <param name="info"></param> public void AddThinkReport(UsiThinkReport info) { if (info.Moves != null || info.InfoString != null) { // -- 指し手文字列の構築 // Positionクラスを用いて指し手文字列を構築しないといけない。 // UI Threadからしかこのメソッドを呼び出さないことは保証されているので、 // positionのimmutable性は保つ必要はなく、Position.DoMove()~UndoMove()して良いが、素直にCloneしたほうが速いと思われ.. var pos = position.Clone(); var kifuString = new StringBuilder(); // kifuStringに文字列を追加するlocal method。 // 文字列を追加するときに句切りのスペースを自動挿入する。 void append(string s) { if (kifuString.Length != 0) { kifuString.Append(' '); } kifuString.Append(s); } if (info.Moves != null) { var moves = new List <Move>(); foreach (var move in info.Moves) { if (!pos.IsLegal(move)) { if (move.IsSpecial()) { append(move_to_kif_string(pos, move)); } else if (move.To() != Square.NB) { // 非合法手に対してKIF2の文字列を生成しないようにする。(それが表示できるとは限らないので..) // また、Square.NBはparseに失敗した文字列であるから、これは出力する意味がない。(あえて出力するなら元の文字列を出力してやるべき) append($"非合法手({ move.Pretty()})"); } break; } append(move_to_kif_string(pos, move)); moves.Add(move); // このあと盤面表示用にmovesを保存するが、 // 非合法局面の指し手を渡すことは出来ないので、合法だとわかっている指し手のみを保存しておく。 pos.DoMove(move); } } else { kifuString.Append(info.InfoString); // この文字列を読み筋として突っ込む。 } // -- listView.Itemsに追加 // それぞれの項目、nullである可能性を考慮しながら表示すべし。 // 経過時間、1/10秒まで表示する。 // "info string"の文字列を表示する時は、info.Eval == nullでkifuStringにその表示すべき文字列が渡されてここに来るので注意。 var elpasedTimeString = info.ElapsedTime == null ? null : info.ElapsedTime.ToString("hh':'mm':'ss'.'ff"); var ranking = info.MultiPvString == null ? "1" : info.MultiPvString; var depthString = info.Eval == null ? null : (info.Depth != null && info.SelDepth != null) ? $"{info.Depth}/{info.SelDepth}" : (info.Depth == null ? null : info.Depth.ToString()); // info.Evalは自分から見たスコアが格納されている。 // 後手番の時に先手から見た評価値を表示する設定であるなら、評価値の表示を反転させる。 // ここで表示している値、保存していないので即時反映は無理だわ…。まあ、これは仕様ということで…。 var isWhite = position.sideToMove == Model.Shogi.Core.Color.WHITE; // 形勢判断の文字列を出力する。 var evalJudgement = TheApp.app.Config.DisplayEvalJudgement; var evalJudgementString = (evalJudgement == 0 || info.Eval == null) ? null : !isWhite?info.Eval.Eval.ToEvalJudgement() : // 先手 info.Eval.negate().Eval.ToEvalJudgement(); // 後手 if (isWhite && TheApp.app.Config.NegateEvalWhenWhite) { if (info.Eval != null) { info.Eval = info.Eval.negate(); } } var evalString = info.Eval == null ? null : evalJudgementString == null?info.Eval.Eval.Pretty() : $"{evalJudgementString}({info.Eval.Eval.Pretty()})"; var evalBound = info.Eval == null ? null : info.Eval.Bound.Pretty(); kifuString.Append(info.MovesSuffix); var pvString = kifuString.ToString(); var list = new[] { ranking, // MultiPVの順 elpasedTimeString, // 思考時間 depthString, // 探索深さ info.NodesString, // ノード数 evalString, // 評価値 evalBound, // "+-" pvString, // 読み筋 }; var item = new ListViewItem(list); if (sortRanking) { int r; if (!int.TryParse(ranking, out r) || r < 1) { r = 1; } while (listView1.Items.Count < r) { listView1.Items.Add(string.Empty); list_item_moves.Add(null); } // r行目のところに代入 list_item_moves[r - 1] = info.Moves; // listView1.Items[r - 1] = item; // r行目しか代入していないのに再描画でちらつく。 // ListView、ダブルバッファにしているにも関わらず。 // .NET FrameworkのListView、出来悪すぎない? var old = listView1.Items[r - 1]; if (old.SubItems.Count == list.Length) { // 要素一つひとつ入替えてやる。 // これならちらつかない。なんなんだ、このバッドノウハウ…。 for (int i = 0; i < list.Length; ++i) { old.SubItems[i].Text = list[i]; } } else { // 要素数が異なるので丸ごと入れ替える。 listView1.Items[r - 1] = item; } // r-1が選択されていたなら、この選択を解除してやるほうがいいような? // 解除するとカーソルキーで上下で移動するときに問題があるのか…そうか…。 // TODO : エンジンを使った検討モードで更新された読み筋を再度表示する手段が…再クリックとかenterとか // 何かで再表示されて欲しい気はする。 } else { listView1.Items.Add(item); // 読み筋をここに保存しておく。(ミニ盤面で開く用) なければnullもありうる。 list_item_moves.Add(info.Moves); try { // 検討ウィンドウの縦幅を縮めているとTopItemへの代入がぬるぽになる。 listView1.TopItem = item; // 自動スクロール } catch { } } } // -- その他、nullでない項目に関して、ヘッダー情報のところに反映させておく。 UpdateHeader(info); }
/// <summary> /// [UI Thread] : 読み筋を1行追加する。 /// </summary> /// <param name="info"></param> public void AddThinkReport(UsiThinkReport info) { if (info.Moves != null || info.InfoString != null) { // -- 指し手文字列の構築 // Positionクラスを用いて指し手文字列を構築しないといけない。 // UI Threadからしかこのメソッドを呼び出さないことは保証されているので、 // positionのimmutable性は保つ必要はなく、Position.DoMove()~UndoMove()して良いが、素直にCloneしたほうが速いと思われ.. var pos = position.Clone(); var kifuString = new StringBuilder(); // kifuStringに文字列を追加するlocal method。 // 文字列を追加するときに句切りのスペースを自動挿入する。 void append(string s) { if (kifuString.Length != 0) { kifuString.Append(' '); } kifuString.Append(s); } if (info.Moves != null) { var moves = new List <Move>(); foreach (var move in info.Moves) { if (!pos.IsLegal(move)) { if (move.IsSpecial()) { append(move_to_kif_string(pos, move)); } else { // 非合法手に対してKIF2の文字列を生成しないようにする。(それが表示できるとは限らないので..) append($"非合法手({ move.Pretty()})"); } break; } append(move_to_kif_string(pos, move)); moves.Add(move); // このあと盤面表示用にmovesを保存するが、 // 非合法局面の指し手を渡すことは出来ないので、合法だとわかっている指し手のみを保存しておく。 pos.DoMove(move); } } else { kifuString.Append(info.InfoString); // この文字列を読み筋として突っ込む。 } // -- listView.Itemsに追加 // それぞれの項目、nullである可能性を考慮しながら表示すべし。 // 経過時間、1/10秒まで表示する。 var elpasedTimeString = info.ElapsedTime == null ? null : info.ElapsedTime.ToString("hh':'mm':'ss'.'f"); var ranking = info.MultiPvString == null ? "1" : info.MultiPvString; var list = new[] { ranking, // MultiPVの順 elpasedTimeString, // 思考時間 $"{info.Depth}/{info.SelDepth}", // 探索深さ info.NodesString, // ノード数 info.Eval.Eval.Pretty(), // 評価値 info.Eval.Bound.Pretty(), // "+-" kifuString.ToString() // 読み筋 }; var item = new ListViewItem(list); if (sortRanking) { int r; if (!int.TryParse(ranking, out r)) { r = 1; } while (listView1.Items.Count < r) { listView1.Items.Add(string.Empty); list_item_moves.Add(null); } // r行目のところに代入 listView1.Items[r - 1] = item; list_item_moves[r - 1] = info.Moves; } else { listView1.Items.Add(item); // 読み筋をここに保存しておく。(ミニ盤面で開く用) なければnullもありうる。 list_item_moves.Add(info.Moves); try { // 検討ウィンドウの縦幅を縮めているとTopItemへの代入がぬるぽになる。 listView1.TopItem = item; // 自動スクロール } catch { } } } // -- その他、nullでない項目に関して、ヘッダー情報のところに反映させておく。 UpdateHeader(info); }