Pythonで〇×ゲームのAIを一から作成する　その162　αβ 法の視覚化のための Mbtree_Anim の修正 その 3

# 目次と前回の記事

https://qiita.com/ysgeso/items/2381dd4e3283cbed49a0

https://qiita.com/ysgeso/items/b10991d23a8c084c2bba

## これまでに作成したモジュール

以下のリンクから、これまでに作成したモジュールを見ることができます。

| リンク | 説明 |
|:--|:--|
| [marubatsu.py](https://github.com/ysgeso/marubatsu/blob/master/162/marubatsu.py)  | Marubatsu、Marubatsu_GUI クラスの定義|
| [ai.py](https://github.com/ysgeso/marubatsu/blob/master/162/ai.py)  |  AI に関する関数 |
| [test.py](https://github.com/ysgeso/marubatsu/blob/master/162/test.py)  |  テストに関する関数 |
|  [util.py](https://github.com/ysgeso/marubatsu/blob/master/162/util.py) |  ユーティリティ関数の定義。現在は `gui_play` のみ定義されている |
| [tree.py](https://github.com/ysgeso/marubatsu/blob/master/162/tree.py)  | ゲーム木に関する Node、Mbtree クラスの定義  |
| [gui.py](https://github.com/ysgeso/marubatsu/blob/master/162/gui.py)  |  GUI に関する処理を行う基底クラスとなる GUI クラスの定義  |

AI の一覧とこれまでに作成したデータファイルについては、下記の記事を参照して下さい。

https://qiita.com/ysgeso/items/10d1d01192c014173e4b

# α 値または β 値の初期値に関するバグの修正

前回の記事では αβ 法の視覚化 のための Mbtree_Anim の修正の続きを行いましたが、その際に __["tt" の状態で表示する内容の一部にバグが存在する](https://qiita.com/ysgeso/items/b10991d23a8c084c2bba#%E6%9D%A1%E4%BB%B6-5-%E3%81%AE%E8%A1%A8%E7%A4%BA%E3%81%AE%E5%95%8F%E9%A1%8C%E7%82%B9)__ ことがわかりましたので、最初にそのバグを修正することにします。

## バグの原因

[以前の記事](https://qiita.com/ysgeso/items/c43a1c7dc65b746c8394#l--%CE%B1--%CE%B2-%E3%81%AE%E5%A0%B4%E5%90%88)で説明したように、"tt" の状態で行われる __α 値または β 値の初期値の更新__ は、α 値と β 値の初期値の範囲を __置換表の範囲外の値が含まれない__ ように __範囲を狭める方向で更新する__ ことで、__枝狩りの効率を上げる__ という目的で行います。

その際に行う __更新処理__ は[以前の記事](https://qiita.com/ysgeso/items/c43a1c7dc65b746c8394#exact-value-%E3%81%8C%E5%90%AB%E3%81%BE%E3%82%8C%E3%82%8B%E5%A0%B4%E5%90%88%E3%81%AE%E5%87%A6%E7%90%86%E3%81%AE%E8%A8%98%E8%BF%B0)で説明したように、__以下の式で計算__ します。なお、[以前の記事](https://qiita.com/ysgeso/items/42c2140766dd59906a02#%E4%BB%8A%E5%9B%9E%E3%81%AE%E8%A8%98%E4%BA%8B%E3%81%A7%E5%88%A9%E7%94%A8%E3%81%99%E3%82%8B%E8%A8%98%E5%8F%B7)と同様に α 値の初期値を $α$、β 値の初期値を $β$、置換表の範囲の下界を $l$、置換表の範囲の上界を $u$ と表記します。

* α 値の初期値を $α = max(α, l)$ とする
* β 値の初期値を $β = min(β, u)$ とする

バグの原因は[以前の記事](https://qiita.com/ysgeso/items/c43a1c7dc65b746c8394#ai_abs_tt2-%E3%81%AE%E4%BF%AE%E6%AD%A3)で __`ai_abs_tt2`__ を修正した際に、上記の処理を行う下記のプログラムの 10、11 行目で筆者がうっかり __`min` と `max` を間違えて記述__ してしまったことです。

```python
 1          if boardtxt in tt:
 2              lower_bound, upper_bound = tt[boardtxt]
 3              if lower_bound == upper_bound:
 4                  return lower_bound
 5              elif upper_bound <= alpha:
 6                  return upper_bound
 7              elif beta <= lower_bound:
 8                  return lower_bound
 9              else:
10                  alpha = min(alpha, lower_bound)
11                  beta = max(beta, upper_bound)
```

その後、[以前の記事](https://qiita.com/ysgeso/items/bcd2526d5327fd1e72e9#%E6%89%8B%E9%A0%86-4-%E3%81%AE%E5%AE%9F%E8%A3%85)で __`calc_score_for_anim`__ を __`ai_abs_tt2` を元に定義__ したためこのバグが `calc_score_for_anim` でも発生してしまいました。

:::note
このような、`min` と `max` を間違えるような __うっかりミス__ は __気を付けていても良く起きるバグの一つ__ です。同様のバグとして下記のようなバグは実際によく発生するので注意が必要です。

* < と > を間違える
* < と <= を間違える
* &gt; と >= を間違える
:::

## バグの修正

このバグは __`ai_abs_tt2`__、__`ai_abs_tt3`__、__`ai_abs_tt4`__、__`calc_score_for_anim`__ にあるのでそれぞれを修正することにします。

後で説明しますが、このバグによって __枝狩りの効率が悪く__ なりますが、__評価値は正しく計算される__ ので [修正前の AI は強解決の AI](https://qiita.com/ysgeso/items/c43a1c7dc65b746c8394#ai_abs_tt2-%E3%81%8C%E5%BC%B7%E8%A7%A3%E6%B1%BA%E3%81%AE-ai-%E3%81%A7%E3%81%82%E3%82%8B%E3%81%93%E3%81%A8%E3%81%AE%E7%A2%BA%E8%AA%8D) です。また、過去の記事に遡ってこのバグを修正してしまうと、今回の記事の意味がわからなくなるので過去の記事の内容はそのままにします。

### `ai_abs_tt2` の修正

__`ai_abs_tt2`__ の修正は以下の通りです。先程説明したように 18、19 行目の __`min` と `max` を入れ替えました__。

```python
 1  from ai import ai_by_score, dprint
 2  from marubatsu import Marubatsu
 3  from copy import deepcopy
 4  
 5  @ai_by_score
 6  def ai_abs_tt2(mb:Marubatsu, debug:bool=False, shortest_victory:bool=False):   
 7      count = 0
 8      def ab_search(mborig, tt, alpha=float("-inf"), beta=float("inf")):
元と同じなので省略
 9          if boardtxt in tt:
10              lower_bound, upper_bound = tt[boardtxt]
11              if lower_bound == upper_bound:
12                  return lower_bound
13              elif upper_bound <= alpha:
14                  return upper_bound
15              elif beta <= lower_bound:
16                  return lower_bound
17              else:
18                  alpha = max(alpha, lower_bound)
19                  beta = min(beta, upper_bound)
元と同じなので省略
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
from ai import ai_by_score, dprint
from marubatsu import Marubatsu
from copy import deepcopy

@ai_by_score
def ai_abs_tt2(mb, debug=False, shortest_victory=False):   
    count = 0
    def ab_search(mborig, tt, alpha=float("-inf"), beta=float("inf")):
        nonlocal count
        count += 1
        if mborig.status == Marubatsu.CIRCLE:
            return (11 - mborig.move_count) / 2 if shortest_victory else 1
        elif mborig.status == Marubatsu.CROSS:
            return (mborig.move_count - 10) / 2 if shortest_victory else -1
        elif mborig.status == Marubatsu.DRAW:
            return 0
        
        boardtxt = mborig.board_to_str()
        if boardtxt in tt:
            lower_bound, upper_bound = tt[boardtxt]
            if lower_bound == upper_bound:
                return lower_bound
            elif upper_bound <= alpha:
                return upper_bound
            elif beta <= lower_bound:
                return lower_bound
            else:
                alpha = max(alpha, lower_bound)
                beta = min(beta, upper_bound)
        
        alphaorig = alpha
        betaorig = beta

        legal_moves = mborig.calc_legal_moves()
        if mborig.turn == Marubatsu.CIRCLE:
            score = float("-inf")
            for x, y in legal_moves:
                mb = deepcopy(mborig)
                mb.move(x, y)
                score = max(score, ab_search(mb, tt, alpha, beta))
                if score >= beta:
                    break
                alpha = max(alpha, score)
        else:
            score = float("inf")
            for x, y in legal_moves:
                mb = deepcopy(mborig)
                mb.move(x, y)
                score = min(score, ab_search(mb, tt, alpha, beta))
                if score <= alpha:
                    break
                beta = min(beta, score)   
            
        from util import calc_same_boardtexts

        boardtxtlist = calc_same_boardtexts(mborig)
        if score <= alphaorig:
            lower_bound = float("-inf")
            upper_bound = score
        elif score < betaorig:
            lower_bound = score
            upper_bound = score
        else:
            lower_bound = score
            upper_bound = float("inf")
        for boardtxt in boardtxtlist:
            tt[boardtxt] = (lower_bound, upper_bound)
        return score
                
    score = ab_search(mb, {})
    dprint(debug, "count =", count)
    if mb.turn == Marubatsu.CIRCLE:
        score *= -1
    return score
```
</details>

<details><summary><strong>修正箇所</strong></summary>

```diff_python
from ai import ai_by_score, dprint
from marubatsu import Marubatsu
from copy import deepcopy

@ai_by_score
def ai_abs_tt2(mb, debug=False, shortest_victory=False):   
    count = 0
    def ab_search(mborig, tt, alpha=float("-inf"), beta=float("inf")):
元と同じなので省略
        if boardtxt in tt:
            lower_bound, upper_bound = tt[boardtxt]
            if lower_bound == upper_bound:
                return lower_bound
            elif upper_bound <= alpha:
                return upper_bound
            elif beta <= lower_bound:
                return lower_bound
            else:
-               alpha = min(alpha, lower_bound)
+               alpha = max(alpha, lower_bound)
-               beta = max(beta, upper_bound)
+               beta = min(beta, upper_bound)
元と同じなので省略
```
</details>

### `ai_abs_tt3` の修正

__`ai_abs_tt3`__ の修正は以下の通りです。修正箇所は先程と同様です。

```python
 1  @ai_by_score
 2  def ai_abs_tt3(mb, debug=False, shortest_victory=False):   
 3      count = 0
 4      def ab_search(mborig, tt, alpha=float("-inf"), beta=float("inf")):
元と同じなので省略
 5          if boardtxt in tt:
 6              lower_bound, upper_bound = tt[boardtxt]
 7              if lower_bound == upper_bound:
 8                  return lower_bound
 9              elif upper_bound <= alpha:
10                  return upper_bound
11              elif beta <= lower_bound:
12                  return lower_bound
13              else:
14                  alpha = max(alpha, lower_bound)
15                  beta = min(beta, upper_bound)
元と同じなので省略
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
@ai_by_score
def ai_abs_tt3(mb, debug=False, shortest_victory=False):       
    count = 0
    def ab_search(mborig, tt, alpha=float("-inf"), beta=float("inf")):
        nonlocal count
        count += 1
        if mborig.status == Marubatsu.CIRCLE:
            return (11 - mborig.move_count) / 2 if shortest_victory else 1
        elif mborig.status == Marubatsu.CROSS:
            return (mborig.move_count - 10) / 2 if shortest_victory else -1
        elif mborig.status == Marubatsu.DRAW:
            return 0
        
        boardtxt = mborig.board_to_str()
        if boardtxt in tt:
            lower_bound, upper_bound = tt[boardtxt]
            if lower_bound == upper_bound:
                return lower_bound
            elif upper_bound <= alpha:
                return upper_bound
            elif beta <= lower_bound:
                return lower_bound
            else:
                alpha = max(alpha, lower_bound)
                beta = min(beta, upper_bound)
        else:
            lower_bound = float("-inf")
            upper_bound = float("inf")
        
        alphaorig = alpha
        betaorig = beta

        legal_moves = mborig.calc_legal_moves()
        if mborig.turn == Marubatsu.CIRCLE:
            score = float("-inf")
            for x, y in legal_moves:
                mb = deepcopy(mborig)
                mb.move(x, y)
                score = max(score, ab_search(mb, tt, alpha, beta))
                if score >= beta:
                    break
                alpha = max(alpha, score)
        else:
            score = float("inf")
            for x, y in legal_moves:
                mb = deepcopy(mborig)
                mb.move(x, y)
                score = min(score, ab_search(mb, tt, alpha, beta))
                if score <= alpha:
                    break
                beta = min(beta, score)   
            
        from util import calc_same_boardtexts

        boardtxtlist = calc_same_boardtexts(mborig)
        if score <= alphaorig:
            upper_bound = score
        elif score < betaorig:
            lower_bound = score
            upper_bound = score
        else:
            lower_bound = score
        for boardtxt in boardtxtlist:
            tt[boardtxt] = (lower_bound, upper_bound)
        return score
                
    score = ab_search(mb, {})
    dprint(debug, "count =", count)
    if mb.turn == Marubatsu.CIRCLE:
        score *= -1
    return score
```
</details>

<details><summary><strong>修正箇所</strong></summary>

```diff_python
@ai_by_score
def ai_abs_tt3(mb, debug=False, shortest_victory=False):   
    count = 0
    def ab_search(mborig, tt, alpha=float("-inf"), beta=float("inf")):
元と同じなので省略
        if boardtxt in tt:
            lower_bound, upper_bound = tt[boardtxt]
            if lower_bound == upper_bound:
                return lower_bound
            elif upper_bound <= alpha:
                return upper_bound
            elif beta <= lower_bound:
                return lower_bound
            else:
-               alpha = min(alpha, lower_bound)
+               alpha = max(alpha, lower_bound)
-               beta = max(beta, upper_bound)
+               beta = min(beta, upper_bound)
元と同じなので省略
```
</details>

### `ai_abs_tt4` の修正

__`ai_abs_tt4`__ を __修正する前と後__ で、ゲーム開始時の局面[^1]の評価値を計算した際に __計算されるノードの数がどのように変わるかを確認するため__ に、下記のプログラムを実行します。実行結果から修正前の __バグのある `ai_abs_tt4`__ では計算されるノードの数が、最短の勝利を目指さない場合は 832、目指す場合は 1343 であることが確認できました。

```python
from ai import ai_abs_tt4
from marubatsu import Marubatsu

mb = Marubatsu()
ai_abs_tt4(mb, calc_score=True, shortest_victory=False, debug=True)
ai_abs_tt4(mb, calc_score=True, shortest_victory=True, debug=True)
```

実行結果

```
count = 832
count = 1343
```

__`ai_abs_tt4`__ の修正は以下の通りです。修正箇所は先程と同様です。

```python
 1  @ai_by_score
 2  def ai_abs_tt4(mb, debug=False, shortest_victory=False):   
 3      count = 0
 4      def ab_search(mborig, tt, alpha=float("-inf"), beta=float("inf")):
元と同じなので省略
 5          if boardtxt in tt:
 6              lower_bound, upper_bound = tt[boardtxt]
 7              if lower_bound == upper_bound:
 8                  return lower_bound
 9              elif upper_bound <= alpha:
10                  return upper_bound
11              elif beta <= lower_bound:
12                  return lower_bound
13              else:
14                  alpha = max(alpha, lower_bound)
15                  beta = min(beta, upper_bound)
元と同じなので省略
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
@ai_by_score
def ai_abs_tt4(mb, debug, shortest_victory=False):   
    count = 0
    def ab_search(mborig, tt, alpha=float("-inf"), beta=float("inf")):
        nonlocal count
        count += 1
        if mborig.status == Marubatsu.CIRCLE:
            return (11 - mborig.move_count) / 2 if shortest_victory else 1
        elif mborig.status == Marubatsu.CROSS:
            return (mborig.move_count - 10) / 2 if shortest_victory else -1
        elif mborig.status == Marubatsu.DRAW:
            return 0
        
        boardtxt = mborig.board_to_str()
        if boardtxt in tt:
            lower_bound, upper_bound = tt[boardtxt]
            if lower_bound == upper_bound:
                return lower_bound
            elif upper_bound <= alpha:
                return upper_bound
            elif beta <= lower_bound:
                return lower_bound
            else:
                alpha = max(alpha, lower_bound)
                beta = min(beta, upper_bound)
        else:
            lower_bound = min_score
            upper_bound = max_score
        
        alphaorig = alpha
        betaorig = beta

        legal_moves = mborig.calc_legal_moves()
        if mborig.turn == Marubatsu.CIRCLE:
            score = float("-inf")
            for x, y in legal_moves:
                mb = deepcopy(mborig)
                mb.move(x, y)
                score = max(score, ab_search(mb, tt, alpha, beta))
                if score >= beta:
                    break
                alpha = max(alpha, score)
        else:
            score = float("inf")
            for x, y in legal_moves:
                mb = deepcopy(mborig)
                mb.move(x, y)
                score = min(score, ab_search(mb, tt, alpha, beta))
                if score <= alpha:
                    break
                beta = min(beta, score)   
            
        from util import calc_same_boardtexts

        boardtxtlist = calc_same_boardtexts(mborig)
        if score <= alphaorig:
            upper_bound = score
        elif score < betaorig:
            lower_bound = score
            upper_bound = score
        else:
            lower_bound = score
        for boardtxt in boardtxtlist:
            tt[boardtxt] = (lower_bound, upper_bound)
        return score
                
    min_score = -2 if shortest_victory else -1
    max_score = 3 if shortest_victory else 1

    score = ab_search(mb, {}, alpha=min_score, beta=max_score)
    dprint(debug, "count =", count)
    if mb.turn == Marubatsu.CIRCLE:
        score *= -1
    return score
```
</details>

<details><summary><strong>修正箇所</strong></summary>

```diff_python
@ai_by_score
def ai_abs_tt4(mb, debug=False, shortest_victory=False):   
    count = 0
    def ab_search(mborig, tt, alpha=float("-inf"), beta=float("inf")):
元と同じなので省略
        if boardtxt in tt:
            lower_bound, upper_bound = tt[boardtxt]
            if lower_bound == upper_bound:
                return lower_bound
            elif upper_bound <= alpha:
                return upper_bound
            elif beta <= lower_bound:
                return lower_bound
            else:
-               alpha = min(alpha, lower_bound)
+               alpha = max(alpha, lower_bound)
-               beta = max(beta, upper_bound)
+               beta = min(beta, upper_bound)
元と同じなので省略
```
</details>

### 修正したプログラムの確認

上記の修正後に下記のプログラムを実行することで、修正した `ai_abs_tt2`、`ai_abs_tt3`、`ai_abs_tt4` が __強解決の AI であることが確認__ できます。

```python
from util import Check_solved

Check_solved.is_strongly_solved(ai_abs_tt2)
Check_solved.is_strongly_solved(ai_abs_tt3)
Check_solved.is_strongly_solved(ai_abs_tt4)
```

実行結果

```
100%|██████████| 431/431 [00:03<00:00, 143.54it/s]
431/431 100.00%
100%|██████████| 431/431 [00:02<00:00, 143.91it/s]
431/431 100.00%
100%|██████████| 431/431 [00:02<00:00, 194.00it/s]
431/431 100.00%
```

なお、このバグは __α 値と β 値の初期値の範囲__ が、__意図していた範囲よりも広くなってしまう__ というバグです。このバグによって __枝狩りの効率が悪くなります__[^2]が、__ルートノードの α 値と β 値の初期値__ はこのバグによって __変化しないため__、__ルートノードの評価値は正しく計算されます__。従ってこのバグがあっても `ai_abs_tt2`、`ai_abs_tt3`、`ai_abs_tt4` が強解決の AI であることに変わりはありません。

次に、__修正後の `ai_abs_tt4`__ でゲーム開始時の局面の評価値を計算した際に __計算されるノードの数__ を下記のプログラムで表示します。

```python
ai_abs_tt4(mb, calc_score=True, shortest_victory=False, debug=True)
ai_abs_tt4(mb, calc_score=True, shortest_victory=True, debug=True)
```

実行結果

```
count = 832
count = 1242
```

下記は修正前と修正後の結果をまとめた表です。

||修正前|修正後|
|:--|--:|--:|
|__最短の勝利を目指さない場合__| 832 | 832 |
|__最短の勝利を目指す場合__| 1343 | 1242 |

最短の勝利を目指さない場合は変化はありませんでしたが、__最短の勝利を目指す場合__ は修正前の 1343 から 1242 に減っていることから __バグによって枝狩りの効率が悪くなっていた__ ことが確認できました。

__最短の勝利を目指さない__ 場合に __変化がない__ のは、下記のような原因だと思います。

* 最短の勝利を目指さない場合は __評価値の範囲__ が -1 ~ 1 と __狭い__
* また、評価値は -1、0、1 の __3 種類の整数しかない__
* そのため、α 値と β 値の初期値の範囲は -1 ~ 1、-1 ~ 0、0 ~ 1 の __3 種類しかない__
* そのため、このバグによって α 値と β 値の初期値の範囲が __正しく計算されないという状況がほとんど発生しない__
* また、そのような状況が発生した場合でも、__α 値と β 値の初期値の範囲が狭い__ ため __枝狩りの数が変化するような場合が存在しない__

一方、__最短の勝利を目指す場合__ は __評価値の範囲__ が -2 ~ 3 と __広い__ ので、α 値と β 値の初期値の範囲が正しく計算されないという __状況が多く__、その中で実際に枝狩りの効率が悪くなる状況が実際に存在します。

このように __バグが存在しても__、プログラムが扱うデータが __バグが発生する条件を満たさない__ 場合は __正常な計算が行われることが良くあります__。プログラムが正常に動作しない場合は必ずバグが存在しますが、__プログラムが正常に動作したからと言ってバグが存在しないとは限らない__ ということを常に忘れないようにする必要があります。

### `calc_score_for_anim` の修正

__`calc_score_for_anim`__ の修正は以下の通りです。バグの修正箇所は先程と同様です。

また、このバグとは関係ないのですが、ノードの評価値が確定した __"end" のフレーム__ では、下記のプログラムの 39 行目の後で __下界と上界が等しい場合は評価値の種類を表す文字列を "exact value" とする__ 処理を行っていましたが、よく考えると __それは評価値の種類の意味を考えるとおかしい__ 気がしましたので __その処理は削除する__ ことにしました。この修正はこの後で実装する __"end" の状態の表示に影響__ します。

* __19 行目__：α 値または β 値が更新されるかどうかの判定式を修正した
* __20、21 行目__：`max` と `min` が入れ替わっていた点を修正した
* 39 行目の下にあった、評価値が確定した際に、下界と上界が等しい場合に評価値の種類を "exact value" とする処理を削除した

```python
 1  from tree import Mbtree
 2
 3  def calc_score_for_anim(self, abroot, debug=False, minimax=False, init_ab=False, use_tt=False, shortest_victory=None):
元と同じなので省略
 4              if use_tt:
 5                  boardtxt = node.mb.board_to_str()
 6                  if boardtxt in tt:
 7                      registered_in_tt = True
 8                      lower_bound, upper_bound = tt[boardtxt]
 9                      if lower_bound == upper_bound:
10                          node.score = lower_bound
11                          tt_pruned = True
12                      elif upper_bound <= alphaorig:
13                          node.score = upper_bound
14                          tt_pruned = True
15                      elif betaorig <= lower_bound:
16                          node.score = lower_bound
17                          tt_pruned = True
18                      else:
19                          ab_updated = alphaorig < lower_bound or betaorig > upper_bound
20                          alphaorig = max(alphaorig, lower_bound)
21                          betaorig = min(betaorig, upper_bound)
元と同じなので省略
22          if node.score <= alphaorig:
23              score_type = "fail low"
24              clower_bound = min_score
25              cupper_bound = node.score
26              tlower_bound = lower_bound
27              tupper_bound = node.score
28          elif node.score < betaorig:
29              score_type = "exact value"
30              clower_bound = node.score
31              cupper_bound = node.score
32              tlower_bound = node.score
33              tupper_bound = node.score
34          else:
35              score_type = "fail high"
36              clower_bound = node.score
37              cupper_bound = max_score
38              tlower_bound = node.score
39              tupper_bound = upper_bound
元と同じなので省略
40  
41  Mbtree.calc_score_for_anim = calc_score_for_anim
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
from tree import Mbtree

def calc_score_for_anim(self, abroot, debug=False, minimax=False, init_ab=False, use_tt=False, shortest_victory=None):
    def assign_pruned_index(node, index):
        node.pruned_index = index
        self.num_pruned += 1
        for childnode in node.children:
            assign_pruned_index(childnode, index)
            
    def calc_node_score(node, tt, alphaorig=float("-inf"), betaorig=float("inf")):
        if minimax:
            alphaorig = float("-inf")
            betaorig = float("inf")            

        self.nodelist_by_score.append(node)
        self.ablist_by_score.append({
            "status": "start",
            "alphaorig": alphaorig,
            "betaorig": betaorig,
            "num_calculated": self.num_calculated,
            "num_pruned": self.num_pruned,
        })
        
        registered_in_tt = False
        tt_pruned = False
        if node.mb.status != Marubatsu.PLAYING:        
            self.calc_score_of_node(node)
            lower_bound = node.score
            upper_bound = node.score
        else:
            ab_updated = False
            if use_tt:
                boardtxt = node.mb.board_to_str()
                if boardtxt in tt:
                    registered_in_tt = True
                    lower_bound, upper_bound = tt[boardtxt]
                    if lower_bound == upper_bound:
                        node.score = lower_bound
                        tt_pruned = True
                    elif upper_bound <= alphaorig:
                        node.score = upper_bound
                        tt_pruned = True
                    elif betaorig <= lower_bound:
                        node.score = lower_bound
                        tt_pruned = True
                    else:
                        ab_updated = alphaorig < lower_bound or betaorig > upper_bound
                        alphaorig = max(alphaorig, lower_bound)
                        betaorig = min(betaorig, upper_bound)
                else:
                    lower_bound = min_score
                    upper_bound = max_score
                self.nodelist_by_score.append(node)
                if tt_pruned:
                    for childnode in node.children:
                        assign_pruned_index(childnode, len(self.nodelist_by_score) - 1)  
                self.ablist_by_score.append({
                    "status": "tt",
                    "alphaorig": alphaorig,
                    "betaorig": betaorig,
                    "tt_pruned": tt_pruned,
                    "ab_updated": ab_updated,
                    "registered_in_tt": registered_in_tt,
                    "lower_bound": lower_bound,
                    "upper_bound": upper_bound,
                    "num_calculated": self.num_calculated,
                    "num_pruned": self.num_pruned,
                })
            else:
                lower_bound = min_score
                upper_bound = max_score
        
            if not tt_pruned:
                alpha = alphaorig
                beta = betaorig
                ab_pruned = False
                maxnode = node.mb.turn == Marubatsu.CIRCLE
                node.score = min_score if maxnode else max_score
                for childnode in node.children:
                    childscore = calc_node_score(childnode, tt, alpha, beta)
                    self.nodelist_by_score.append(node)
                    self.ablist_by_score.append({
                        "status": "score",
                        "alphaorig": alphaorig,
                        "betaorig": betaorig,
                        "score": node.score,
                        "childscore": childscore,
                        "registered_in_tt": registered_in_tt,
                        "lower_bound": lower_bound,
                        "upper_bound": upper_bound,
                        "num_calculated": self.num_calculated,
                        "num_pruned": self.num_pruned,
                    })   
                    if maxnode:
                        updated = node.score < childscore
                        node.score = max(node.score, childscore)
                        alpha = max(node.score, alpha)
                        if node.score >= beta:
                            ab_pruned = True
                    else:
                        updated = node.score > childscore
                        node.score = min(node.score, childscore)
                        beta = min(node.score, beta)
                        if node.score <= alpha:
                            ab_pruned = True
                    self.nodelist_by_score.append(node)
                    if ab_pruned:
                        index = node.children.index(childnode)
                        for prunednode in node.children[index + 1:]:
                            assign_pruned_index(prunednode, len(self.nodelist_by_score) - 1)
                    self.ablist_by_score.append({
                        "status": "update",
                        "alphaorig": alphaorig,
                        "betaorig": betaorig,
                        "score": node.score,
                        "registered_in_tt": registered_in_tt,
                        "updated": updated,
                        "ab_pruned": ab_pruned,
                        "lower_bound": lower_bound,
                        "upper_bound": upper_bound,
                        "num_calculated": self.num_calculated,
                        "num_pruned": self.num_pruned,
                    })   
                    if ab_pruned:
                        break 
                    
        if node.score <= alphaorig:
            score_type = "fail low"
            clower_bound = min_score
            cupper_bound = node.score
            tlower_bound = lower_bound
            tupper_bound = node.score
        elif node.score < betaorig:
            score_type = "exact value"
            clower_bound = node.score
            cupper_bound = node.score
            tlower_bound = node.score
            tupper_bound = node.score
        else:
            score_type = "fail high"
            clower_bound = node.score
            cupper_bound = max_score
            tlower_bound = node.score
            tupper_bound = upper_bound

        if use_tt:
            from util import calc_same_boardtexts
            boardtxtlist = calc_same_boardtexts(node.mb)
            for boardtxt in boardtxtlist:
                tt[boardtxt] = (tlower_bound, tupper_bound) 

        self.nodelist_by_score.append(node)
        self.num_calculated += 1     
        self.ablist_by_score.append({
            "status": "end",
            "alphaorig": alphaorig,
            "betaorig": betaorig,
            "score": node.score,
            "registered_in_tt": registered_in_tt,
            "tt_pruned": tt_pruned,
            "score_type": score_type,
            "lower_bound": lower_bound,
            "upper_bound": upper_bound,
            "clower_bound": clower_bound,
            "cupper_bound": cupper_bound,
            "tlower_bound": tlower_bound,
            "tupper_bound": tupper_bound,
            "num_calculated": self.num_calculated,
            "num_pruned": self.num_pruned,
        })   
        node.score_index = len(self.nodelist_by_score) - 1          
        return node.score   
                                    
    self.calculated_by_calc_score_for_anim = True
    self.minimax = minimax
    self.init_ab = init_ab
    self.use_tt = use_tt
    if shortest_victory is not None:
        self.shortest_victory = shortest_victory

    from ai import dprint       
    for node in self.nodelist:
        node.score_index = float("inf")
        node.pruned_index = float("inf")
    self.nodelist_by_score = []
    self.ablist_by_score = []
    self.num_calculated = 0
    self.num_pruned = 0
    if init_ab:
        min_score = -2 if self.shortest_victory else -1
        max_score = 3 if self.shortest_victory else 1
    else:
        min_score = float("-inf")
        max_score = float("inf")
    calc_node_score(abroot, {}, min_score, max_score)
    total_nodenum = self.num_pruned + self.num_calculated
    ratio = self.num_calculated / total_nodenum * 100
    dprint(debug, "ミニマックス法で計算したか", minimax)    
    dprint(debug, "計算したノードの数",  self.num_calculated)
    dprint(debug, "枝狩りしたノードの数",  self.num_pruned)
    dprint(debug, "合計",  total_nodenum)
    dprint(debug, f"割合 {ratio:.1f}%") 
    
Mbtree.calc_score_for_anim = calc_score_for_anim
```
</details>

<details><summary><strong>修正箇所</strong></summary>

```diff_python
from tree import Mbtree

def calc_score_for_anim(self, abroot, debug=False, minimax=False, init_ab=False, use_tt=False, shortest_victory=None):
元と同じなので省略
            if use_tt:
                boardtxt = node.mb.board_to_str()
                if boardtxt in tt:
                    registered_in_tt = True
                    lower_bound, upper_bound = tt[boardtxt]
                    if lower_bound == upper_bound:
                        node.score = lower_bound
                        tt_pruned = True
                    elif upper_bound <= alphaorig:
                        node.score = upper_bound
                        tt_pruned = True
                    elif betaorig <= lower_bound:
                        node.score = lower_bound
                        tt_pruned = True
                    else:
-                       ab_updated = alphaorig > lower_bound or betaorig < upper_bound
+                       ab_updated = alphaorig < lower_bound or betaorig > upper_bound
-                       alphaorig = min(alphaorig, lower_bound)
+                       alphaorig = max(alphaorig, lower_bound)
-                       betaorig = max(betaorig, upper_bound)
+                       betaorig = min(betaorig, upper_bound)
元と同じなので省略
        if node.score <= alphaorig:
            score_type = "fail low"
            clower_bound = min_score
            cupper_bound = node.score
            tlower_bound = lower_bound
            tupper_bound = node.score
        elif node.score < betaorig:
            score_type = "exact value"
            clower_bound = node.score
            cupper_bound = node.score
            tlower_bound = node.score
            tupper_bound = node.score
        else:
            score_type = "fail high"
            clower_bound = node.score
            cupper_bound = max_score
            tlower_bound = node.score
            tupper_bound = upper_bound
-       if tlower_bound == tupper_bound:
-           score_type = "exact value"
元と同じなので省略

Mbtree.calc_score_for_anim = calc_score_for_anim
```
</details>

上記の修正後に下記のプログラムを実行し、[前回の記事](https://qiita.com/ysgeso/items/b10991d23a8c084c2bba#%E6%9D%A1%E4%BB%B6-5-%E3%81%AE%E8%A1%A8%E7%A4%BA%E3%81%AE%E5%95%8F%E9%A1%8C%E7%82%B9)で α 値と β 値の初期値の範囲が __間違っていた 67 フレーム目__ を表示することで、正しく修正されていることを確認します。

```python
from tree import Mbtree_Anim

mbtree = Mbtree.load("../data/abtree_root")
mbtree.calc_score_for_anim(mbtree.root, minimax=False, use_tt=True, shortest_victory=True, init_ab=True)
Mbtree_Anim(mbtree, isscore=True)
```

下図は __67 フレーム目の図__ で、その下の図は前回の記事の間違っていた 67 フレーム目の図です。上図では下図と異なり黄色の __α 値と β 値の初期値の範囲__ が赤い矢印の置換表の範囲の外を含まないように __正しく更新されている__ ことが確認できます。


修正後の 67 フレーム目の図

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/52ef40fe-58b6-4845-8993-d8f1969bcbe7.png" width="500">

修正前の 67 フレーム目の図

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/84847c67-638f-4a27-9b49-800735edab60.png" width="500">

バグの修正前と修正後が行う __処理の違いは__ 、下図の __1 つ前の "start" の状態の 66 フレーム目の図__ を見ることで確認することができます。__修正前と修正後__ の 67 フレーム目の __α 値と β 値の初期値の範囲__ が以下のように計算されていることを確認して下さい。

* 修正前は「66 フレーム目の α 値と β 値の初期値の範囲」と「67 フレーム目の赤い矢印の置換表の範囲」の __両方を含む（OR の）範囲__ で、範囲が __広くなる__
* 修正後は「66 フレーム目の α 値と β 値の初期値の範囲」と「67 フレーム目の赤い矢印の置換表の範囲」の __両方で共通する（AND の）範囲__ で、範囲が __狭くなる__

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/899f8f5c-99b9-4a99-af7e-b6ac1b84cf73.png" width="500">

# 残りの αβ 法の視覚化のための Mbtree_Anim の修正

バグが修正できたので、残りの αβ 法の視覚化のための Mbtree_Anim の修正を行ないます。

残っているのは下記の __"score"__、__"update"__、__"end"__ のフレームの表示です。なお、ミニマックス法と共通する内容には先頭に（※）を記述しました。全体の視覚化の検討結果については[前回の記事](https://qiita.com/ysgeso/items/b10991d23a8c084c2bba#%E8%A6%96%E8%A6%9A%E5%8C%96%E3%81%AE%E6%A4%9C%E8%A8%8E%E3%81%AE%E3%81%8A%E3%81%95%E3%82%89%E3%81%84)を復習して下さい。

* __"score"__ の状態
    * （※）数直線上にそのフレームでのノードの評価値を表示
    * （※）数直線上に子ノードの評価値を表示
    * そのフレームでの α 値と β 値
* __"update"__ の状態
    * （※）数直線上にそのフレームでのノードの評価値を表示
    * ノードの評価値が更新された場合
        * （※）赤字で「評価値の更新」を表示
    * 更新されていない場合  
        * （※）黒字で「評価値の更新なし」を表示
    * そのフレームでの α 値と β 値
    * α 狩りが行われた場合は赤字で「α 狩り」と表示
    * β 狩りが行われた場合は赤字で「β 狩り」と表示
* __"end"__ の状態
    * （※）数直線上にそのフレームでのノードの評価値を表示
    * このノードの評価値の種類によってフレームの状態を以下のように表示する
        * fail low の場合は「評価値の確定（fail low）」を表示
        * exact value の場合は「評価値の確定（exact value）」を表示
        * fail high の場合は「評価値の確定（fail high）」を表示
    * 置換表を利用する場合
        * 置換表による枝狩りが行われた場合
            * （※）黒字で「置換表に登録されていたデータを利用」を表示
        * 置換表による枝狩りが行われていない場合
            * （※）赤字で「置換表への登録」を表示
            * 計算された範囲
            * 登録する範囲

## "score" の状態で表示する内容の実装

"score" の状態で新しく表示する内容は「__そのフレームでの α 値と β 値__」です。どのように実装すればよいかについて少し考えてみて下さい。

そのフレームでの α 値と β 値は、__子ノードの評価値を計算する際に受け継がれる値__ ですが、__現在のフレームの評価値を計算__ する際には __利用されない値__ です。また、"score" のフレームでは数直線上にそのノードの評価値と子ノードの評価値が表示されているので、そのフレームでの α 値と β 値を __表示するとわかりづらくなるという問題が発生__ します。そこで、__"score" の状態__ ではそのフレームでの α 値と β 値を __表示しない__ ことにします。従って `update_frameinfo` を修正する必要はありません。

下図はゲーム開始時の局面の最初の子ノードの評価値が計算された "score" のフレームの図で、そのフレームでの α 値と β 値は表示されていませんが、それらの値はこのノードの評価値を計算する際に利用されないので表示しなくても大きな問題はありません。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/1869906e-bb78-4066-a4f7-f926e6bb594e.png" width="500">

## "update" の状態で表示する内容の実装

下記は __"update"__ の状態で表示する内容の再掲です。（※）の部分は実装済なので、それ以外の場合の表示を実装する必要があります。

* （※）数直線上にそのフレームでのノードの評価値を表示
* ノードの評価値が更新された場合
    * （※）赤字で「評価値の更新」を表示
* 更新されていない場合  
    * （※）黒字で「評価値の更新なし」を表示
* そのフレームでの α 値と β 値
* α 狩りが行われた場合は赤字で「α 狩り」と表示
* β 狩りが行われた場合は赤字で「β 狩り」と表示

また、上記とは別に __以下のような表示の工夫__ を行なうことにします。どのように実装すればよいかについて少し考えてみて下さい。

* 真ん中の 3 行目のメッセージを「評価値の更新（fail low）」のように、末尾にこのフレームでのノードの評価値の範囲を表示する
* 右下に表示される範囲の説明を下記のように表示する。
    * max ノードの場合は fail high の範囲を「fail high（β 狩り）」と表示する
    * min ノードの場合は fail low の範囲を「fail low（α 狩り）」と表示する
    * このフレームでのノードの評価値の範囲を赤字で表示する

"update" のフレームで __α 狩りまたは β 狩りが行なわれる場合__ は、残りの子ノードの評価値の計算は省略されるので、__そのフレームでの α 値と β 値が利用されることはありません__。従ってその場合はそのフレームでの α 値と β 値を __表示しないことにします__。

α 狩りまたは β 狩りが __行なわれない場合__ は、__次のフレーム__ では __次の子ノードの評価値の計算を開始__ する "start" のフレームになり、その際に __そのフレームの α 値と β 値__ の値が __そのまま子ノードの α 値と β 値の初期値として引き継がれます__。従って、__"update"__ のフレームにはそのフレームでの α 値と β 値を __表示したほうが良い__ でしょう。

具体的にはそのフレームでの α 値と β 値を下記のように表示することにします。

* α 値を数直線上に赤い丸で表示する
* β 値を数直線上に青い丸で表示する
* max ノードでは数直線の右上に「α 値」と「β 値」を注釈として表示し、そこから数直線上の α 値と β 値に矢印を表示する
* min ノードでは数直線の左上に「α 値」と「β 値」を注釈として表示し、そこから数直線上の α 値と β 値に矢印を表示する

なお、__α 値と β 値の注釈を上記のように表示する理由__ は以下の通りです。

* そのフレームの α 値と β 値の具体的な値を注釈に表示するスペースが無い
* max ノードではそのフレームでの α 値と β 値は以下の値になる
    * α 値はそのフレームでの評価値と α 値の初期値の最大値
    * β 値は β 値の初期値
* min ノードではそのフレームでの α 値と β 値は以下の値になる
    * α 値は α 値の初期値
    * β 値はそのフレームでの評価値と β 値の初期値の最小値
* "update" のフレームでは、α 値と β 値の初期値とそのフレームでの評価値は __すべて数直線上の注釈で値が表示されている__ ので、__そのフレームでの α 値と β 値の値__ は注釈の __どこかに必ず表示されている__
* max ノードではそのフレームの α 値は、そのフレームでの評価値以上になるので、__数直線上__ では必ずそのフレームでの評価値と __同じかそれより右に配__ 置される。従って、そのフレームの α 値の注釈をそのフレームの評価値の右に表示することで、その 2 つの注釈から数直線上に伸びる矢印が __交差してみづらくなることが無い__
* min ノードでも同様の理由で注釈から伸びる矢印が交差しない

わかりづらいと思いますので、max ノードでの __"update" のフレームの数直線の表示例__ を下図に示します。__下記の式が常に成り立つ__ ので、下図の注釈から数直線に伸びる __3 本の矢印が交差してみづらくなることはありません__。

そのフレームの評価値 ≦ そのフレームの α 値 < そのフレームの β 値

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/8cf06456-1b9d-468c-b543-573a929202f7.png" width="300">

α 狩りや β 狩りが行なわれた場合に表示する「α 狩り」や「β 狩り」は真ん中の 4 行目に表示することにします。

### `update_frameinfo` の修正

下記は上記を表示するように `update_frameinfo` を修正したプログラムです。

* __11 ~ 35 行目__：αβ 法の場合で "update" のフレームの処理を記述する
* __12 ~ 18 行目__：このフレームでのノードの評価値の範囲を表す文字列を `score_type` に代入し、18 行目で真ん中の 4 行目のメッセージの末尾にその文字列を追加する
* __19 ~ 21 行目__：α 狩りまたは β 狩りが行なわれる場合は、真ん中の 5 行目で赤字で「α 狩り」または「β 狩り」を表示するようにする
* __22 ~ 35 行目__：α 狩りまたは β 狩りが行われない場合の処理を記述する
* __23 ~ 26 行目__：そのフレームの α 値と β 値と、それらの数直線上の座標を計算する
* __27 行目__：α 値の注釈を表示する x 座標を以下のように計算する
    * max ノードの場合は左端に左揃えで表示する必要があるので、左端の座標を計算する
    * min ノードの場合は右端に右揃えで表示する必要があるので右端の座標を元に計算する。その際に β 値の注釈の左に表示するように右端から 1.5 を引いた座標を計算する
* __28 行目__：β 値の注釈を表示する x 座標を上記と同様の方法で計算する
* __29 行目__：α 値と β 値の注釈を表示する際の文字の揃えを計算する
* __30 ~ 35 行目__：α 値と β 値の数直線上の丸と注釈を描画する
* __39 ~ 49 行目__："update" のフレームの場合の範囲の説明の表示の処理を記述する
* __40 ~ 43 行目__：範囲の説明に max ノードの場合は "fail high (β 狩り)" を、min ノードの場合は "fail low (α 狩り)" を表示するようにする
* __44 ~ 49 行目__：12 ~ 17 行目で計算した `score_type` の値を利用して、そのノードの評価値が含まれる範囲を赤字で表示するようにする

```python
 1  import matplotlib.patches as patches
 2
 3  def update_frameinfo(self):
元と同じなので省略
 4      # ノードの評価値が更新されたかどうかの表示
 5      elif status == "update":
 6          if framedata["updated"]:
 7              textlist[3] = "評価値の更新"
 8              textcolorlist[3] = "red"
 9          else:
10              textlist[3] = "評価値の更新なし"
11          if not minimax:
12              if score <= alphaorig:
13                  score_type = "fail low"
14              elif score < betaorig:
15                  score_type = "exact value"
16              else:
17                  score_type = "fail high"
18              textlist[3] += f" ({score_type})"
19              if framedata["ab_pruned"]:
20                  textlist[4] = "β 狩り" if maxnode else "α 狩り"
21                  textcolorlist[4] = "red"
22              else:
23                  alpha = max(alphaorig, score) if maxnode else alphaorig
24                  beta = betaorig if maxnode else min(betaorig, score)
25                  alpha_coord = calc_coord(alpha)
26                  beta_coord = calc_coord(beta)
27                  alpha_text_coord = rightx - 1.5 if maxnode else leftx
28                  beta_text_coord = rightx if maxnode else leftx + 1.5
29                  ha = "right" if maxnode else "left"
30                  self.abax.plot(alpha_coord, 0, "or")
31                  self.abax.plot(beta_coord, 0, "ob")
32                  self.abax.annotate(f"α 値", xy=(alpha_coord, 0),
33                            xytext=(alpha_text_coord, 1), arrowprops=arrowprops, ha=ha)
34                  self.abax.annotate(f"β 値", xy=(beta_coord, 0),
35                            xytext=(beta_text_coord, 1), arrowprops=arrowprops, ha=ha)
元と同じなので省略
36      # 範囲の説明の表示
37      if not minimax:
元と同じなので省略
38          if status == "tt" and framedata["tt_pruned"]:
元と同じなので省略
39          elif status == "update":
40              if maxnode:
41                  textlist[2] = "fail high (β 狩り)"
42              else:
43                  textlist[0] = "fail low (α 狩り)"
44              if score_type == "fail low":
45                  textcolorlist[0] = "red"
46              elif score_type == "exact value":
47                  textcolorlist[1] = "red"
48              elif score_type == "fail high":
49                  textcolorlist[2] = "red"            
50          for i in range(3):
51              rect = patches.Rectangle(xy=(15, linetop - 0.1 - (i + 5) * 0.7), 
52                                       width=0.8, height=0.5, fc=facecolorlist[i], ec="k")
53              self.abax.add_patch(rect)
54              self.abax.text(16.2, linetop - (i + 5) * 0.7, textlist[i], c=textcolorlist[i])  
元と同じなので省略
55           
56  Mbtree_Anim.update_frameinfo = update_frameinfo
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
import matplotlib.patches as patches

def update_frameinfo(self):
    def calc_coord(score):
        return min(max(minus_inf, score), plus_inf)
        
    framedata = self.mbtree.ablist_by_score[self.play.value]
    status = framedata["status"]
    maxnode = self.selectednode.mb.turn == Marubatsu.CIRCLE
    minimax = self.mbtree.minimax
    
    self.abax.clear()
    self.abax.set_xlim(-4, 23)
    if minimax:
        self.abax.set_ylim(-1.5, 1.5)
    else:
        self.abax.set_ylim(-4.3, 2.3)
    self.abax.axis("off")

    minus_inf = -3 if self.mbtree.shortest_victory else -2
    plus_inf = 4 if self.mbtree.shortest_victory else 2

    # 範囲の色分け
    if not minimax:
        alphaorig = framedata["alphaorig"]
        betaorig = framedata["betaorig"]
        alphaorig_coord = calc_coord(alphaorig)
        betaorig_coord = calc_coord(betaorig)
        color = "lightgray" if maxnode else "aqua"
        rect = patches.Rectangle(xy=(minus_inf, -0.5), width=alphaorig_coord-minus_inf,
                                height=1, fc=color)
        self.abax.add_patch(rect)
        rect = patches.Rectangle(xy=(alphaorig_coord, -0.5), width=betaorig_coord-alphaorig_coord,
                                height=1, fc="yellow")
        self.abax.add_patch(rect)
        color = "aqua" if maxnode else "lightgray"
        rect = patches.Rectangle(xy=(betaorig_coord, -0.5), width=plus_inf-betaorig_coord,
                                height=1, fc=color)
        self.abax.add_patch(rect)
    # 数直線の描画    
    self.abax.plot(range(minus_inf, plus_inf + 1), [0] * (plus_inf + 1 - minus_inf) , "|-k")
    for num in range(minus_inf, plus_inf + 1):
        if num == minus_inf:
            numtext = "" if self.mbtree.init_ab else "-∞"
        elif num == plus_inf:
            numtext = "" if self.mbtree.init_ab else "∞"
        else:
            numtext = num
        self.abax.text(num, -1, numtext, ha="center")        

    # メッセージの表示
    if minimax:
        linenum = 4
        linetop = 1
    else:
        linenum = 9
        linetop = 1.7
    textlist = [""] * linenum
    textcolorlist = ["black"] * linenum

    algorithm = "mm 法" if self.mbtree.minimax else "αβ法"
    use_tt = "〇" if self.mbtree.use_tt else "×"
    shortest_victory = "〇" if self.mbtree.shortest_victory else "×"
    init_ab = "〇" if self.mbtree.init_ab else "×"
    textlist[0] = f"{algorithm}　置換表 {use_tt}　最短 {shortest_victory}"
    if not self.mbtree.minimax:
        textlist[0] += f"　初期値 {init_ab}"
    
    textlist[1] = f"深さ {self.selectednode.mb.move_count} "
    if maxnode:
        textlist[1] += "max node"
    else:
        textlist[1] += "min node"
    
    statusdata = {
        "start": {
            "text": "処理の開始",
            "color": "white"
        },
        "tt": {
            "text": "置換表の処理",
            "color": "honeydew"
        },
        "score": {
            "text": "子ノードの評価値",
            "color": "lightyellow"
        },
        "update": {
            "text": "更新処理",
            "color": "lightcyan"
        },
        "end": {
            "text": "評価値の確定",
            "color": "lavenderblush"
        },
    }
    textlist[2] = statusdata[status]["text"]
    facecolor = statusdata[status]["color"]
    
    arrowprops = { "arrowstyle": "->"}
    leftx = -3
    rightx = 4
    centerx = (leftx + rightx) / 2
    # α 値 と β 値の初期値の表示
    if not minimax:
        if status == "start" or status == "tt":
            self.abax.plot(alphaorig_coord, 0, "or")
            self.abax.annotate(f"α = {alphaorig}", xy=(alphaorig_coord, 0),
                           xytext=(leftx, 1.7), arrowprops=arrowprops, ha="left")        
            self.abax.plot(betaorig_coord, 0, "ob")
            self.abax.annotate(f"β = {betaorig}", xy=(betaorig_coord, 0),
                           xytext=(rightx, 1.7), arrowprops=arrowprops, ha="right")        
        else:
            self.abax.text(leftx, 1.7, f"α = {alphaorig}", ha="left")   
            self.abax.text(rightx, 1.7, f"β = {betaorig}", ha="right")       
    # そのフレームでのノードの評価値の表示
    if status in ["score", "update", "end"]:
        score = framedata["score"]
        score_coord = calc_coord(score)
        text_coord = leftx if maxnode else rightx
        ha = "left" if maxnode else "right"
        self.abax.plot(score_coord, 0, "ok")
        self.abax.annotate(f"score = {score}", xy=(score_coord, 0),
                           xytext=(text_coord, 1), arrowprops=arrowprops, ha=ha)        
    # 子ノードの評価値の表示
    if status == "score":
        childscore = framedata["childscore"]
        childscore_coord = calc_coord(childscore)
        text_coord = rightx if maxnode else leftx
        ha = "right" if maxnode else "left"
        self.abax.plot(childscore_coord, 0, "og")
        self.abax.annotate(f"cscore = {childscore}", xy=(childscore_coord, 0),
                           xytext=(text_coord, 1), arrowprops=arrowprops, ha=ha)
    # 置換表にデータが登録されていたかどうかの表示
    elif status == "tt":
        if framedata["registered_in_tt"]:
            textlist[3] = "置換表に登録済"
            textcolorlist[3] = "red"
            if minimax:
                score = framedata["lower_bound"]
                score_coord = calc_coord(score)
                self.abax.plot(score_coord, 0, "om")
                self.abax.annotate(f"置換表の評価値 = {score}", xy=(score_coord, 0),
                                   xytext=(centerx, 1), arrowprops=arrowprops, ha="center")
            else:
                if framedata["tt_pruned"]:
                    lower_bound = framedata["lower_bound"]
                    upper_bound = framedata["upper_bound"]
                    if lower_bound == upper_bound:
                        pruned_type = "exact value"
                    elif upper_bound <= alphaorig:
                        pruned_type = "fail low"
                    elif betaorig <= lower_bound:
                        pruned_type = "fail high"
                    textlist[4] = f"置換表による枝狩り ({pruned_type})"
                    textcolorlist[4] = "red"
                else:
                    if framedata["ab_updated"]:
                        textlist[5] = "α 値または β 値の初期値の更新"
                        textcolorlist[4] = "red"            
        else:
            textlist[3] = "置換表に未登録"
    # ノードの評価値が更新されたかどうかの表示
    elif status == "update":
        if framedata["updated"]:
            textlist[3] = "評価値の更新"
            textcolorlist[3] = "red"
        else:
            textlist[3] = "評価値の更新なし"
        if not minimax:
            if score <= alphaorig:
                score_type = "fail low"
            elif score < betaorig:
                score_type = "exact value"
            else:
                score_type = "fail high"
            textlist[3] += f" ({score_type})"
            if framedata["ab_pruned"]:
                textlist[4] = "β 狩り" if maxnode else "α 狩り"
                textcolorlist[4] = "red"
            else:
                alpha = max(alphaorig, score) if maxnode else alphaorig
                beta = betaorig if maxnode else min(betaorig, score)
                alpha_coord = calc_coord(alpha)
                beta_coord = calc_coord(beta)
                alpha_text_coord = rightx - 1.5 if maxnode else leftx
                beta_text_coord = rightx if maxnode else leftx + 1.5
                ha = "right" if maxnode else "left"
                self.abax.plot(alpha_coord, 0, "or")
                self.abax.plot(beta_coord, 0, "ob")
                self.abax.annotate(f"α 値", xy=(alpha_coord, 0),
                                      xytext=(alpha_text_coord, 1), arrowprops=arrowprops, ha=ha)
                self.abax.annotate(f"β 値", xy=(beta_coord, 0),
                                      xytext=(beta_text_coord, 1), arrowprops=arrowprops, ha=ha)
    # 置換表に登録したかどうかの表示
    elif status == "end":
        if self.mbtree.use_tt:
            if framedata["registered_in_tt"]:
                textlist[3] = "置換表に登録されていたデータを利用"
            else:
                textlist[3] = "置換表への登録"
                textcolorlist[3] = "red"
                 
    def draw_bound(lower, upper, y, color):
        lower_coord = calc_coord(lower)
        upper_coord = calc_coord(upper)
        if lower == upper:
            self.abax.plot(lower_coord, y, color=color, marker="o")        
        else:
            self.abax.annotate(f"", xy=(lower_coord, y), xytext=(upper_coord, y),
                                        arrowprops={ "arrowstyle": "<->", "color": color, "linewidth": 1.5})
            
    # 下界と上界に関する表示
    if not minimax:
        lower_text = ""
        upper_text = ""
        if status != "start":
            lower_bound = framedata["lower_bound"]
            upper_bound = framedata["upper_bound"]
            if framedata["registered_in_tt"]:
                color = "red"
                textlist[6] = "置換表のミニマックス値の範囲"
            else:
                color = "black"
                textlist[6] = "ミニマックス値は置換表に未登録"
            draw_bound(lower_bound, upper_bound, -2.3, color)
            lower_text = f"下界 = {lower_bound}"
            upper_text = f"上界 = {upper_bound}"
            self.abax.text(leftx, -1.8, lower_text, ha="left")   
            self.abax.text(rightx, -1.8, upper_text, ha="right")                   

    self.abfig.set_facecolor(facecolor)
    for i in range(linenum):
        self.abax.text(5, linetop - i * 0.7, textlist[i], c=textcolorlist[i])

    num_calculated = framedata["num_calculated"]
    num_pruned = framedata["num_pruned"]
    num_total = num_calculated + num_pruned
    num_ratio = num_calculated / num_total if num_total != 0 else 0
    prev_framedata = self.mbtree.ablist_by_score[self.prev_frame]
    prev_num_calculated = prev_framedata["num_calculated"]
    prev_num_pruned = prev_framedata["num_pruned"]
    prev_num_total = prev_num_calculated + prev_num_pruned
    diff_num_calculated = num_calculated - prev_num_calculated
    diff_num_pruned = num_pruned - prev_num_pruned
    diff_num_total = num_total - prev_num_total
    diff_num_ratio = diff_num_calculated / diff_num_total if diff_num_total != 0 else 0

    textlist = [ "計算済", "枝狩り", "合計", "割合" ]
    datalist = [ num_calculated, num_pruned, num_total, f"{num_ratio * 100:.1f}%"]
    diff_datalist = [ f"{diff_num_calculated:+d}", f"{diff_num_pruned:+d}", 
                    f"{diff_num_total:+d}", f"{diff_num_ratio * 100:.1f}%"]
    for i in range(4):
        self.abax.text(15, linetop - i * 0.7, textlist[i])
        self.abax.text(19.5, linetop - i * 0.7, datalist[i], ha="right")
        self.abax.text(22.5, linetop - i * 0.7, diff_datalist[i], ha="right")
        
    # 範囲の説明の表示
    if not minimax:
        facecolorlist = [
            "lightgray" if maxnode else "aqua", 
            "yellow",
            "aqua" if maxnode else "lightgray", 
        ]
        textlist = ["fail low", "exact value", "fail high"]
        textcolorlist = ["black", "black", "black"]
        if status == "tt" and framedata["tt_pruned"]:
            if pruned_type == "fail low":
                textcolorlist[0] = "red"
            elif pruned_type == "exact value":
                textcolorlist[1] = "red"
            elif pruned_type == "fail high":
                textcolorlist[2] = "red"
        elif status == "update":
            if maxnode:
                textlist[2] = "fail high (β 狩り)"
            else:
                textlist[0] = "fail low (α 狩り)"
            if score_type == "fail low":
                textcolorlist[0] = "red"
            elif score_type == "exact value":
                textcolorlist[1] = "red"
            elif score_type == "fail high":
                textcolorlist[2] = "red"            
        for i in range(3):
            rect = patches.Rectangle(xy=(15, linetop - 0.1 - (i + 5) * 0.7), 
                                     width=0.8, height=0.5, fc=facecolorlist[i], ec="k")
            self.abax.add_patch(rect)
            self.abax.text(16.2, linetop - (i + 5) * 0.7, textlist[i], c=textcolorlist[i])  
         
Mbtree_Anim.update_frameinfo = update_frameinfo
```
</details>

<details><summary><strong>修正箇所</strong></summary>

```diff_python
import matplotlib.patches as patches

def update_frameinfo(self):
元と同じなので省略
    # ノードの評価値が更新されたかどうかの表示
    elif status == "update":
        if framedata["updated"]:
            textlist[3] = "評価値の更新"
            textcolorlist[3] = "red"
        else:
            textlist[3] = "評価値の更新なし"
+       if not minimax:
+           if score <= alphaorig:
+               score_type = "fail low"
+           elif score < betaorig:
+               score_type = "exact value"
+           else:
+               score_type = "fail high"
+           textlist[3] += f" ({score_type})"
+           if framedata["ab_pruned"]:
+               textlist[4] = "β 狩り" if maxnode else "α 狩り"
+               textcolorlist[4] = "red"
+           else:
+               alpha = max(alphaorig, score) if maxnode else alphaorig
+               beta = betaorig if maxnode else min(betaorig, score)
+               alpha_coord = calc_coord(alpha)
+               beta_coord = calc_coord(beta)
+               alpha_text_coord = rightx - 1.5 if maxnode else leftx
+               beta_text_coord = rightx if maxnode else leftx + 1.5
+               ha = "right" if maxnode else "left"
+               self.abax.plot(alpha_coord, 0, "or")
+               self.abax.plot(beta_coord, 0, "ob")
+               self.abax.annotate(f"α 値", xy=(alpha_coord, 0),
+                                  xytext=(alpha_text_coord, 1), arrowprops=arrowprops, ha=ha)
+               self.abax.annotate(f"β 値", xy=(beta_coord, 0),
+                                  xytext=(beta_text_coord, 1), arrowprops=arrowprops, ha=ha)
元と同じなので省略
    # 範囲の説明の表示
    if not minimax:
元と同じなので省略
        if status == "tt" and framedata["tt_pruned"]:
元と同じなので省略
+       elif status == "update":
+           if maxnode:
+               textlist[2] = "fail high (β 狩り)"
+           else:
+               textlist[0] = "fail low (α 狩り)"
+           if score_type == "fail low":
+               textcolorlist[0] = "red"
+           elif score_type == "exact value":
+               textcolorlist[1] = "red"
+           elif score_type == "fail high":
+               textcolorlist[2] = "red"            
        for i in range(3):
            rect = patches.Rectangle(xy=(15, linetop - 0.1 - (i + 5) * 0.7), 
                                     width=0.8, height=0.5, fc=facecolorlist[i], ec="k")
            self.abax.add_patch(rect)
            self.abax.text(16.2, linetop - (i + 5) * 0.7, textlist[i], c=textcolorlist[i])  
元と同じなので省略
         
Mbtree_Anim.update_frameinfo = update_frameinfo
```
</details>

### 表示の確認

上記の修正後に下記のプログラムを実行して正しく表示されることを確認します。

```python
Mbtree_Anim(mbtree, isscore=True)
```

まず、__max ノード__ である __ゲーム開始時の局面の "update"__ のフレームの表示を確認します。

下図は __下段の > を 2 回クリック__ して __ゲーム開始時の局面の最初の子ノードの評価値が計算__ された __"score"__ の状態の 4061 フレーム目の図で、__子ノードの評価値として 0 が計算された__ ことがわかります。なお、ゲーム開始時の局面のミニマックス値の範囲は置換表に登録されていないことは明らかなので "tt" のフレームの表示は省略しました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/9d9e97e9-b8f5-4b71-8491-7be8cb2fba18.png" width="500">

下図は __下段の > をクリック__ して表示される __"update"__ の状態のフレームの図で、下記のように __正しく表示されることが確認__ できます。

* max ノードなので、右下の範囲の説明に「fail high（β 狩り）」が表示される
* ノードの評価値が 0 に更新されたことが下記のように表示される
    * 0 は exact value の範囲なので、真ん中の 4 行目に赤字で「評価値の更新（exact value）」が表示される
    * 右下の範囲の説明の「exact value」が赤字で表示される
* このフレームの α 値と β 値に関する情報が下記のように表示される
    * α 値と β 値を表す丸が正しい位置と色で数直線上に表示される
    * max ノードなので α 値と β 値の注釈が数直線の右上に表示される
    * 注釈から数直線上への矢印が交差することなく正しく表示される

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/3728ad45-5f40-443a-88b2-e19a2e6363dd.png" width="500">

下図は __上段の > をクリック__ して表示される __次の子ノードの評価値の計算が開始__ される __"start"__ の状態のフレームで、__上図のフレームの α 値と β 値__ がこのノードの __α 値と β 値の初期値として受け継がれた__ ことが確認できます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/b1d2e545-3a4a-4f99-8b9b-dfb6f448cd17.png" width="500">

次に、この __min ノード__ での __"update"__ のフレームの表示を確認します。

下図は __下段の > をクリック__ して表示される __"tt"__ の状態のフレームで __置換表に__ このノードのミニマックス値の範囲が __登録されていない__ ことがわかります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/40168ec0-1775-4b97-a6f3-dd40ca6d809f.png" width="500">

下図は __下段の > をクリック__ して表示される __"score"__ の状態のフレームの図で、__子ノードの評価値として 0 が計算された__ ことがわかります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/a56ab7d3-c8a7-4394-b5e7-a91cf03c6537.png" width="500">

下図は __下段の > をクリック__ して表示される __"update"__ の状態のフレームの図で、下記のように __正しく表示される__ ことが確認できます。

* min ノードなので、右下の範囲の説明に「fail low（α 狩り）」が表示される
* ノードの評価値が 0 に更新されたことが下記のように表示される
    * 0 は fail low の範囲なので、真ん中の 4 行目に赤字で「評価値の更新（fail low）」が表示される
    * 右下の範囲の説明の「fail low（α 狩り）」が赤字で表示される
* α 狩りが行なわれるので真ん中の 5 行目に赤字で「α 狩り」が表示される
* α 狩りが行なわれるので、このフレームの α 値と β 値に関する情報は表示されない

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/a3f7e534-af25-4427-a5d6-f8f4a358be03.png" width="500">

__min ノード__ で __α 狩りが行なわれない場合__ の表示を確認するために、__ゲーム開始時の局面の最初の子ノードの計算が開始__ される __2 フレーム目に移動__ し、__下段の > を 2 回クリック__ すると下図の __"score"__ の状態のフレームが表示され、子ノードの評価値として 2 が計算されたことがわかります。なお、ゲーム開始時の局面の最初の子ノードのミニマックス値の範囲は置換表に登録されていないことは明らかなので "tt" のフレームの表示は省略しました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/756ff6e0-a328-4534-97d4-4e1338cd050d.png" width="500">

下図は __下段の > をクリック__ して表示される __"update"__ の状態のフレームの図で、下記のよう __に正しく表示される__ ことが確認できます。

* min ノードなので、右下の範囲の説明に「fail low（α 狩り）」が表示される
* ノードの評価値が 2 に更新されたことが下記のように表示される
    * 2 は exact value の範囲なので、真ん中の 4 行目に赤字で「評価値の更新（exact value）」が表示される
    * 右下の範囲の説明の「exact value」が赤字で表示される
* このフレームの α 値と β 値に関する情報が下記のように表示される
    * α 値と β 値を表す丸が正しい位置と色で数直線上に表示される
    * min ノードなので α 値と β 値の注釈が数直線の左上に表示される
    * 注釈から数直線上への矢印が交差することなく正しく表示される

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/afdaef97-7200-443f-81fe-b4bcd54ef174.png" width="500">
    
まだ確認していない下記の場合の "update" の状態の表示を確認します。

* max ノードでそのノードの評価値が fail low の場合
* max ノードでそのノードの評価値が fail high で β 狩りが行なわれる場合
* min ノードでそのノードの評価値が fail high の場合

説明が長くなるので直前の "score" のフレームの図は省略します。

下図は __max ノードで評価値が fail low__ の場合の "update" の状態の 25 フレーム目の図で、下記のように正しく表示されることが確認できます。なお、先程の例と異なり下図では α 値はそのノードの評価値より大きな値が正しく計算されています。

* max ノードなので、右下の範囲の説明に「fail high（β 狩り）」が表示される
* ノードの評価値が 1 に更新されたことが下記のように表示される
    * 1 は fail low の範囲なので、真ん中の 4 行目に赤字で「評価値の更新（fail low）」が表示される
    * 右下の範囲の説明の「fail low」が赤字で表示される
* このフレームの α 値と β 値に関する情報が下記のように表示される
    * α 値と β 値を表す丸が正しい位置と色で数直線上に表示される
    * max ノードなので α 値と β 値の注釈が数直線の右上に表示される
    * 注釈から数直線上への矢印が交差することなく正しく表示される

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/eb3e0812-4883-4449-9f70-64e0d411a354.png" width="500">

:::note
[前回の記事](https://qiita.com/ysgeso/items/b10991d23a8c084c2bba#%E6%9D%A1%E4%BB%B6-2--6-%E3%82%92%E6%BA%80%E3%81%9F%E3%81%99%E3%83%95%E3%83%AC%E3%83%BC%E3%83%A0%E3%82%92%E6%8E%A2%E3%81%99%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E3%81%AE%E4%BD%9C%E6%88%90)で作成したプログラムと同様なので説明は省略しますが、上記の条件を満たすフレームを探す際に、下記のようなプログラムを実行しました。

```python
for i, framedata in enumerate(mbtree.ablist_by_score):
    if framedata["status"] == "update":
        node = mbtree.nodelist_by_score[i]
        maxnode = node.mb.move_count % 2 == 0
        score = framedata["score"]
        alphaorig = framedata["alphaorig"]
        betaorig = framedata["betaorig"]
        if maxnode and score <= alphaorig:
            print(i)
            break
```

実行結果

```
25
```

下記は残りの条件のフレームを探すプログラムです。

max ノードでそのノードの評価値が fail high で β 狩りが行なわれる場合。

```python
for i, framedata in enumerate(mbtree.ablist_by_score):
    if framedata["status"] == "update":
        node = mbtree.nodelist_by_score[i]
        maxnode = node.mb.move_count % 2 == 0
        score = framedata["score"]
        alphaorig = framedata["alphaorig"]
        betaorig = framedata["betaorig"]
        if maxnode and score >= betaorig:
            print(i)
            break
```

実行結果

```
71
```

min ノードでそのノードの評価値が fail high の場合。

```python
for i, framedata in enumerate(mbtree.ablist_by_score):
    if framedata["status"] == "update":
        node = mbtree.nodelist_by_score[i]
        maxnode = node.mb.move_count % 2 == 0
        score = framedata["score"]
        alphaorig = framedata["alphaorig"]
        betaorig = framedata["betaorig"]
        if not maxnode and score >= betaorig:
            print(i)
            break
```

実行結果

```
283
```
:::

下図は __max ノード__ で評価値が __fail high で β 狩りが行なわれる場合__ の "update" の状態の 71 フレーム目の図で、下記のように正しく表示されることが確認できます。

* max ノードなので、右下の範囲の説明に「fail high（β 狩り）」が表示される
* ノードの評価値が 1 に更新されたことが下記のように表示される
    * 1 は fail high の範囲なので、真ん中の 4 行目に赤字で「評価値の更新（fail high）」が表示される
    * 右下の範囲の説明の「fail high（β 狩り）」が赤字で表示される
* β 狩りが行なわれるので真ん中の 5 行目に赤字で「β 狩り」が表示される
* β 狩りが行なわれるので、このフレームの α 値と β 値に関する情報は表示されない

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/e33f93a3-be71-4c4c-aa44-ee04609a06f1.png" width="500">

下図は __min ノード__ で評価値が __fail high の場合__ の "update" の状態の 283 フレーム目の図でで、fail high の場合の表示が下記のように正しく表示されることが確認できます。

* min ノードなので、右下の範囲の説明に「fail low（α 狩り）」が表示される
* ノードの評価値が 2 に更新されたことが下記のように表示される
    * 2 は fail high の範囲なので、真ん中の 4 行目に赤字で「評価値の更新（fail high）」が表示される
    * 右下の範囲の説明の「fail high（β 狩り）」が赤字で表示される
* このフレームの α 値と β 値に関する情報が下記のように表示される
    * α 値と β 値を表す丸が正しい位置と色で数直線上に表示される
    * min ノードなので α 値と β 値の注釈が数直線の左上に表示される
    * 注釈から数直線上への矢印が交差することなく正しく表示される

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/287a4e27-d409-4136-9840-d8a7cb3f975b.png" width="500">

__"update"__  のフレームで __評価値の更新がされない場合__ の確認を忘れていたことに気が付きましたので、確認することにします。__ゲーム開始時の局面の全ての子ノードの評価値は 0__ なので、ゲーム開始時の局面の __2 つ目の子ノードの評価値が計算された次の "update" のフレーム__ では __評価値は更新されません__。下図は最初のフレームに戻って __下段の > を 5 回クリック__ して表示されるそのフレームの図で、真ん中の 4 行目に __黒字で「評価値の更新なし（exact value）」が正しく表示される__ ことが確認できます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/e0bd791f-46b0-4e14-8c67-5224c86191c7.png" width="500">

## "end" の状態で表示する内容の実装

下記は "end" の状態で表示する内容の再掲です。（※）の部分は実装済なので、それ以外の場合の表示を実装する必要があります。

* （※）数直線上にそのフレームでのノードの評価値を表示
* このノードの評価値の種類によってフレームの状態を以下のように表示する
    * fail low の場合は「評価値の確定（fail low）」を表示
    * exact value の場合は「評価値の確定（exact value）」を表示
    * fail high の場合は「評価値の確定（fail high）」を表示
* 置換表を利用する場合
    * 置換表による枝狩りが行われた場合
        * （※）黒字で「置換表に登録されていたデータを利用」を表示
    * 置換表による枝狩りが行われていない場合
        * （※）赤字で「置換表への登録」を表示
        * 計算された範囲
        * 登録する範囲

また、上記とは別に、__以下のような表示の工夫__ を行なうことにします。どのように実装すればよいかについて少し考えてみて下さい。

* 右下に表示される範囲の説明のうち、確定したノードの評価値の範囲を赤字で表示する
* "tt" のフレームで __置換表による枝狩りを行わない場合__ は、__"end" のフレームで置換表の内容を更新する処理__[^3] を行う必要がある。そこで、__置換表による枝狩りを行わない場合__ は、__"end" のフレーム__ で __計算された範囲__ と __登録する範囲__ を表示するようにする。また、__下界と上界の注釈に表示する__ 値を、__登録する範囲の下界と上界の__ 値にする

なお、置換表の範囲、計算された範囲、登録する範囲は本記事独自の用語で、[以前の記事](https://qiita.com/ysgeso/items/42c2140766dd59906a02#end-%E3%81%AE%E7%8A%B6%E6%85%8B%E3%81%AE%E8%A1%A8%E7%A4%BA)で説明した __3 種類のミニマックス値の範囲__ を __区別できるよう__ に短く表記した用語です。

| 表記 | 意味 |
|:--|:--|
| __置換表の範囲__  | 置換表に登録されていたミニマックス値の範囲  | 
| __計算された範囲__  | 子ノードの評価値から計算されたミニマックス値の範囲  |
| __登録する範囲__  | 置換表に登録する、上記の 2 つの共通する範囲  | 

本記事では上記の計算された範囲と登録する範囲を以下のように表示することにします。

* 計算された範囲を置換表の範囲の下に緑色の図形で表示する
* 計算された範囲の図形の右に（真ん中の 8 行目の位置）に「計算されたミニマックス値」という説明文を表示する
* 登録する範囲を計算された範囲の下に青色の図形で表示する
* 登録する範囲の図形の右に（真ん中の 9 行目の位置）に「置換表に登録したミニマックス値」という説明文を表示する

### `update_frameinfo` の修正

下記は上記を表示するように `update_frameinfo` を修正したプログラムです。

__評価値の種類によって対応する範囲の説明を赤く表示__ するという処理は、__"update" のフレームでも行っています__。そこで、__評価値の種類__ を "update" のフレームの場合と __同じ `score_type` という変数に代入__ することで、範囲の説明を赤く表示するという処理を __63 ~ 69 行目でまとめて行うという工夫__ を行ないました。

* __4 ~ 6 行目__：αβ 法の場合は、フレームの状態を表す dict の `"score_type"` というキーに評価値の種類を表す文字列が代入されているので、それを `score_type` に代入し、真ん中の 2 行目の末尾に追加する
* __8 行目__：ミニマックス法では真ん中の 3 行目に表示するメッセージを、置換表に登録されていたかどうかで変えていたが、αβ 法では置換表に登録されていた場合でも置換表の値を更新する場合があるので、置換表による枝狩りが行われているかどうかで判定を行うように修正した
* __15、16、27、28、38、39 行目__：数直線の下に表示する下界と上界の注釈とその値を、状況に応じて計算して変数に代入する。なお、"end" の状態で置換表による枝狩りが行われていない場合は、27、28 行目で計算された値を 38、39 行目で上書きする
* __40、41 行目__：下界と上界の注釈の表示はこれまでは 28 行目の下に "start" 以外の状態のブロックで行なっていたが、40、41 行目でフレームの状態に関わらずすべての場合でまとめて表示するように修正した
* __29 ~ 39 行目__：αβ 法、"end" のフレーム、置換表による枝狩りが行われていないという条件がすべて満たされる場合に、計算された範囲と登録する範囲をフレームの情報を表す dict から取り出し、その図形を[前回の記事](https://qiita.com/ysgeso/items/b10991d23a8c084c2bba#%E4%B8%A1%E7%AB%AF%E3%81%AE%E7%9F%A2%E5%8D%B0%E3%81%8C%E4%BB%98%E3%81%84%E3%81%9F%E7%B7%9A%E3%81%AE%E6%8F%8F%E7%94%BB%E6%96%B9%E6%B3%95)で定義した `draw_bound` を利用して描画する
* __36、37 行目__：計算された範囲と登録する範囲を説明するメッセージを真ん中の 8、9 行目に表示するようにする
* __63 ~ 69 行目__：ノードの評価値の種類を 5 行目で "update" の状態の場合に評価値の種類を計算した `score_type` という同じ名前の変数に代入したので、"update" と "end" の状態で右下の範囲の説明を赤く表示する処理をまとめて行うように修正した

```python
 1  def update_frameinfo(self):
元と同じなので省略
 2      # 置換表に登録したかどうかの表示
 3      elif status == "end":
 4          if not minimax:
 5              score_type = framedata['score_type']
 6              textlist[2] += f"（{score_type}）"
 7          if self.mbtree.use_tt:
 8              if framedata["tt_pruned"]:
 9                  textlist[3] = "置換表に登録されていたデータを利用"
10              else:
11                  textlist[3] = "置換表への登録"
12                  textcolorlist[3] = "red"
元と同じなので省略
13      # 下界と上界に関する表示
14      if not minimax:
15          lower_text = ""
16          upper_text = ""
17          if status != "start":
18              lower_bound = framedata["lower_bound"]
19              upper_bound = framedata["upper_bound"]
20              if framedata["registered_in_tt"]:
21                  color = "red"
22                  textlist[6] = "置換表のミニマックス値の範囲"
23              else:
24                  color = "black"
25                  textlist[6] = "ミニマックス値は置換表に未登録"
26              draw_bound(lower_bound, upper_bound, -2.3, color)
27              lower_text = f"下界 = {lower_bound}"
28              upper_text = f"上界 = {upper_bound}"
29          if status == "end" and not framedata["tt_pruned"]:
30              clower_bound = framedata["clower_bound"]
31              cupper_bound = framedata["cupper_bound"]
32              draw_bound(clower_bound, cupper_bound, -3.0, "green")
33              tlower_bound = framedata["tlower_bound"]
34              tupper_bound = framedata["tupper_bound"]
35              draw_bound(tlower_bound, tupper_bound, -3.7, "blue")
36              textlist[7] = "計算されたミニマックス値"
37              textlist[8] = "置換表に登録したミニマックス値"
38              lower_text = f"下界 = {tlower_bound}"
39              upper_text = f"上界 = {tupper_bound}"
40          self.abax.text(leftx, -1.8, lower_text, ha="left")   
41          self.abax.text(rightx, -1.8, upper_text, ha="right")             
元と同じなので省略
42      # 範囲の説明の表示
43      if not minimax:
44          facecolorlist = [
45              "lightgray" if maxnode else "aqua", 
46              "yellow",
47              "aqua" if maxnode else "lightgray", 
48          ]
49          textlist = ["fail low", "exact value", "fail high"]
50          textcolorlist = ["black", "black", "black"]
51          if status == "tt" and framedata["tt_pruned"]:
52              if pruned_type == "fail low":
53                  textcolorlist[0] = "red"
54              elif pruned_type == "exact value":
55                  textcolorlist[1] = "red"
56              elif pruned_type == "fail high":
57                  textcolorlist[2] = "red"
58          elif status == "update":
59              if maxnode:
60                  textlist[2] = "fail high (β 狩り)"
61              else:
62                  textlist[0] = "fail low (α 狩り)"
63          if status == "update" or status == "end":
64              if score_type == "fail low":
65                  textcolorlist[0] = "red"
66              elif score_type == "exact value":
67                  textcolorlist[1] = "red"
68              elif score_type == "fail high":
69                  textcolorlist[2] = "red"                 
70          for i in range(3):
71              rect = patches.Rectangle(xy=(15, linetop - 0.1 - (i + 5) * 0.7), 
72                                       width=0.8, height=0.5, fc=facecolorlist[i], ec="k")
73              self.abax.add_patch(rect)
74              self.abax.text(16.2, linetop - (i + 5) * 0.7, textlist[i], c=textcolorlist[i])  
75           
76  Mbtree_Anim.update_frameinfo = update_frameinfo
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
def update_frameinfo(self):
    def calc_coord(score):
        return min(max(minus_inf, score), plus_inf)
        
    framedata = self.mbtree.ablist_by_score[self.play.value]
    status = framedata["status"]
    maxnode = self.selectednode.mb.turn == Marubatsu.CIRCLE
    minimax = self.mbtree.minimax
    
    self.abax.clear()
    self.abax.set_xlim(-4, 23)
    if minimax:
        self.abax.set_ylim(-1.5, 1.5)
    else:
        self.abax.set_ylim(-4.3, 2.3)
    self.abax.axis("off")

    minus_inf = -3 if self.mbtree.shortest_victory else -2
    plus_inf = 4 if self.mbtree.shortest_victory else 2

    # 範囲の色分け
    if not minimax:
        alphaorig = framedata["alphaorig"]
        betaorig = framedata["betaorig"]
        alphaorig_coord = calc_coord(alphaorig)
        betaorig_coord = calc_coord(betaorig)
        color = "lightgray" if maxnode else "aqua"
        rect = patches.Rectangle(xy=(minus_inf, -0.5), width=alphaorig_coord-minus_inf,
                                height=1, fc=color)
        self.abax.add_patch(rect)
        rect = patches.Rectangle(xy=(alphaorig_coord, -0.5), width=betaorig_coord-alphaorig_coord,
                                height=1, fc="yellow")
        self.abax.add_patch(rect)
        color = "aqua" if maxnode else "lightgray"
        rect = patches.Rectangle(xy=(betaorig_coord, -0.5), width=plus_inf-betaorig_coord,
                                height=1, fc=color)
        self.abax.add_patch(rect)
    # 数直線の描画    
    self.abax.plot(range(minus_inf, plus_inf + 1), [0] * (plus_inf + 1 - minus_inf) , "|-k")
    for num in range(minus_inf, plus_inf + 1):
        if num == minus_inf:
            numtext = "" if self.mbtree.init_ab else "-∞"
        elif num == plus_inf:
            numtext = "" if self.mbtree.init_ab else "∞"
        else:
            numtext = num
        self.abax.text(num, -1, numtext, ha="center")        

    # メッセージの表示
    if minimax:
        linenum = 4
        linetop = 1
    else:
        linenum = 9
        linetop = 1.7
    textlist = [""] * linenum
    textcolorlist = ["black"] * linenum

    algorithm = "mm 法" if self.mbtree.minimax else "αβ法"
    use_tt = "〇" if self.mbtree.use_tt else "×"
    shortest_victory = "〇" if self.mbtree.shortest_victory else "×"
    init_ab = "〇" if self.mbtree.init_ab else "×"
    textlist[0] = f"{algorithm}　置換表 {use_tt}　最短 {shortest_victory}"
    if not self.mbtree.minimax:
        textlist[0] += f"　初期値 {init_ab}"
    
    textlist[1] = f"深さ {self.selectednode.mb.move_count} "
    if maxnode:
        textlist[1] += "max node"
    else:
        textlist[1] += "min node"
    
    statusdata = {
        "start": {
            "text": "処理の開始",
            "color": "white"
        },
        "tt": {
            "text": "置換表の処理",
            "color": "honeydew"
        },
        "score": {
            "text": "子ノードの評価値",
            "color": "lightyellow"
        },
        "update": {
            "text": "更新処理",
            "color": "lightcyan"
        },
        "end": {
            "text": "評価値の確定",
            "color": "lavenderblush"
        },
    }
    textlist[2] = statusdata[status]["text"]
    facecolor = statusdata[status]["color"]
    
    arrowprops = { "arrowstyle": "->"}
    leftx = -3
    rightx = 4
    centerx = (leftx + rightx) / 2
    # α 値 と β 値の初期値の表示
    if not minimax:
        if status == "start" or status == "tt":
            self.abax.plot(alphaorig_coord, 0, "or")
            self.abax.annotate(f"α = {alphaorig}", xy=(alphaorig_coord, 0),
                           xytext=(leftx, 1.7), arrowprops=arrowprops, ha="left")        
            self.abax.plot(betaorig_coord, 0, "ob")
            self.abax.annotate(f"β = {betaorig}", xy=(betaorig_coord, 0),
                           xytext=(rightx, 1.7), arrowprops=arrowprops, ha="right")        
        else:
            self.abax.text(leftx, 1.7, f"α = {alphaorig}", ha="left")   
            self.abax.text(rightx, 1.7, f"β = {betaorig}", ha="right")       
    # そのフレームでのノードの評価値の表示
    if status in ["score", "update", "end"]:
        score = framedata["score"]
        score_coord = calc_coord(score)
        text_coord = leftx if maxnode else rightx
        ha = "left" if maxnode else "right"
        self.abax.plot(score_coord, 0, "ok")
        self.abax.annotate(f"score = {score}", xy=(score_coord, 0),
                           xytext=(text_coord, 1), arrowprops=arrowprops, ha=ha)        
    # 子ノードの評価値の表示
    if status == "score":
        childscore = framedata["childscore"]
        childscore_coord = calc_coord(childscore)
        text_coord = rightx if maxnode else leftx
        ha = "right" if maxnode else "left"
        self.abax.plot(childscore_coord, 0, "og")
        self.abax.annotate(f"cscore = {childscore}", xy=(childscore_coord, 0),
                           xytext=(text_coord, 1), arrowprops=arrowprops, ha=ha)
    # 置換表にデータが登録されていたかどうかの表示
    elif status == "tt":
        if framedata["registered_in_tt"]:
            textlist[3] = "置換表に登録済"
            textcolorlist[3] = "red"
            if minimax:
                score = framedata["lower_bound"]
                score_coord = calc_coord(score)
                self.abax.plot(score_coord, 0, "om")
                self.abax.annotate(f"置換表の評価値 = {score}", xy=(score_coord, 0),
                                   xytext=(centerx, 1), arrowprops=arrowprops, ha="center")
            else:
                if framedata["tt_pruned"]:
                    lower_bound = framedata["lower_bound"]
                    upper_bound = framedata["upper_bound"]
                    if lower_bound == upper_bound:
                        pruned_type = "exact value"
                    elif upper_bound <= alphaorig:
                        pruned_type = "fail low"
                    elif betaorig <= lower_bound:
                        pruned_type = "fail high"
                    textlist[4] = f"置換表による枝狩り ({pruned_type})"
                    textcolorlist[4] = "red"
                else:
                    if framedata["ab_updated"]:
                        textlist[5] = "α 値または β 値の初期値の更新"
                        textcolorlist[4] = "red"            
        else:
            textlist[3] = "置換表に未登録"
    # ノードの評価値が更新されたかどうかの表示
    elif status == "update":
        if framedata["updated"]:
            textlist[3] = "評価値の更新"
            textcolorlist[3] = "red"
        else:
            textlist[3] = "評価値の更新なし"
        if not minimax:
            if score <= alphaorig:
                score_type = "fail low"
            elif score < betaorig:
                score_type = "exact value"
            else:
                score_type = "fail high"
            textlist[3] += f" ({score_type})"
            if framedata["ab_pruned"]:
                textlist[4] = "β 狩り" if maxnode else "α 狩り"
                textcolorlist[4] = "red"
            else:
                alpha = max(alphaorig, score) if maxnode else alphaorig
                beta = betaorig if maxnode else min(betaorig, score)
                alpha_coord = calc_coord(alpha)
                beta_coord = calc_coord(beta)
                alpha_text_coord = rightx - 1.5 if maxnode else leftx
                beta_text_coord = rightx if maxnode else leftx + 1.5
                ha = "right" if maxnode else "left"
                self.abax.plot(alpha_coord, 0, "or")
                self.abax.plot(beta_coord, 0, "ob")
                self.abax.annotate(f"α 値", xy=(alpha_coord, 0),
                                      xytext=(alpha_text_coord, 1), arrowprops=arrowprops, ha=ha)
                self.abax.annotate(f"β 値", xy=(beta_coord, 0),
                                      xytext=(beta_text_coord, 1), arrowprops=arrowprops, ha=ha)
    # 置換表に登録したかどうかの表示
    elif status == "end":
        if not minimax:
            score_type = framedata['score_type']
            textlist[2] += f"（{score_type}）"
        if self.mbtree.use_tt:
            if framedata["registered_in_tt"]:
                textlist[3] = "置換表に登録されていたデータを利用"
            else:
                textlist[3] = "置換表への登録"
                textcolorlist[3] = "red"
                 
    def draw_bound(lower, upper, y, color):
        lower_coord = calc_coord(lower)
        upper_coord = calc_coord(upper)
        if lower == upper:
            self.abax.plot(lower_coord, y, color=color, marker="o")        
        else:
            self.abax.annotate(f"", xy=(lower_coord, y), xytext=(upper_coord, y),
                                        arrowprops={ "arrowstyle": "<->", "color": color, "linewidth": 1.5})
            
    # 下界と上界に関する表示
    if not minimax:
        lower_text = ""
        upper_text = ""
        if status != "start":
            lower_bound = framedata["lower_bound"]
            upper_bound = framedata["upper_bound"]
            if framedata["registered_in_tt"]:
                color = "red"
                textlist[6] = "置換表のミニマックス値の範囲"
            else:
                color = "black"
                textlist[6] = "ミニマックス値は置換表に未登録"
            draw_bound(lower_bound, upper_bound, -2.3, color)
            lower_text = f"下界 = {lower_bound}"
            upper_text = f"上界 = {upper_bound}"
        if status == "end" and not framedata["tt_pruned"]:
            clower_bound = framedata["clower_bound"]
            cupper_bound = framedata["cupper_bound"]
            draw_bound(clower_bound, cupper_bound, -3.0, "green")
            tlower_bound = framedata["tlower_bound"]
            tupper_bound = framedata["tupper_bound"]
            draw_bound(tlower_bound, tupper_bound, -3.7, "blue")
            textlist[7] = "計算されたミニマックス値"
            textlist[8] = "置換表に登録したミニマックス値"
            lower_text = f"下界 = {tlower_bound}"
            upper_text = f"上界 = {tupper_bound}"
        self.abax.text(leftx, -1.8, lower_text, ha="left")   
        self.abax.text(rightx, -1.8, upper_text, ha="right") 
        
    self.abfig.set_facecolor(facecolor)
    for i in range(linenum):
        self.abax.text(5, linetop - i * 0.7, textlist[i], c=textcolorlist[i])

    num_calculated = framedata["num_calculated"]
    num_pruned = framedata["num_pruned"]
    num_total = num_calculated + num_pruned
    num_ratio = num_calculated / num_total if num_total != 0 else 0
    prev_framedata = self.mbtree.ablist_by_score[self.prev_frame]
    prev_num_calculated = prev_framedata["num_calculated"]
    prev_num_pruned = prev_framedata["num_pruned"]
    prev_num_total = prev_num_calculated + prev_num_pruned
    diff_num_calculated = num_calculated - prev_num_calculated
    diff_num_pruned = num_pruned - prev_num_pruned
    diff_num_total = num_total - prev_num_total
    diff_num_ratio = diff_num_calculated / diff_num_total if diff_num_total != 0 else 0

    textlist = [ "計算済", "枝狩り", "合計", "割合" ]
    datalist = [ num_calculated, num_pruned, num_total, f"{num_ratio * 100:.1f}%"]
    diff_datalist = [ f"{diff_num_calculated:+d}", f"{diff_num_pruned:+d}", 
                    f"{diff_num_total:+d}", f"{diff_num_ratio * 100:.1f}%"]
    for i in range(4):
        self.abax.text(15, linetop - i * 0.7, textlist[i])
        self.abax.text(19.5, linetop - i * 0.7, datalist[i], ha="right")
        self.abax.text(22.5, linetop - i * 0.7, diff_datalist[i], ha="right")
        
    # 範囲の説明の表示
    if not minimax:
        facecolorlist = [
            "lightgray" if maxnode else "aqua", 
            "yellow",
            "aqua" if maxnode else "lightgray", 
        ]
        textlist = ["fail low", "exact value", "fail high"]
        textcolorlist = ["black", "black", "black"]
        if status == "tt" and framedata["tt_pruned"]:
            if pruned_type == "fail low":
                textcolorlist[0] = "red"
            elif pruned_type == "exact value":
                textcolorlist[1] = "red"
            elif pruned_type == "fail high":
                textcolorlist[2] = "red"
        elif status == "update":
            if maxnode:
                textlist[2] = "fail high (β 狩り)"
            else:
                textlist[0] = "fail low (α 狩り)"
        if status == "update" or status == "end":
            if score_type == "fail low":
                textcolorlist[0] = "red"
            elif score_type == "exact value":
                textcolorlist[1] = "red"
            elif score_type == "fail high":
                textcolorlist[2] = "red"                  
        for i in range(3):
            rect = patches.Rectangle(xy=(15, linetop - 0.1 - (i + 5) * 0.7), 
                                     width=0.8, height=0.5, fc=facecolorlist[i], ec="k")
            self.abax.add_patch(rect)
            self.abax.text(16.2, linetop - (i + 5) * 0.7, textlist[i], c=textcolorlist[i])  
         
Mbtree_Anim.update_frameinfo = update_frameinfo
```
</details>

<details><summary><strong>修正箇所</strong></summary>

```diff_python
def update_frameinfo(self):
元と同じなので省略
    # 置換表に登録したかどうかの表示
    elif status == "end":
+       if not minimax:
+           score_type = framedata['score_type']
+           textlist[2] += f"（{score_type}）"
        if self.mbtree.use_tt:
-           if framedata["registered_in_tt"]:
+           if framedata["tt_pruned"]:
                textlist[3] = "置換表に登録されていたデータを利用"
            else:
                textlist[3] = "置換表への登録"
                textcolorlist[3] = "red"
元と同じなので省略
    # 下界と上界に関する表示
    if not minimax:
        lower_text = ""
        upper_text = ""
        if status != "start":
            lower_bound = framedata["lower_bound"]
            upper_bound = framedata["upper_bound"]
            if framedata["registered_in_tt"]:
                color = "red"
                textlist[6] = "置換表のミニマックス値の範囲"
            else:
                color = "black"
                textlist[6] = "ミニマックス値は置換表に未登録"
            draw_bound(lower_bound, upper_bound, -2.3, color)
            lower_text = f"下界 = {lower_bound}"
            upper_text = f"上界 = {upper_bound}"
-           self.abax.text(leftx, -1.8, lower_text, ha="left")   
-           self.abax.text(rightx, -1.8, upper_text, ha="right")             
+       if status == "end" and not framedata["tt_pruned"]:
+           clower_bound = framedata["clower_bound"]
+           cupper_bound = framedata["cupper_bound"]
+           draw_bound(clower_bound, cupper_bound, -3.0, "green")
+           tlower_bound = framedata["tlower_bound"]
+           tupper_bound = framedata["tupper_bound"]
+           draw_bound(tlower_bound, tupper_bound, -3.7, "blue")
+           textlist[7] = "計算されたミニマックス値"
+           textlist[8] = "置換表に登録したミニマックス値"
+           lower_text = f"下界 = {tlower_bound}"
+           upper_text = f"上界 = {tupper_bound}"
+       self.abax.text(leftx, -1.8, lower_text, ha="left")   
+       self.abax.text(rightx, -1.8, upper_text, ha="right")             
元と同じなので省略
    # 範囲の説明の表示
    if not minimax:
        facecolorlist = [
            "lightgray" if maxnode else "aqua", 
            "yellow",
            "aqua" if maxnode else "lightgray", 
        ]
        textlist = ["fail low", "exact value", "fail high"]
        textcolorlist = ["black", "black", "black"]
        if status == "tt" and framedata["tt_pruned"]:
            if pruned_type == "fail low":
                textcolorlist[0] = "red"
            elif pruned_type == "exact value":
                textcolorlist[1] = "red"
            elif pruned_type == "fail high":
                textcolorlist[2] = "red"
        elif status == "update":
            if maxnode:
                textlist[2] = "fail high (β 狩り)"
            else:
                textlist[0] = "fail low (α 狩り)"
-           if score_type == "fail low":
-               textcolorlist[0] = "red"
-           elif score_type == "exact value":
-               textcolorlist[1] = "red"
-           elif score_type == "fail high":
-               textcolorlist[2] = "red"                
+       if status == "update" or status == "end":
+           if score_type == "fail low":
+               textcolorlist[0] = "red"
+           elif score_type == "exact value":
+               textcolorlist[1] = "red"
+           elif score_type == "fail high":
+               textcolorlist[2] = "red"                 
        for i in range(3):
            rect = patches.Rectangle(xy=(15, linetop - 0.1 - (i + 5) * 0.7), 
                                     width=0.8, height=0.5, fc=facecolorlist[i], ec="k")
            self.abax.add_patch(rect)
            self.abax.text(16.2, linetop - (i + 5) * 0.7, textlist[i], c=textcolorlist[i])  
         
Mbtree_Anim.update_frameinfo = update_frameinfo
```
</details>

### 表示の確認

上記の修正後に下記のプログラムを実行して正しく表示されることを確認します。

```python
Mbtree_Anim(mbtree, isscore=True)
```

まず、__置換表にミニマックス値の範囲が記録されていない場合__ の表示の確認として、__ゲーム開始時の局面の表示を確認__ します。

下記は最初の __"start"__ の状態のフレームで、これまでと同様に __正しく表示される__ ことが確認できます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/b7ff4b0b-91e6-4acd-a141-6f664aa7d3d7.png" width="500">

下記は __下段の > をクリック__ して表示されるゲーム開始時の局面の __"tt"__、__"score"__、__"update"__ のフレームの表示で、いずれも数直線の下に表示される __下界と上界の注釈と範囲が正しく表示される__ ことが確認できます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/73bcfb14-3653-44c6-874f-655f7c21edeb.png" width="500">

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/6af1abd2-1fe0-4905-9be7-d13f4a801a64.png" width="500">

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/32a94704-59e3-4e13-9a08-7c4e2365e1d0.png" width="500">

下記は __下段の >> をクリック__ 表示されるゲーム開始時の局面の __"end"__ のフレームの表示で、__このノードの評価値__ が __exact value の範囲である 0 に確定__ しており、下記のように正しい表示が行われていることが確認できます。

* 確定した評価値が exact value なので以下の表示が行われる
    * 真ん中の 3 行目に「評価値の確定（exact value）」が表示される
    * 計算された範囲である 0 の位置に緑色の丸で表示され、その右に「計算されたミニマックス値」という説明文が表示される
    * 右下の範囲の説明の「exact value」が赤色で表示される
* 置換表による枝狩りが行われなかったため真ん中の 4 行目に赤字で「置換表への登録」が表示される
* 登録する範囲は置換表の範囲と計算された範囲の共通する部分なので下界と上界は 0 となり以下の内容が表示される
    * 登録する範囲が 0 の位置に青色の丸で表示される
    * その右に「置換表に登録したミニマックス値」という説明文が表示される
    * 下界と上界の注釈の値に、登録する範囲の下界と上界の値である 0 が表示される

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/eaf96085-3f8f-4703-ada4-aa52119f2f30.png" width="500">

次に、置換表による枝狩りが行われるノードの表示を確認します。

[以前の記事](https://qiita.com/ysgeso/items/b10991d23a8c084c2bba#%E8%A1%A8%E7%A4%BA%E3%81%AE%E7%A2%BA%E8%AA%8D)で説明したように、ゲーム開始時の局面の 3 つ目の子ノードは 1 つ目の子ノードの同一局面なので、そのノードのミニマックス値の範囲は置換表に登録されています。

下図はそのノードの "tt" のフレームで、置換表にミニマックス値の範囲として下界と上界が等しい exact value に分類される値が登録されているので置換表による枝狩りが行われます。そのことが下記のように正しく表示されています。

* 真ん中の 4 行目に赤字で「置換表に登録済」が表示される
* 真ん中の 5 行目に赤字で「置換表による枝狩り（exact value）」が表示される

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/4caa6509-6302-4b64-86d6-5eb8663685eb.png" width="500">

従って、その次のフレームは下図のように "end" のフレームとなり、下記のように正しい表示が行われていることが確認できます。

* 真ん中の 4 行目に黒字で「置換表に登録されていたデータを利用」が表示される
* 数直線の下には置換表の範囲のみが表示される

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/335fd222-e659-4d82-a984-4a934e29a77b.png" width="500">

最後に、置換表にミニマックス値が登録されているが、置換表による枝狩りが行われないノードの表示を確認します。下記はそのフレームを探すプログラムです。

```python
for i, framedata in enumerate(mbtree.ablist_by_score):
    if framedata["status"] == "end":
        if not framedata["tt_pruned"] and framedata["registered_in_tt"]:
            print(i)
            break
```            

実行結果

```
72
```

下記は上記で見つけた 72 フレーム目の図で、下記のように正しい表示が行われていることが確認できます。

* 真ん中の 4 行目に黒字で「置換表に登録されていたデータを利用」が表示される
* 数直線の下に計算された範囲と登録された範囲が表示される
* 登録された範囲に置換表の範囲と計算された範囲の共通する部分が正しく計算されている

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/557b94fe-f27a-469f-8c4a-b482875a20ab.png" width="500">

# 今回の記事のまとめ

今回の記事では前回の記事で説明したバグを修正し、__αβ 法の視覚化 のための Mbtree_Anim の修正を完了__ しました。余裕がある方は、様々なフレームを表示して αβ 法の視覚化がうまく行われていることを確認してみて下さい。

また、もっと良い視覚化の方法を思いついた人は是非実装してみて下さい。その際はコメントでその方法を紹介して頂けるとうれしいです。

# 本記事で入力したプログラム

| リンク | 説明 |
|:--|:--|
| [marubatsu.ipynb](https://github.com/ysgeso/marubatsu/blob/master/162/marubatsu.ipynb)  | 本記事で入力して実行した JupyterLab のファイル |
| [tree.py](https://github.com/ysgeso/marubatsu/blob/master/162/tree_new.py)  | 本記事で更新した tree_new.py  |
| [ai.py](https://github.com/ysgeso/marubatsu/blob/master/162/ai_new.py)  | 本記事で更新した ai_new.py  |

# 次回の記事

https://qiita.com/ysgeso/items/7fe6c130a6fcf34260dc

[^1]:前回までの記事ではゲーム開始時の局面のノードのことを単にルートノードと記述していましたが、評価値を計算する局面のノードもルートノードと記述しており紛らわしいため、今回の記事から「ゲーム開始時の局面」と記述して区別できるようにすることにします
[^2]:α 値と β 値の初期値の範囲が本来よりも広くなった場合は、本来行われるはずの α 狩りまたは β 狩りが行われなくなってしまうという状況が生じます
[^3]:登録する範囲が、置換表の範囲と等しい場合も含みます