Pythonで〇×ゲームのAIを一から作成する　その158　ミニマックス法の視覚化のための Mbtree_Anim の修正

# 目次と前回の記事

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

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

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

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

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

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

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

# ミニマックス法の視覚化のための Mbtree_Anim の修正

前回の記事では __`calc_score_for_anim`__ で行ったミニマックス法の計算手順の視覚化の検討と、視覚化に必要なデータの記録の実装を行いました。今回の記事では __Mbtree_Anim を修正してミニマックス法の視覚化__ を行います。

## 視覚化の検討のおさらい

前回の記事で行ったミニマックス法の視覚化と、フレームに記録するデータの検討結果は以下の通りです。

### 常に表示する内容

[常に表示する内容](https://qiita.com/ysgeso/items/ddcbd0cb9ab62656173e#%E5%B8%B8%E3%81%AB%E8%A1%A8%E7%A4%BA%E3%81%99%E3%82%8B%E5%86%85%E5%AE%B9)と、表示するために必要な情報は以下の表の通りです。なお、__`mbtree`__ は __`calc_score_for_anim` で計算した__ Mbtree_Anim で表示を行う __ゲーム木のデータ__、__`node`__ はアニメーションの __フレームで処理を行うノードのデータ__ を表すものとします。

|表示内容|必要な情報|
|:--|:--|
|__[設定された条件](https://qiita.com/ysgeso/items/ddcbd0cb9ab62656173e#%E8%A8%AD%E5%AE%9A%E3%81%95%E3%82%8C%E3%81%9F%E6%9D%A1%E4%BB%B6%E3%81%AE%E8%A1%A8%E7%A4%BA)の表示__|`mbtree.minimax`<br>`mbtree.use_tt`<br>`mbtree.shortest_victory`<br>`mbtree.init_ab`|
|__数直線__|`mbtree.shortest_victory`|
|__深さ__|`node.mb.move_count`|
|__ノードの種類__|`node.mb.move_count`|
|__フレームの状態__|フレームの状態を表す文字列|
|__枝狩りが行われた数などの情報__|計算を行ったノードの数と枝狩りを行ったノードの数|

ただし、数直線の表示範囲は負の無限大と正の無限大の間に、以下の __評価値の最小値と最大値の範囲__ を表示することにします。

* 最短の勝利を目指さない場合は -1 ~ 1 の範囲
* 最短の勝利を目指す場合は -2 ~ 3 の範囲

また、前回の記事では記述し忘れましたが、__フレームの状態の表示__ では「処理の開始」のような文字列だけではなく、これまでの Mbtree_Anim と同様に __状態の種類によって背景色を変える__ ことにします。

### フレームの状態ごとに表示する内容

それぞれの [フレームの状態ごとに表示する内容](https://qiita.com/ysgeso/items/ddcbd0cb9ab62656173e#%E3%83%95%E3%83%AC%E3%83%BC%E3%83%A0%E3%81%AE%E7%8A%B6%E6%85%8B%E3%81%94%E3%81%A8%E3%81%AB%E8%A1%A8%E7%A4%BA%E3%81%99%E3%82%8B%E5%86%85%E5%AE%B9)は以下の通りです。

* __"start"__ の状態
なし
* __"tt"__ の状態
    * 置換表にノードの評価値が登録されている場合
        * 赤字で「置換表に登録済」を表示
        * 数直線上に置換表に登録されていた評価値を表示
    * 登録されていない場合
        * 黒字で「置換表に未登録」を表示
* __"score"__ の状態
    * 数直線上にそのフレームでのノードの評価値を表示
    * 数直線上に子ノードの評価値を表示
* __"update"__ の状態
    * 数直線上にそのフレームでのノードの評価値を表示
    * ノードの評価値が更新された場合
        * 赤字で「評価値の更新」を表示
    * 更新されていない場合  
        * 黒字で「評価値の更新なし」を表示
* __"end"__ の状態
    * 数直線上にそのフレームでのノードの評価値を表示
    * 置換表を利用する場合
        * 置換表にノードの評価値が登録されている場合
            * 黒字で「置換表に登録されていたデータを利用」を表示
        * 登録されていない場合
            * 赤字で「置換表への登録」を表示

それぞれの状態で __必要となるデータを表にまとめる__ と以下のようになります。データが必要な場合は 〇 を表記しました。

|データ|"start"|"tt"|"score"|"update"|"end"|
|:--|:-:|:-:|:-:|:-:|:-:|
|置換表に登録されているかどうか|×|〇|×|×|〇|
|置換表に登録されていた評価値|×|〇|×|×|×|×|
|そのフレームでのノードの評価値|×|×|〇|〇|〇|
|子ノードの評価値|×|×|〇|×|×|
|ノードの評価値が更新されたかどうか|×|×|×|〇|×|

### フレームに記録するデータ

`calc_score_for_anim` が記録する __[フレームのデータを表す dict の内容](https://qiita.com/ysgeso/items/ddcbd0cb9ab62656173e#calc_score_for_anim-%E3%81%AE%E4%BF%AE%E6%AD%A3)__ は以下の通りです。

|キー|キーの値の意味|
|:--|:--|
|__"status"__|フレームの状態を表す文字列|
|__"num_calculated"__|計算されたノードの数|
|__"num_pruned"__|枝狩りされたノードの数|
|__"registered_in_tt"__|置換表にノードの評価値が登録されていたかどうか|
|__"lower_bound"__|ミニマックス値の下界|
|__"upper_bound"__|ミニマックス値の上界|
|__"score"__|そのフレームでのノードの評価値|
|__"childscore"__|子ノードの評価値|
|__"updated"__|ノードの評価値が更新されたかどうか|

上記で検討したように表示を行うために、Mbtree_Anim をどのように修正すれば良いかについて少し考えてみて下さい。

# `calc_score_by_ab` と `calc_score_for_anim` への両対応

[前回の記事](https://qiita.com/ysgeso/items/ddcbd0cb9ab62656173e#%E5%AE%9F%E8%A3%85%E6%96%B9%E6%B3%95%E3%81%AE%E9%81%B8%E6%8A%9E)では、__互換性を重視__ して Mbtree_Anim が __`calc_score_by_ab`__ と __`calc_score_for_anim`__ の __両方__ のメソッドで作成されたデータに __対応する__ ように修正することにしました。また、__どちらのメソッドで作成された__ データであるかを判定するために、__[`calculated_by_calc_score_for_anim`](https://qiita.com/ysgeso/items/ddcbd0cb9ab62656173e#calc_score_for_anim-%E3%81%AE%E4%BF%AE%E6%AD%A3)という名前の属性を導入__ しました。

そこで、__`calc_score_by_ab`__ で作成されたデータに対しては __従来通りの方法で視覚化__ を行い、__`calc_score_for_anim`__ で作成されたデータに対しては __前回の記事で検討した方法で視覚化__ を行うように Mbtree_Anim を修正する必要があります。

## `__init__` メソッドの修正

[前回の記事](https://qiita.com/ysgeso/items/ddcbd0cb9ab62656173e#calc_score_by_ab-%E3%81%AE%E4%BF%AE%E6%AD%A3) のノートで言及したように __"../data/abtree_root.mbtree"__ などの、[前回の記事で修正](https://qiita.com/ysgeso/items/ddcbd0cb9ab62656173e#calc_score_by_ab-%E3%81%AE%E4%BF%AE%E6%AD%A3)する前の __`calc_score_by_ab` で作成__ したゲーム木のデータには __`calculated_by_calc_score_for_anim` 属性は存在していません__。従って、__それらのデータにも対応__ できるようにするためには、下記のプログラムのようにゲーム木のデータの __`calculated_by_calc_score_for_anim` 属性が存在しない場合は `False` を代入する__ ように __`__init__` メソッドを修正する__ 必要があります。

* __5 行目__：組み込み関数 [`getattr`](https://qiita.com/ysgeso/items/3400391d2637eafc8899#%E7%B5%84%E3%81%BF%E8%BE%BC%E3%81%BF%E9%96%A2%E6%95%B0-getattr-%E3%82%92%E5%88%A9%E7%94%A8%E3%81%97%E3%81%9F%E4%BF%AE%E6%AD%A3) を利用して __`calculated_by_calc_score_for_anim`__ 属性が __`self.mbtree` に存在しない場合__ は、その属性に __`False` を代入する__ ように修正する

```python
 1  from tree import Mbtree_Anim
 2  
 3  def __init__(self, mbtree, isscore, size=0.15):
 4      self.mbtree = mbtree
 5      self.mbtree.calculated_by_calc_score_for_anim = \
 6                   getattr(self.mbtree, "calculated_by_calc_score_for_anim", False)
元と同じなので省略
 7      
 8  Mbtree_Anim.__init__ = __init__
```

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

```python
from tree import Mbtree_Anim

def __init__(self, mbtree, isscore, size=0.15):
    self.mbtree = mbtree
    self.mbtree.calculated_by_calc_score_for_anim = \
                getattr(self.mbtree, "calculated_by_calc_score_for_anim", False)
    self.isscore = isscore
    self.size = size
    self.width = 50
    self.height = 65
    self.nodelist = self.mbtree.nodelist_by_score if isscore else self.mbtree.nodelist 
    self.nodenum = len(self.nodelist)
    self.prev_frame = 0
    super(Mbtree_Anim, self).__init__()
    
Mbtree_Anim.__init__ = __init__
```
</details>

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

```diff_python
from tree import Mbtree_Anim

def __init__(self, mbtree, isscore, size=0.15):
    self.mbtree = mbtree
+   self.mbtree.calculated_by_calc_score_for_anim = \
+               getattr(self.mbtree, "calculated_by_calc_score_for_anim", False)
元と同じなので省略
    
Mbtree_Anim.__init__ = __init__
```
</details>

## `create_event_handler` メソッドの修正

`calc_score_by_ab` と `calc_score_for_anim` では、__`ablist_by_score` の要素__ に記録したフレームの情報の __データ型__ が __tuple と dict__ のように __異なっています__。そのため、__`ablist_by_score` に関する処理を修正__ する必要があります。

どこを修正すれば良いかについては、__Ctrl + F__ キーで呼びだせる __VSCode の検索機能__ を使って、__tree.py__ 内の __Mbtree_Anim の定義の中で記述__ されている __`ablist_by_score` の処理を検索__ すると良いでしょう。検索を行うと __`create_event_handler`__ 内で `ablist_by_score` の処理が行われていることがわかるので、`create_event_handler` を修正する必要があります。

:::note
`create_widgets` メソッド内でも下記のプログラムのように `ablist_by_score` の処理が記述されていますが、この処理は `self.mbtree` が `ablist_by_score` という属性を持っているかどうかを判定する処理を行っており、__`ablist_by_score` の内容に関する処理を行っていない__ ので __修正する必要はありません__。

```python
    if self.isscore and hasattr(self.mbtree, "ablist_by_score"):
```                
:::

また、`calc_score_by_ab` では、置換表にノードの評価値が登録されていた場合は `"tt"` のフレームしか記録しませんでしたが、[以前の記事](https://qiita.com/ysgeso/items/bcd2526d5327fd1e72e9#%E8%A8%98%E9%8C%B2%E3%81%99%E3%82%8B%E3%83%95%E3%83%AC%E3%83%BC%E3%83%A0%E3%81%AE%E7%8A%B6%E6%85%8B%E3%81%AE%E5%85%B1%E9%80%9A%E5%8C%96)で __`calc_score_for_anim`__ では __常に "start" と "end" のフレームを記録__ するように修正しました。その結果、__"tt" のフレーム__ は __"start" の直後のフレームに記録__ されることになりますが、現状の Mbtree_Anim では __下段の < ボタンや > ボタン__ をクリックした際に、__"tt" のフレームが飛ばされてしまう__ ので、それらのボタンをクリックした際に __"tt" のフレームを飛ばさないように修正__ する必要があります。

下記は、そのように __`create_event_handler`__ を修正したプログラムです。

* __5 ~ 8、14 ~ 17 行目__：`calc_score_by_ab` ではフレームの状態は `ablist_by_score` に記録されたフレームの情報を表す tuple の 3 番の要素に代入されていたが、`calc_score_for_anim` では dict の `"status"` のキーの値に代入されているので、`calculated_by_calc_score_for_anim` 属性の値によってフレームの状態を正しく変数に代入できるように修正した
* __27 ~ 30、33 ~ 36 行目__：下段の < と > ボタンをクリックした際に、`calc_score_for_anim` で計算されたデータの場合は `"tt"` のフレームにも移動するように修正した。`chage_frame` の処理について忘れた方は、[以前の記事](https://qiita.com/ysgeso/items/741c2c18f0bc6fcdba27#%E3%83%95%E3%83%AC%E3%83%BC%E3%83%A0%E3%82%92%E6%8E%A2%E3%81%97%E3%81%A6%E7%A7%BB%E5%8B%95%E3%81%99%E3%82%8B%E9%96%A2%E6%95%B0%E3%81%AE%E5%AE%9A%E7%BE%A9)を復習すること

```python
 1  def create_event_handler(self):
元と同じなので省略
 2      def change_frame(edge_status, diff, status_list):
 3          frame = self.play.value
 4          selectednode = self.mbtree.nodelist_by_score[frame]
 5          if self.mbtree.calculated_by_calc_score_for_anim:
 6              selectedstatus = self.mbtree.ablist_by_score[frame]["status"]
 7          else:
 8              selectedstatus = self.mbtree.ablist_by_score[frame][3]
 9          if selectedstatus == edge_status:
10              return
11          while True:
12              frame += diff
13              node = self.mbtree.nodelist_by_score[frame]
14              if self.mbtree.calculated_by_calc_score_for_anim:
15                  status = self.mbtree.ablist_by_score[frame]["status"]
16              else:
17                  status = self.mbtree.ablist_by_score[frame][3]
18              if node == selectednode and status in status_list:
19                  break
20          self.play.value = frame
21          self.update_gui()
22              
23      def on_node_first_button_clicked(b=None):
24          change_frame("start", -1, ["start"])
25              
26      def on_node_prev_button_clicked(b=None):
27          if self.mbtree.calculated_by_calc_score_for_anim:
28              change_frame("start", -1, ["start", "score", "tt"])
29          else:
30              change_frame("start", -1, ["start", "score"])
31  
32      def on_node_next_button_clicked(b=None):
33          if self.mbtree.calculated_by_calc_score_for_anim:
34              change_frame("end", 1, ["end", "score", "tt"])
35          else:
36              change_frame("end", 1, ["end", "score"])
元と同じなので省略
37  
38  Mbtree_Anim.create_event_handler = create_event_handler
```

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

```python
def create_event_handler(self):
    def on_play_changed(changed):
        self.prev_frame = changed.old
        self.update_gui()
            
    def on_prev_button_clicked(b=None):
        self.play.value -= 1
        self.update_gui()
        
    def on_next_button_clicked(b=None):
        self.play.value += 1
        self.update_gui()

    self.prev_button.on_click(on_prev_button_clicked)
    self.next_button.on_click(on_next_button_clicked)

    self.play.observe(on_play_changed, names="value")
    
    def change_frame(edge_status, diff, status_list):
        frame = self.play.value
        selectednode = self.mbtree.nodelist_by_score[frame]
        if self.mbtree.calculated_by_calc_score_for_anim:
            selectedstatus = self.mbtree.ablist_by_score[frame]["status"]
        else:
            selectedstatus = self.mbtree.ablist_by_score[frame][3]
        if selectedstatus == edge_status:
            return
        while True:
            frame += diff
            node = self.mbtree.nodelist_by_score[frame]
            if self.mbtree.calculated_by_calc_score_for_anim:
                status = self.mbtree.ablist_by_score[frame]["status"]
            else:
                status = self.mbtree.ablist_by_score[frame][3]
            if node == selectednode and status in status_list:
                break
        self.play.value = frame
        self.update_gui()
            
    def on_node_first_button_clicked(b=None):
        change_frame("start", -1, ["start"])
            
    def on_node_prev_button_clicked(b=None):
        if self.mbtree.calculated_by_calc_score_for_anim:
            change_frame("start", -1, ["start", "score", "tt"])
        else:
            change_frame("start", -1, ["start", "score"])

    def on_node_next_button_clicked(b=None):
        if self.mbtree.calculated_by_calc_score_for_anim:
            change_frame("end", 1, ["end", "score", "tt"])
        else:
            change_frame("end", 1, ["end", "score"])
        
    def on_node_last_button_clicked(b=None):
        change_frame("end", 1, ["end"])
    
    if self.abfig is not None:
        self.node_first_button.on_click(on_node_first_button_clicked)
        self.node_prev_button.on_click(on_node_prev_button_clicked)
        self.node_next_button.on_click(on_node_next_button_clicked)
        self.node_last_button.on_click(on_node_last_button_clicked)

Mbtree_Anim.create_event_handler = create_event_handler
```
</details>

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

```diff_python
def create_event_handler(self):
元と同じなので省略
    def change_frame(edge_status, diff, status_list):
        frame = self.play.value
        selectednode = self.mbtree.nodelist_by_score[frame]
-       selectedstatus = self.mbtree.ablist_by_score[frame][3]
+       if self.mbtree.calculated_by_calc_score_for_anim:
+           selectedstatus = self.mbtree.ablist_by_score[frame]["status"]
+       else:
+           selectedstatus = self.mbtree.ablist_by_score[frame][3]
        if selectedstatus == edge_status:
            return
        while True:
            frame += diff
            node = self.mbtree.nodelist_by_score[frame]
-           status = self.mbtree.ablist_by_score[frame][3]
+           if self.mbtree.calculated_by_calc_score_for_anim:
+               status = self.mbtree.ablist_by_score[frame]["status"]
+           else:
+               status = self.mbtree.ablist_by_score[frame][3]
            if node == selectednode and status in status_list:
                break
        self.play.value = frame
        self.update_gui()
            
    def on_node_first_button_clicked(b=None):
        change_frame("start", -1, ["start"])
            
    def on_node_prev_button_clicked(b=None):
-       change_frame("start", -1, ["start", "score"])
+       if self.mbtree.calculated_by_calc_score_for_anim:
+           change_frame("start", -1, ["start", "score", "tt"])
+       else:
+           change_frame("start", -1, ["start", "score"])

    def on_node_next_button_clicked(b=None):
-       change_frame("end", 1, ["end", "score"])
+       if self.mbtree.calculated_by_calc_score_for_anim:
+           change_frame("end", 1, ["end", "score", "tt"])
+       else:
+           change_frame("end", 1, ["end", "score"])
元と同じなので省略

Mbtree_Anim.create_event_handler = create_event_handler
```
</details>

## `update_gui` メソッドの修正

`ablist_by_score` の処理は `updata_gui` メソッドでも行われているので修正を行います。

一つ目の修正は、下段の __<<、<、>、>> ボタンの状態__ に関する処理です。`calc_score_by_ab` で作成したデータでは、ノードの情報が置換表に記録されていた場合は "tt" のフレームしか存在しませんでした。そのため、`"tt"` のフレームが表示されている場合は 4 つすべてのボタンを押せないようにしていました。一方、__`calc_score_for_anim` で作成されたデータ__ の場合は、__"tt" のフレーム__ は __"start" と "end" のフレームの間に配置__ されるので、"tt" のフレームでは __4 つのボタンを全て押せる__ ように修正する必要があります。

もう一つの修正は、`calc_score_by_ab` で作成されたデータと `calc_score_for_anim` で作成されたデータでは、ゲーム木の上部の __フレームの情報の表示内容が全く異なる__ ので、__`calc_score_by_ab`__ で作成されたデータの場合は __これまで通り `update_ab` で表示__ を行い、__`calc_score_for_anim`__ で作成されたデータの場合は __`update_frameinfo`__ というこの後で定義する別のメソッドで __表示を行う__ ように修正する点です。なお、メソッドの名前はフレーム（frame）の情報（information）を表示する事からそのように命名しました。

下記は、そのように __`update_gui`__ を修正したプログラムです。

* __4、9 行目__：`calc_score_for_anim` で作成されたデータの場合は `update_frameinfo` を、そうでない場合はこれまで通り `update_ab` を呼び出すように修正した
* __5、10 行目__：先程と同様に `ablist_by_score` から状況に応じてフレームの状態のデータを正しく取得するように修正した
* __6、7、11 ~ 16 行目__：<< と < ボタンの状態を <code>disabled</code>、> と >> ボタンの状態を `disabled2` という変数で計算し、13 ~ 16 行目ではその変数を利用してそれぞれのボタンの状態を設定するように修正した。`calc_score_for_anim` で作成されたデータの場合は、"tt" の状態ですべてのボタンを押すことができるようにしている

```python
 1  def update_gui(self):
元と同じなので省略
 2      if self.abfig is not None:
 3          if self.mbtree.calculated_by_calc_score_for_anim:
 4              self.update_frameinfo()
 5              status = self.mbtree.ablist_by_score[self.play.value]["status"]
 6              disabled = status == "start"
 7              disabled2 = status == "end"
 8          else:
 9              self.update_ab()
10              status = self.mbtree.ablist_by_score[self.play.value][3]
11              disabled = status == "start" or status == "tt"
12              disabled2 = status == "end" or status == "tt"
13          self.set_button_status(self.node_first_button, disabled=disabled)
14          self.set_button_status(self.node_prev_button, disabled=disabled)
15          self.set_button_status(self.node_next_button, disabled=disabled2)
16          self.set_button_status(self.node_last_button, disabled=disabled2)
元と同じなので省略
17      
18  Mbtree_Anim.update_gui = update_gui
19  
```

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

```python
def update_gui(self):
    self.ax.clear()
    self.ax.set_xlim(-1, self.width - 1)
    self.ax.set_ylim(-1, self.height - 1)   
    self.ax.invert_yaxis()
    self.ax.axis("off")   
    
    self.selectednode = self.nodelist[self.play.value]
    self.centernode = self.selectednode
    if self.mbtree.algo == "bf":
        if self.centernode.depth > 0:
            self.centernode = self.centernode.parent
    while self.centernode.depth > 6:
        self.centernode = self.centernode.parent
    if self.centernode.depth <= 4:
        maxdepth = self.centernode.depth + 1
    elif self.centernode.depth == 5:
        maxdepth = 7
    else:
        maxdepth = 9
    self.mbtree.draw_subtree(centernode=self.centernode, selectednode=self.selectednode,
                            anim_frame=self.play.value, isscore=self.isscore, 
                            ax=self.ax, maxdepth=maxdepth, size=self.size)
    if self.abfig is not None:
        if self.mbtree.calculated_by_calc_score_for_anim:
            self.update_frameinfo()
            status = self.mbtree.ablist_by_score[self.play.value]["status"]
            disabled = status == "start"
            disabled2 = status == "end"
        else:
            self.update_ab()
            status = self.mbtree.ablist_by_score[self.play.value][3]
            disabled = status == "start" or status == "tt"
            disabled2 = status == "end" or status == "tt"
        self.set_button_status(self.node_first_button, disabled=disabled)
        self.set_button_status(self.node_prev_button, disabled=disabled)
        self.set_button_status(self.node_next_button, disabled=disabled2)
        self.set_button_status(self.node_last_button, disabled=disabled2)

    disabled = self.play.value == 0
    self.set_button_status(self.prev_button, disabled=disabled)
    disabled = self.play.value == self.nodenum - 1
    self.set_button_status(self.next_button, disabled=disabled)
    
Mbtree_Anim.update_gui = update_gui
```
</details>

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

```diff_python
def update_gui(self):
元と同じなので省略
    if self.abfig is not None:
-       self.update_ab()
-       status = self.mbtree.ablist_by_score[self.play.value][3]
+       if self.mbtree.calculated_by_calc_score_for_anim:
+           self.update_frameinfo()
+           status = self.mbtree.ablist_by_score[self.play.value]["status"]
+           disabled = status == "start"
+           disabled2 = status == "end"
+       else:
+           self.update_ab()
+           status = self.mbtree.ablist_by_score[self.play.value][3]
+           disabled = status == "start" or status == "tt"
+           disabled2 = status == "end" or status == "tt"
-       disabled = status == "start" or status == "tt"
        self.set_button_status(self.node_first_button, disabled=disabled)
        self.set_button_status(self.node_prev_button, disabled=disabled)
-       disabled2 = status == "end" or status == "tt"
-       self.set_button_status(self.node_next_button, disabled=disabled)
-       self.set_button_status(self.node_last_button, disabled=disabled)
+       self.set_button_status(self.node_next_button, disabled=disabled2)
+       self.set_button_status(self.node_last_button, disabled=disabled2)
元と同じなので省略
    
Mbtree_Anim.update_gui = update_gui
```
</details>

# `update_frameinfo` の定義

次に、__`calc_score_for_anim`__ で作成したデータの __フレームの情報を描画__ する __`update_frameinfo`__ メソッドを定義します。まず、常に表示する内容を実装し、その後でそれぞれの状態で表示する内容を実装します。

## 常に表示する内容の実装

まず、[先程説明](#常に表示する内容)した常に表示する内容の表示を実装します。それぞれの実装方法について少し考えてみて下さい。

### 数直線の描画

先程の表では「設定された条件の表示」を表の最初に表記しましたが、本記事では一番左に表示する __数直線の描画から実装__ することにします。

数直線には無限大と正の無限大の間に、以下の __評価値の最小値と最大値の範囲__ を表示する必要があります。どのようにプログラムを記述すれば良いかについて少し考えてみて下さい。

* 最短の勝利を目指さない場合は -1 ~ 1 の範囲
* 最短の勝利を目指す場合は -2 ~ 3 の範囲

下記はそのような数直線を描画する `update_frame_info` メソッドの定義です。

* __2 ~ 5 行目__：Axes の表示のクリア、表示範囲の設定、軸の表示の消去を行う。`update_ab` 内の処理と同じ
* __7、8 行目__：__負の無限大と正の無限大__ の __数直線上の座標__ をそれぞれ「評価値の最小値 - 1」、「評価値の最大値 + 1」 として計算する
* __10 ~ 19 行目__：数直線を描画する。この部分の処理について忘れた方は[以前の記事](https://qiita.com/ysgeso/items/1468226fe9a96ae6a55c#%E7%9B%AE%E7%9B%9B%E3%82%8A%E3%81%AE%E4%B8%8B%E3%81%AE%E6%95%B0%E5%80%A4%E3%81%AE%E6%8F%8F%E7%94%BB)を復習すること

```python
 1  def update_frameinfo(self):
 2      self.abax.clear()
 3      self.abax.set_xlim(-4, 23)
 4      self.abax.set_ylim(-1.5, 1.5)
 5      self.abax.axis("off")
 6  
 7      minus_inf = -3 if self.mbtree.shortest_victory else -2
 8      plus_inf = 4 if self.mbtree.shortest_victory else 2
 9  
10      # 数直線の描画    
11      self.abax.plot(range(minus_inf, plus_inf + 1), [0] * (plus_inf + 1 - minus_inf) , "|-k")
12      for num in range(minus_inf, plus_inf + 1):
13          if num == minus_inf:
14              numtext = "-∞"
15          elif num == plus_inf:
16              numtext = "∞"
17          else:
18              numtext = num
19          self.abax.text(num, -1, numtext, ha="center")        
20  
21  Mbtree_Anim.update_frameinfo = update_frameinfo
```

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

```python
def update_frameinfo(self):
    self.abax.clear()
    self.abax.set_xlim(-4, 23)
    self.abax.set_ylim(-1.5, 1.5)
    self.abax.axis("off")

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

    # 数直線の描画    
    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 = "-∞"
        elif num == plus_inf:
            numtext = "∞"
        else:
            numtext = num
        self.abax.text(num, -1, numtext, ha="center")        

Mbtree_Anim.update_frameinfo = update_frameinfo
```
</details>

上記の修正後に下記のプログラムで __`calc_score_by_ab`__ で計算されたゲーム木のデータを Mbtree_Anim で視覚化を行うと、実行結果のように __これまで通りの視覚化__ が行われることが確認できます。なお、ゲーム木の部分の表示は省略しました。余裕がある方は、他のフレームでもこれまで通りの表示が行われるかを確認してみて下さい。

```python
from tree import Mbtree

mbtree = Mbtree.load("../data/abtree_root")
Mbtree_Anim(mbtree, isscore=True)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/b91e6741-4e2f-433d-9b18-5dcb986a927f.png" width="500">

次に、下記のプログラムで __`calc_score_for_anim`__ で __ミニマックス法__ で __最短の勝利を目指さない__ という条件で計算を行った場合は、実行結果のように __数直線の範囲が正しく描画される__ ことが確認できます。

```python
mbtree.calc_score_for_anim(mbtree.root, minimax=True, shortest_victory=False)
Mbtree_Anim(mbtree, isscore=True)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/6b5a53b4-e5f1-4873-a7b8-6963529c7309.png" width="250">

下記のプログラムを実行すると、__最短の勝利を目指す場合__ も実行結果のように __数直線の範囲が正しく描画される__ ことが確認できます。

```python
mbtree.calc_score_for_anim(mbtree.root, minimax=True, shortest_victory=True)
Mbtree_Anim(mbtree, isscore=True)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/333feddb-1bae-415d-817d-3f730c918890.png" width="300">

### 設定された条件の表示

[前回の記事](https://qiita.com/ysgeso/items/ddcbd0cb9ab62656173e#%E8%A8%AD%E5%AE%9A%E3%81%95%E3%82%8C%E3%81%9F%E6%9D%A1%E4%BB%B6%E3%81%AE%E8%A1%A8%E7%A4%BA)で説明したように、__設定された条件__ は下記のように表示します。

「ミニマックス法　置換表 〇　最短 ×　初期値 〇」のように、「アルゴリズムの種類」、「置換表を利用しているかどうか」、「最短の勝利を目指す評価値を計算するかどうか」、「ルートノードの α 値と β 値を評価値の最小値と最大値で初期化するかどうか」を表す文字を表示する。ただし、ミニマックス法では α 値と β 値を利用しないので「初期値 〇」の部分は表示しない。

`update_ab` では真ん中の 1 行目に 「深さ 0 max node」という表示を行い、その下に α 値と β 値によって分けられる 3 つの評価値の範囲に関する表示を行っていましたが、__ミニマックス法__ では __α 値と β 値は利用しない__ ため __評価値の範囲に関する表示__ を __行う必要はありません__。そのため、__真ん中に表示するメッセージ__ は `update_ab` の場合と比べて __少なくて済みます__。そこで、__`update_frameinfo`__ では __真ん中の 1 行目__ に __設定された条件を表示__ することにします。また、`update_ab` では真ん中の 1 行目に表示する内容と、その下の行で表示する内容を別の処理で行なっていましたが、__`update_frameinfo`__ では __まとめて表示する__ といという工夫を行なうことにします。

下記は __`update_frameinfo`__ にその処理を記述したプログラムです。

* __5 行目__：真ん中に表示するメッセージの行数を変数に代入する。ミニマックス法では真ん中に最大で 4 行分表示することになるので 4 を設定した
* __6、7 行目__：真ん中に表示するメッセージの一覧とメッセージの色の一覧を表す変数を、すべての行が空文字と黒色を表すような list で初期化する
* __9 ~ 15 行目__：1 行目に表示する文字列を計算し、1 行目に表示する文字列を表す `textlist[0]` に代入する。その際に 13 ~ 15 行目の処理で αβ 法の場合のみ `init_ab` に対応する表示を行うようにしている
* __17、18 行目__：`linenum` の行の数だけメッセージを描画する。行なっている処理は、表示する座標を少し調整した点と、αβ 法での評価値の範囲の色を表す長方形を表示しない点以外は [`update_ab` で行なっている処理](https://qiita.com/ysgeso/items/fd421d90cd1f47e38cdb#%E5%A1%97%E3%82%8A%E3%81%A4%E3%81%B6%E3%81%97%E3%81%AE%E8%89%B2%E3%81%AE%E8%AA%AC%E6%98%8E%E3%81%AE%E6%8F%8F%E7%94%BB) とほぼ同じである

```python
 1  def update_frameinfo(self):
 2      数直線の描画処理
 3  
 4      # メッセージの表示
 5      linenum = 4
 6      textlist = [""] * linenum
 7      textcolorlist = ["black"] * linenum
 8  
 9      algorithm = "mm 法" if self.mbtree.minimax else "αβ法"
10      use_tt = "〇" if self.mbtree.use_tt else "×"
11      shortest_victory = "〇" if self.mbtree.shortest_victory else "×"
12      init_ab = "〇" if self.mbtree.init_ab else "×"
13      textlist[0] = f"{algorithm}　置換表 {use_tt}　最短 {shortest_victory}"
14      if not self.mbtree.minimax:
15          textlist[0] += f"　初期値 {init_ab}"
16  
17      for i in range(linenum):
18          self.abax.text(5, 1 - i * 0.7, textlist[i], c=textcolorlist[i])
19  
20  Mbtree_Anim.update_frameinfo = update_frameinfo
```

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

```python
def update_frameinfo(self):
    self.abax.clear()
    self.abax.set_xlim(-4, 23)
    self.abax.set_ylim(-1.5, 1.5)
    self.abax.axis("off")

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

    # 数直線の描画    
    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 = "-∞"
        elif num == plus_inf:
            numtext = "∞"
        else:
            numtext = num
        self.abax.text(num, -1, numtext, ha="center")        

    # メッセージの表示
    linenum = 4
    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}"

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

Mbtree_Anim.update_frameinfo = update_frameinfo
```
</details>


上記を実行後に __いくつかの設定でプログラムを実行__ することで、設定した条件の部分が正しく表示されることを確認しすることにします。

下記は __ミニマックス法__、__置換表を利用しない__、__最短の勝利を目指す__ 場合で、実行結果のように __意図通りの表示が行われている__ ことが確認できます。なお、ミニマックス法では `init_ab` に対する表示は行いません。

```python
mbtree.calc_score_for_anim(mbtree.root, minimax=True, shortest_victory=True, init_ab=True)
Mbtree_Anim(mbtree, isscore=True)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/dedbb739-49cc-4125-80ce-1c583e441d56.png" width="350">

下記は __αβ 法__、__置換表を利用する__、__最短の勝利を目指さない__、__α 値と β 値の初期値を設定する__ 場合で、実行結果のように __意図通りの表示が行われている__ ことが確認できます。なお、αβ 法なので `init_ab` に対する表示が行なわれています。

```python
mbtree.calc_score_for_anim(mbtree.root, minimax=False, use_tt=True,
                           shortest_victory=False, init_ab=True)
Mbtree_Anim(mbtree, isscore=True)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/57cca037-e3fc-48bf-bf9e-ae7d0b4379c9.png" width="400">

### 深さ、ノードの種類、フレームの状態の表示

次に、`update_ab` で表示していた「深さ 0 max node 処理の開始」の表示と、フレームの状態に応じた背景色の表示処理を実装します。ただし、「__深さとノードの種類の表示__」と「__フレームの状態の表示__」を __同じ行に表示するのがわかりにくい__ 気がした点と、ミニマックス法では表示する内容が少ない点から、__それらを別の行で表示__ することにします。

下記は __`update_frameinfo`__ にその処理を記述したプログラムです。処理の多くは `update_ab` と似ていますが、フレームの状態を表す文字列と背景色を表すデータを dict に代入して利用するなど、いくつかの点でプログラムの工夫を行ないました。

* __3 行目__：このフレームの情報を表す dict を `framedata` に代入する
* __4 行目__：このフレームの状態を `status` に代入する
* __5 行目__：max ノードであるかどうかを計算して `maxnode` に代入する
* __11、12 行目__：深さとノードの種類を表す文字列を計算して 1 行目の文字列を表す `textlist[1]` に代入する
* __14 ~ 35 行目__：それぞれのフレームの状態の文字列と背景色を表す dict を変数に代入する
* __36 ~ 38 行目__：上記の dict からフレームの状態を表す文字列と背景色を `textlist[2]` と `facecolor` に代入する。38 行目で [`set_facecolor`](https://qiita.com/ysgeso/items/0335cc42055729d0fac3#axes-%E3%81%AE%E9%85%8D%E7%BD%AE%E4%BD%8D%E7%BD%AE%E3%81%AE%E7%A2%BA%E8%AA%8D) メソッドを利用して背景色を変更する

```python
 1  from marubatsu import Marubatsu
 2
 3  def update_frameinfo(self):
 4      framedata = self.mbtree.ablist_by_score[self.play.value]
 5      status = framedata["status"]
 6      maxnode = self.selectednode.mb.turn == Marubatsu.CIRCLE
 7      
 8      数直線の描画処理
 9      1 行目の表示処理
10      
11      nodetype = "max node" if maxnode else "min node"
12      textlist[1] = f"深さ {self.selectednode.mb.move_count} {nodetype}"
13
14      statusdata = {
15          "start": {
16              "text": "処理の開始",
17              "color": "white"
18          },
19          "tt": {
20              "text": "置換表の処理",
21              "color": "honeydew"
22          },
23          "score": {
24              "text": "子ノードの評価値",
25              "color": "lightyellow"
26          },
27          "update": {
28              "text": "更新処理",
29              "color": "lightcyan"
30          },
31          "end": {
32              "text": "評価値の確定",
33              "color": "lavenderblush"
34          },
35      }
36      textlist[2] = statusdata[status]["text"]
37      facecolor = statusdata[status]["color"]
38  
39      self.abfig.set_facecolor(facecolor)
40      for i in range(linenum):
41          self.abax.text(5, 1 - i * 0.7, textlist[i], c=textcolorlist[i])
42  
43  Mbtree_Anim.update_frameinfo = update_frameinfo
```

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

```python
from marubatsu import Marubatsu

def update_frameinfo(self):
    framedata = self.mbtree.ablist_by_score[self.play.value]
    status = framedata["status"]
    maxnode = self.selectednode.mb.turn == Marubatsu.CIRCLE

    self.abax.clear()
    self.abax.set_xlim(-4, 23)
    self.abax.set_ylim(-1.5, 1.5)
    self.abax.axis("off")

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

    # 数直線の描画    
    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 = "-∞"
        elif num == plus_inf:
            numtext = "∞"
        else:
            numtext = num
        self.abax.text(num, -1, numtext, ha="center")        

    # メッセージの表示
    linenum = 4
    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}"

    nodetype = "max node" if maxnode else "min node"
    textlist[1] = f"深さ {self.selectednode.mb.move_count} {nodetype}"
    
    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"]

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

Mbtree_Anim.update_frameinfo = update_frameinfo
```
</details>

上記の修正後に下記のプログラムを実行し、__ミニマックス法__、__置換表を利用しない__、__最短の勝利を目指さない__ 場合で __様々なフレームの表示を行う__ ことで __正しく実装できたかどうかを確認__ することにします。

```python
mbtree.calc_score_for_anim(mbtree.root, minimax=True, use_tt=True, shortest_victory=False)
Mbtree_Anim(mbtree, isscore=True)
```

下記は __"start"__ の状態の最初のフレームの表示で、「__深さ 0 max node__」と「__処理の開始__」が __背景色が白色__ で __正しく表示__ されています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/3eec7d02-79e0-4322-8a23-4a55cee33ad7.png" width="350">

下記は __上段の >__ をクリックした __"tt"__ の状態の  1 フレーム目の表示で、__置換表を利用する場合__ は __"start" の次のフレームが必ず "tt" のフレームになる__ ので、「深さ 0 max node」と「__置換表の処理__」が __背景色が薄緑色__ で正しく表示されています。なお、先程の [`create_event_handler` の修正](#create_event_handler-メソッドの修正)によって、"start" の状態のフレームで下段の > をクリックした場合でも "tt" のフレームになります。そのことはこの後で検証します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/d97c9898-4856-4aa7-a0bd-7bb878f32891.png" width="350">

下記は __上段の >__ をクリックした __"start"__ の状態の  2 フレーム目の表示で、__深さ 1 のノード__ の評価値の計算が開始されるので「__深さ 1 min node__」と「__処理の開始__」が __背景色が白色__ で正しく表示されています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/0d94cff5-2e3c-4559-8eef-851057245636.png" width="350">

下記は __下段の >__ をクリックした __"tt"__ の状態の  3 フレーム目の表示で、「深さ 1 min node」と「置換表の処理」が背景色が薄緑色で正しく表示されています。先ほど説明したように、__"start"__ のフレームで __下段の >__ をクリックした場合でも __"tt" のフレームが表示される__ ことが確認できました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/55d3260c-bf68-42a9-a2ad-09e67cf0d30e.png" width="350">

下記は __下段の >__ をクリックした __"score"__ の状態の  5061 フレーム目の表示で、「深さ 1 min node」と「__子ノードの評価値__」が __背景色が薄黄色__ で正しく表示されています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/b7164b0d-7390-4963-9a73-9996799cc862.png" width="350">

下記は __上段の >__ をクリックした __"update"__ の状態の  5062 フレーム目の表示で、「深さ 1 min node」と「__更新処理__」が __背景色が水色__ で正しく表示されています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/9e9885f1-368c-4a94-8759-ffcbd7983762.png" width="350">

下記は __下段の >>__ をクリックした __"end"__ の状態の 5061 フレーム目の表示で、「深さ 1 min node」と「__評価値の確定__」が __背景色が薄赤色__ で正しく表示されています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/613931c6-183a-4f05-874b-dd41edae96b4.png" width="350">

興味がある方は、他の状態でも正しく表示されることを確認してみて下さい。

### 枝狩りが行われた数などの情報の表示

__枝狩りが行われた数などの情報__ の表示方法は __これまでと同じで良い__ ので、__[`update_ab` の対応する部分を参考](https://qiita.com/ysgeso/items/f8fe755341b3cff2d770#mbtree_anim-%E3%82%AF%E3%83%A9%E3%82%B9%E3%81%AE-update_ab-%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%AE%E4%BF%AE%E6%AD%A3)__ に、下記のプログラムのように記述することができます。なお、下記の __5、6、9 ~ 11 行以外は `update_ab` と全く同じ__ です。

* __5、6 行目__：このフレームの情報を表す dict から `num_calculated` と `num_pruned` の情報を取り出す。`update_ab` ではブロックの先頭でこの処理を行っていたが、これらの情報はこの行以前では利用しないのでここに移動した
* __9 ~ 11 行目__：直前に表示されていたフレームの情報を表す dict から `prev_num_calculated` と `prev_num_pruned` の情報を取り出す

```python
 1  def update_frameinfo(self):
 2      数直線の描画処理
 3      1 ~ 3 行目の表示処理
 4  
 5      num_calculated = framedata["num_calculated"]
 6      num_pruned = framedata["num_pruned"]
 7      num_total = num_calculated + num_pruned
 8      num_ratio = num_calculated / num_total if num_total != 0 else 0
 9      prev_framedata = self.mbtree.ablist_by_score[self.prev_frame]
10      prev_num_calculated = prev_framedata["num_calculated"]
11      prev_num_pruned = prev_framedata["num_pruned"]
12      prev_num_total = prev_num_calculated + prev_num_pruned
13      diff_num_calculated = num_calculated - prev_num_calculated
14      diff_num_pruned = num_pruned - prev_num_pruned
15      diff_num_total = num_total - prev_num_total
16      diff_num_ratio = diff_num_calculated / diff_num_total if diff_num_total != 0 else 0
17  
18      textlist = [ "計算済", "枝狩り", "合計", "割合" ]
19      datalist = [ num_calculated, num_pruned, num_total, f"{num_ratio * 100:.1f}%"]
20      diff_datalist = [ f"{diff_num_calculated:+d}", f"{diff_num_pruned:+d}", 
21                      f"{diff_num_total:+d}", f"{diff_num_ratio * 100:.1f}%"]
22      for i in range(4):
23          self.abax.text(15, 1 - i * 0.7, textlist[i])
24          self.abax.text(19.5, 1 - i * 0.7, datalist[i], ha="right")
25          self.abax.text(22.5, 1 - i * 0.7, diff_datalist[i], ha="right")
26          
27  Mbtree_Anim.update_frameinfo = update_frameinfo
```

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

```python
def update_frameinfo(self):
    framedata = self.mbtree.ablist_by_score[self.play.value]
    status = framedata["status"]
    maxnode = self.selectednode.mb.turn == Marubatsu.CIRCLE

    self.abax.clear()
    self.abax.set_xlim(-4, 23)
    self.abax.set_ylim(-1.5, 1.5)
    self.abax.axis("off")

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

    # 数直線の描画    
    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 = "-∞"
        elif num == plus_inf:
            numtext = "∞"
        else:
            numtext = num
        self.abax.text(num, -1, numtext, ha="center")        

    # メッセージの表示
    linenum = 4
    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}"

    nodetype = "max node" if maxnode else "min node"
    textlist[1] = f"深さ {self.selectednode.mb.move_count} {nodetype}"
    
    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"]

    self.abfig.set_facecolor(facecolor)
    for i in range(linenum):
        self.abax.text(5, 1 - 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, 1 - i * 0.7, textlist[i])
        self.abax.text(19.5, 1 - i * 0.7, datalist[i], ha="right")
        self.abax.text(22.5, 1 - i * 0.7, diff_datalist[i], ha="right")
        
Mbtree_Anim.update_frameinfo = update_frameinfo
```
</details>

上記の修正後に下記のプログラムを実行し、__>> ボタンをクリック__ すると実行結果のように __置換表付きミニマックス法__ で __ルートノードの評価値を計算__ した際に __計算したノードの数__ が[以前の記事](https://qiita.com/ysgeso/items/b90305993d682ff16793#%E5%90%8C%E4%B8%80%E5%B1%80%E9%9D%A2%E3%82%92%E8%80%83%E6%85%AE%E3%81%97%E3%81%9F%E7%BD%AE%E6%8F%9B%E8%A1%A8%E3%81%AB%E3%82%88%E3%82%8B%E6%9E%9D%E7%8B%A9%E3%82%8A)で計算した結果と同じ __2271 と正しく表示される__ ことが確認できます。

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

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/d907977d-bad2-4413-98ae-ac3d0263fa50.png" width="500">

## それぞれの状態での表示

次に、先程示した下記のそれぞれの状態の表示を実装します。

* __"start"__ の状態
なし
* __"tt"__ の状態
    * 置換表にノードの評価値が登録されている場合
        * 赤字で「置換表に登録済」を表示
        * 数直線上に置換表に登録されていた評価値を表示
    * 登録されていない場合
        * 黒字で「置換表に未登録」を表示
* __"score"__ の状態
    * 数直線上にそのフレームでのノードの評価値を表示
    * 数直線上に子ノードの評価値を表示
* __"update"__ の状態
    * 数直線上にそのフレームでのノードの評価値を表示
    * ノードの評価値が更新された場合
        * 赤字で「評価値の更新」を表示
    * 更新されていない場合  
        * 黒字で「評価値の更新なし」を表示
* __"end"__ の状態
    * 数直線上にそのフレームでのノードの評価値を表示
    * 置換表を利用する場合
        * 置換表にノードの評価値が登録されている場合
            * 黒字で「置換表に登録されていたデータを利用」を表示
        * 登録されていない場合
            * 赤字で「置換表への登録」を表示

`calc_score_for_anim` が記録する [フレームのデータを表す dict の内容]()は以下の通りです。

|キー|キーの値の意味|
|:--|:--|
|__"status"__|フレームの状態を表す文字列|
|__"num_calculated"__|計算されたノードの数|
|__"num_pruned"__|枝狩りされたノードの数|
|__"registered_in_tt"__|置換表にノードの評価値が登録されていたかどうか|
|__"lower_bound"__|ミニマックス値の下界|
|__"upper_bound"__|ミニマックス値の上界|
|__"score"__|そのフレームでのノードの評価値|
|__"childscore"__|子ノードの評価値|
|__"updated"__|ノードの評価値が更新されたかどうか|

### `update_frameinfo` の修正

上記の表示内容をよく見ると、「数直線上にそのフレームでのノードの評価値を表示」などのように、__複数の状態で共通する表示がある__ 事がわかります。このような場合に、__状態の種類ごとに表示処理を実装すると__、__同じ表示処理を複数回記述する必要が生じる__ という問題が発生します。そこで、__表示するデータの種類ごとに表示処理を実装する__ ことにします。

なお、__評価値を数直線上に表示__ する際に、__評価値が負の無限大__ または __正の無限大__ の場合は、その __座標を計算する必要__ があります。そこで、__評価値を数直線上に描画する際の座標を計算__ する以下のような __ローカル関数を定義__ するという工夫を行ないました。

__名前__：評価値から座標（coordinate）を計算（calculate）するので __`calc_coord`__ と命名する
__処理__：評価値を数直線上に描画する際の座標を計算する
__入力__：評価値を仮引数 `score` に代入する
__出力__：計算した座標を返り値として返す

下記はそのように __`update_frameinfo`__ を修正したプログラムです。

* __2 ~ 3 行目__：ローカル関数 `calc_coord` を定義する。行う処理は組み込み関数 `min` と `max` を利用して、評価値が負の無限大の座標以下になった場合は負の無限大の座標に、正の無限大の座標以上になった場合は正の無限大の座標を計算している
* __8 行目__：注釈を表示する Axes の `annotate` メソッドで表示する矢印の形状を表すデータを変数に代入する。この処理は `update_ab` 内の処理と同じである。注釈の描画方法について忘れた方は[以前の記事](https://qiita.com/ysgeso/items/1468226fe9a96ae6a55c#%E6%B3%A8%E9%87%88%E3%81%AE%E6%8F%8F%E7%94%BB)を復習すること
* __9 ~ 11 行目__：数直線の上部に表示する注釈の文字列の左端、右端、中央の x 座標を変数に代入する。左端と右端の座標としては、最短の勝利を目指す場合の負の無限大と正の無限大の座標である -3 と 4 を設定した。その理由については下記のノートを参照すること
* __13 ~ 20 行目__：そのフレームでのノードの評価値を表示する処理を行う
    * __13 行目__：そのフレームでのノードの評価値は "score" と "update" と "end" の状態で表示するのでその判定を行う
    * __14、15 行目__：評価値をフレームの情報から取り出して、その評価値を数直線上に表示する際の座標を計算する
    * __16、17 行目__：評価値の __注釈を表示する座標__ を計算する。__max ノード__ ではノードの __評価値が左から右に増えていく__ ので __左端に__、__min ノード__ ではノードの評価値が __右から左に減っていく__ ので __右端に表示する__ ように工夫した
    * __18 行目__：ノードの評価値を黒色の丸で数直線上に表示する
    * __19、20__ 行目：__「score = 評価値」という注釈__ を上部に表示する
* __22 ~ 29 行目__："score" の状態のフレームで、子ノードの評価値を表示する処理を行う。行う処理は 13 ~ 20 行目とほぼ同じだが、子ノードの評価値は緑色の丸で表示し、__「cscore = 評価値」という注釈__ をそのフレームでのノードの評価値の注釈の __逆側に表示する__ ように工夫した。なお、cscore は child score の略である
* __31 ~ 41 行目__："tt" の状態のフレームで、置換表にデータが登録されていた場合は赤字で真ん中の 4 行目に「置換表に登録済」と表示するようにし、置換表に登録されていた評価値を数直線上に紫色の丸で表示し、注釈を中央に表示するようにした。[前回の記事](https://qiita.com/ysgeso/items/ddcbd0cb9ab62656173e#calc_score_for_anim-%E3%81%AE%E4%BF%AE%E6%AD%A3)で説明したように、__ミニマック法__ の場合は __置換表に登録されている下界（上界でも良い）が評価値__ となる。登録されていない場合は黒字で「置換表に未登録」と表示するようにする
* __43 ~ 48 行目__："update" の状態のフレームで、ノードの評価値が更新されていた場合は赤字で「評価値の更新」、更新されていない場合は黒字で「評価値の更新なし」と表示するようにする
* __50 ~ 56 行目__："end" の状態のフレームで以下の処理を行う
    * 置換表を利用する場合
        * 置換表にデータが登録されていない場合は赤字で「置換表への登録」を表示する
        * 登録されている場合は黒字で「置換表に登録されていたデータを利用」を表示する
    * 置換表を利用しない場合は何も表示しない 

:::note
注釈の左端の座標を負の無限大の座標である `minus_inf`、右端の座標を正の無限大の座標である `plus_inf` に設定してしまうと、__最短の勝利を目指さない場合__ は左端の座標が -2、右端の座標が 2 となってしまうため、左端と右端にメッセージを表示すると __間が狭すぎて表示が重なってしまう__ ことになります。そのため注釈の左端と右端の座標は常に -3 と 4 に設定しました。
:::

```python
 1  def update_frameinfo(self):
 2      def calc_coord(score):
 3          return min(max(minus_inf, score), plus_inf)
 4          
 5      数直線の描画処理
 6      1 ~ 3 行目の表示処理
 7  
 8      arrowprops = { "arrowstyle": "->"}
 9      leftx = -3
10      rightx = 4
11      centerx = (leftx + rightx) / 2
12      # そのフレームでのノードの評価値の表示
13      if status in ["score", "update", "end"]:
14          score = framedata["score"]
15          score_coord = calc_coord(score)
16          text_coord = leftx if maxnode else rightx
17          ha = "left" if maxnode else "right"
18          self.abax.plot(score_coord, 0, "ok")
19          self.abax.annotate(f"score = {score}", xy=(score_coord, 0),
20                             xytext=(text_coord, 1), arrowprops=arrowprops, ha=ha)        
21      # 子ノードの評価値の表示
22      if status == "score":
23          childscore = framedata["childscore"]
24          childscore_coord = calc_coord(childscore)
25          text_coord = rightx if maxnode else leftx
26          ha = "right" if maxnode else "left"
27          self.abax.plot(childscore_coord, 0, "og")
28          self.abax.annotate(f"cscore = {childscore}", xy=(childscore_coord, 0),
29                             xytext=(text_coord, 1), arrowprops=arrowprops, ha=ha)        
30      # 置換表にデータが登録されていたかどうかの表示
31      elif status =="tt":
32          if framedata["registered_in_tt"]:
33              textlist[3] = "置換表に登録済"
34              textcolorlist[3] = "red"
35              score = framedata["lower_bound"]
36              score_coord = calc_coord(score)
37              self.abax.plot(score_coord, 0, "om")
38              self.abax.annotate(f"置換表の評価値 = {score}", xy=(score_coord, 0),
39                              xytext=(centerx, 1), arrowprops=arrowprops, ha="center")        
40          else:
41              textlist[3] = "置換表に未登録"
42      # ノードの評価値が更新されたかどうかの表示
43      elif status == "update":
44          if framedata["updated"]:
45              textlist[3] = "評価値の更新"
46              textcolorlist[3] = "red"
47          else:
48              textlist[3] = "評価値の更新なし"
49      # 置換表に登録したかどうかの表示
50      elif status == "end":
51          if self.mbtree.use_tt:
52              if framedata["registered_in_tt"]:
53                  textlist[3] = "置換表に登録されていたデータを利用"
54              else:
55                  textlist[3] = "置換表への登録"
56                  textcolorlist[3] = "red"
57  
58      self.abfig.set_facecolor(facecolor)
59      for i in range(linenum):
60          self.abax.text(5, 1 - i * 0.7, textlist[i], c=textcolorlist[i])
61
62      枝狩りが行われた数などの情報の表示
63  
64  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

    self.abax.clear()
    self.abax.set_xlim(-4, 23)
    self.abax.set_ylim(-1.5, 1.5)
    self.abax.axis("off")

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

    # 数直線の描画    
    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 = "-∞"
        elif num == plus_inf:
            numtext = "∞"
        else:
            numtext = num
        self.abax.text(num, -1, numtext, ha="center")        

    # メッセージの表示
    linenum = 4
    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 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"
            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:
            textlist[3] = "置換表に未登録"
    # ノードの評価値が更新されたかどうかの表示
    elif status == "update":
        if framedata["updated"]:
            textlist[3] = "評価値の更新"
            textcolorlist[3] = "red"
        else:
            textlist[3] = "評価値の更新なし"
    # 置換表に登録したかどうかの表示
    elif status == "end":
        if self.mbtree.use_tt:
            if framedata["registered_in_tt"]:
                textlist[3] = "置換表に登録されていたデータを利用"
            else:
                textlist[3] = "置換表への登録"
                textcolorlist[3] = "red"

    self.abfig.set_facecolor(facecolor)
    for i in range(linenum):
        self.abax.text(5, 1 - 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, 1 - i * 0.7, textlist[i])
        self.abax.text(19.5, 1 - i * 0.7, datalist[i], ha="right")
        self.abax.text(22.5, 1 - i * 0.7, diff_datalist[i], ha="right")
        
Mbtree_Anim.update_frameinfo = update_frameinfo
```
</details>

## `create_event_handler` の修正

上記の修正後に下記のプログラムを実行して様々なフレームを表示した所、__以下の点が操作しづらい__ と思いました。

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

* __"score"__ のフレームで __下段の > ボタン__ をクリックすると、__"update" のフレームを飛ばして__ 次の子ノードの "score" のフレームに移動してしまう

__`calc_score_by_ab`__ で作成したデータを Mbtree_Anim で表示する __`update_ab`__ メソッドでは __"score"__ のフレームには真ん中に __次の "update" のフレームでどのような処理が行われるかが表示__ されていましたが、今回実装した __`update_frameinfo`__ メソッドでは __"score"__ のフレームには __次の "update" フレームで行われる処理が表示されない__ ので下段の < や > ボタンをクリックした際に、__"update" のフレームを飛ばさないほうがわかりやすい__ 気がしました。

そこで下記のプログラムの 4、10 行目のように __下段の < と > ボタン__ をクリックした際に __"update" のフレームを飛ばさない__ ように __`calc_event_handler`__ を修正することにします。

```python
 1  def create_event_handler(self):
元と同じなので省略
 2      def on_node_prev_button_clicked(b=None):
 3          if self.mbtree.calculated_by_calc_score_for_anim:
 4              change_frame("start", -1, ["start", "score", "update", "tt"])
 5          else:
 6              change_frame("start", -1, ["start", "score"])
 7  
 8      def on_node_next_button_clicked(b=None):
 9          if self.mbtree.calculated_by_calc_score_for_anim:
10              change_frame("end", 1, ["end", "score", "update", "tt"])
11          else:
12              change_frame("end", 1, ["end", "score"])
13  
14  Mbtree_Anim.create_event_handler = create_event_handler
```

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

```python
def create_event_handler(self):
    def on_play_changed(changed):
        self.prev_frame = changed.old
        self.update_gui()
            
    def on_prev_button_clicked(b=None):
        self.play.value -= 1
        self.update_gui()
        
    def on_next_button_clicked(b=None):
        self.play.value += 1
        self.update_gui()

    self.prev_button.on_click(on_prev_button_clicked)
    self.next_button.on_click(on_next_button_clicked)

    self.play.observe(on_play_changed, names="value")
    
    def change_frame(edge_status, diff, status_list):
        frame = self.play.value
        selectednode = self.mbtree.nodelist_by_score[frame]
        if self.mbtree.calculated_by_calc_score_for_anim:
            selectedstatus = self.mbtree.ablist_by_score[frame]["status"]
        else:
            selectedstatus = self.mbtree.ablist_by_score[frame][3]
        if selectedstatus == edge_status:
            return
        while True:
            frame += diff
            node = self.mbtree.nodelist_by_score[frame]
            if self.mbtree.calculated_by_calc_score_for_anim:
                status = self.mbtree.ablist_by_score[frame]["status"]
            else:
                status = self.mbtree.ablist_by_score[frame][3]
            if node == selectednode and status in status_list:
                break
        self.play.value = frame
        self.update_gui()
            
    def on_node_first_button_clicked(b=None):
        change_frame("start", -1, ["start"])
            
    def on_node_prev_button_clicked(b=None):
        if self.mbtree.calculated_by_calc_score_for_anim:
            change_frame("start", -1, ["start", "score", "update", "tt"])
        else:
            change_frame("start", -1, ["start", "score"])

    def on_node_next_button_clicked(b=None):
        if self.mbtree.calculated_by_calc_score_for_anim:
            change_frame("end", 1, ["end", "score", "update", "tt"])
        else:
            change_frame("end", 1, ["end", "score"])
        
    def on_node_last_button_clicked(b=None):
        change_frame("end", 1, ["end"])
    
    if self.abfig is not None:
        self.node_first_button.on_click(on_node_first_button_clicked)
        self.node_prev_button.on_click(on_node_prev_button_clicked)
        self.node_next_button.on_click(on_node_next_button_clicked)
        self.node_last_button.on_click(on_node_last_button_clicked)

Mbtree_Anim.create_event_handler = create_event_handler
```
</details>

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

```diff_python
def create_event_handler(self):
元と同じなので省略
    def on_node_prev_button_clicked(b=None):
        if self.mbtree.calculated_by_calc_score_for_anim:
-           change_frame("start", -1, ["start", "score", "tt"])
+           change_frame("start", -1, ["start", "score", "update", "tt"])
        else:
            change_frame("start", -1, ["start", "score"])

    def on_node_next_button_clicked(b=None):
        if self.mbtree.calculated_by_calc_score_for_anim:
-           change_frame("end", 1, ["end", "score", "tt"])
+           change_frame("end", 1, ["end", "score", "update", "tt"])
        else:
            change_frame("end", 1, ["end", "score"])

Mbtree_Anim.create_event_handler = create_event_handler
```
</details>

## 表示の確認

置換表を利用する場合と、利用しない場合で表示が正しく行われるかどうかを確認することにします。

### 置換表を利用する場合

下記のプログラムを実行し、__置換表を利用する場合__ に正しく表示されるかどうかを確認することにします。なお、先程確認した背景色と中央の 1 ~ 3 行目の表示の確認は省略します。

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

下記は __"start"__ の状態の最初のフレームの表示です。"start" の状態では共通する内容以外は特に何も表示しないので、先程と同じ表示が行われます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/3eec7d02-79e0-4322-8a23-4a55cee33ad7.png" width="350">

下記は __上段の >__ をクリックした __"tt"__ の状態の 1 フレーム目の表示です。__このノードの情報は置換表に登録されていない__ ので、3 行目に黒字で「__置換表に未登録__」が正しく表示されています。また、__置換表による枝狩りは行われない__ ので右の「__枝狩り__」には __+0 が正しく表示__ されています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/ed61bdf0-30e6-4a1a-a7ac-5f0489474288.png" width="500">

下記は __上段の >__ をクリックした __赤枠__ の __深さ 1 のノード__ の __"start"__ の状態の 2 フレーム目の表示で、先ほどと同様の表示が行われます。ノードの評価値の計算は行われないので右の「計算済」と「枝狩り」には 0 が正しく表示されます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/e8f48c7e-c282-4c48-a530-fec984d707a5.png" width="500">

次に、このノードのフレームの表示を検証することにします。

下記は __下段の >__ をクリックした __"tt"__ の状態の 3 フレーム目の表示です。__このノードの情報は置換表に登録されていない__ ので、3 行目に黒字で「__置換表に未登録__」が正しく表示されています。なお、ゲーム木の表示は上図と変わらないので省略します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/522870df-34f3-48ee-a7a7-3a760e07b26b.png" width="500">

下記は __下段の >__ をクリックした赤枠の最初の子ノードの評価値が計算された __"score"__ の状態の 5061 フレーム目の表示です。__min ノード__ では __ノードの評価値は正の無限大で初期化__ され、そのことが __数直線の右に score = inf__ のように正しく表示されています。また、赤枠の最初の __子ノードの評価値が 1 と計算__ されたことが数直線の __左上に cscore = 1__ のように正しく表示されています。右には子ノードの評価値を計算する際に計算したノードの数と枝狩りが行われたノードの数が表示されます。

なお、__次の "update" の状態__ のフレームで __ノードの評価値が更新されるか__ どうかは、下記のように __簡単に判別__ することができます。そのことは max ノードでも同様です。

* そのフレームでのノードの評価値の注釈と、子ノードの評価値の注釈の __2 つの矢印__ が下図のように __離れている場合__ は __更新される__
* 2 つの矢印が同じ評価値を指して __くっついているか__、__交差する場合__ は __更新されない__

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/399251f0-2762-419d-8a40-4fee8811c14a.png" width="500">

下記は __下段の >__ をクリックした __"update"__ の状態の 5062 フレーム目の表示です。__min ノード__ ではノードの評価値が、それまでのノードの評価値と子ノードの評価値の __最小値で更新__ され、そのことが数直線の所に __score = 1__ のように正しく表示されています。また、真ん中の 4 行目に __赤字で「評価値の更新」__ が表示されます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/dfef4f04-5a0a-4511-bfe7-ffedb0e1bd70.png" width="500">

下記は __下段の >__ をクリックした __"score"__ の状態と __もう一度 >__ をクリックした __"update"__ の 7551、7552 フレーム目の表示です。そのフレームでの評価値と子ノードの評価値は __どちらも 1__ なので 7552 フレーム目ではノードの評価値が __score = 1 のまま変化せず__、真ん中の 4 行目に「__評価値の更新なし__」と正しく表示されています。

なお、先程説明したように、"score" の状態のフレームでは __2 つの注釈の矢印がくっついている__ ことからノードの __評価値が更新されない__ ことが判別できます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/2bfeea41-0ec3-4c68-a639-4b38c31ccfd6.png" width="500">

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/8edd5fc8-7b23-466b-88dd-2a7ce0628b53.png" width="500">

下記は __下段の >>__ をクリックした赤枠のノードの __評価値が確定__ する __"end"__ の状態の 8750 フレーム目の表示です。ゲーム木の図からわかるように、__赤枠の子ノードの評価値の最小値は 0__ であり、このノードの評価値が __score = 0__ と __正しく表示__ されています。また、このノードの評価値は __置換表に登録されていなかった__ ので、真ん中の 4 行目に __赤字で「置換表への登録」__ が正しく表示されます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/5000682b-8ec7-4645-85f2-e1435ae7981a.png" width="500">

下図は下記の操作を行って、赤枠の __ルートノードの 3 番目の子ノード__ の評価値の __計算を開始__ する __"score"__ の状態の 10815 フレーム目の表示です。

* 上段の > ボタンをクリックしてルートノードの最初の子ノードの評価値が計算された "score" の状態のフレームに移動する
* 下段の > ボタンを 3 回クリックして、ルートノードの 2 つ目の子ノードの評価値が計算された "update" の状態のフレームに移動する
* 上段の > ボタンをクリックする

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/74107302-e604-4179-b5d1-72e9a5aede62.png" width="500">

上図の __赤枠のノードの局面__ は、__ルートノードの 1 番目の子ノードの局面__ を時計回りに __90 度回転させた局面__ なので、先程 __評価値を計算して置換表に登録した__ ルートノードの 1 番目の子ノードと __同一局面__ です。従って、__赤枠のノードの評価値は置換表に登録済__ です。

__下段の >__ をクリックした __"tt"__ の状態の下図の 10816 フレーム目の図では以下のように __置換表の処理が正しく行われたことが表示__ されています。

* 先程 8750 フレーム目で __置換表に登録した評価値が取り出された__ ことが数直線の上に「__置換表の評価値 = 0__」のように表示される
* 真ん中の 4 行目に __赤字で「置換表に登録済」__ が表示される
* ゲーム木の __赤枠の子孫ノード__ が、置換表による __枝狩りが行われた__ ことによって __暗く表示__ される
* 右の「__枝狩り__」の __+59704__ から、赤枠の 59704 個の子孫ノードが __置換表によって枝狩りが行われた__ ことが確認できる

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/bd8e4c4b-9988-4f6f-8178-095581c9ac3f.png" width="500">

下記は __下段の >__ をクリックした __"end"__ の状態の 10817 フレーム目の表示です。真ん中の 4 行目に黒字で「__置換表に登録されていたデータを利用__」が正しく表示されています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/3df4a314-808b-452a-9682-d7da38507751.png" width="500">

これで、__置換表を利用する場合__ に __`update_frameinfo`__ に記述した __すべての場合での表示が正しく行われることが確認__ できました。余裕がある方は、他のフレームでも正しく表示されるかどうかを確認してみて下さい。

### 置換表を利用しない場合

下記のプログラムを実行し、__置換表を利用しない場合__ に正しく表示されるかどうかを確認することにします。また、その際に先程と異なり、最短の勝利を目指す場合の表示の確認も行うことにします。

```python
mbtree.calc_score_for_anim(mbtree.root, minimax=False, use_tt=False, shortest_victory=True)
Mbtree_Anim(mbtree, isscore=True)
```

下記は __上段の >__ をクリックした __1 フレーム目の表示__ です。__置換表を利用しない__ ので、ルートノードの __"start" の次のフレーム__ には、ルートノードの __最初の子ノードの処理を開始するフレーム__ が表示されます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/5a57e214-ffa2-4907-b3a8-f76759124546.png" width="500">

下記は __下段の >>__ をクリックした __"end"__ の状態の 11150 フレーム目の表示です。__置換表を利用しない__ ので真ん中の 4 行目には __何も表示されない__ ことが確認できます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/33cfa0b9-dd28-467b-b64d-2ef642549698.png" width="500">

__他の状態__ のフレームの表示は、置換表を利用する場合と利用しない場合で __変わらない__ ので確認を省略します。余裕がある方は確認してみて下さい。

# 今回の記事のまとめ

今回の記事では、__ミニマックス法の視覚化のための Mbtree_Anim の修正__ を行いました。修正前の `update_ab` で行っていた視覚化よりも、__ミニマックス法の視覚化がわかりやすくなった__ と思うのですがどうでしょうか。

次回の記事では αβ 法の視覚化についての実装を行います。

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

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

# 次回の記事

https://qiita.com/ysgeso/items/42c2140766dd59906a02