/// <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);
        }
Пример #3
0
        /// <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);
        }