Pythonで〇×ゲームのAIを一から作成する　その141　αβ 法の視覚化の改良

# 目次と前回の記事

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

https://qiita.com/ysgeso/items/3de5d3316b78d4925351

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

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

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

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

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

# αβ 法での評価値の計算手順の表示の改良

前回の記事で、αβ 法での評価値の計算手順の表示を実装しましたが、いくつか分かりづらい点があるので改良することにします。

## α 値と β 値の表示

αβ 法では、α 値と β 値を計算して枝狩りを行うので、__α 値と β 値を表示__ したほうが計算の手順がわかりやすくなります。そのためには、Mbtree_Anim のそれぞれのアニメーションの __フレームごと__ にその時点での __α 値と β 値を記録__ しておく必要があります。どのように記録すればよいかについて少し考えてみて下さい。

### Mbtree クラスの `calc_score_by_ab` メソッドの修正

Mbtree クラスの __`calc_score_by_ab`__ では、αβ 法で評価値を計算する際に __`nodelist_by_score`__ という属性にアニメーションの __各フレーム__ で処理が行われる __ノードのデータを list の形式で保存__ しました。同様に、__`ablist_by_score`__ という属性に __各フレーム__ での α 値と β 値 を表す __`(α 値, β 値)` という tuple__ を要素として持つ list を代入して記録することにします。

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

* __9、32 行目__：`nodelist_by_score` に `node` を追加した後で、`ablist_by_score` の要素に `(α 値, β 値)` を表す tuple を追加する
* __14、17 行目__：11 行目で `nodelist_by_score` に `node` を追加した場合は __その後の処理で `alpha` の値を更新する__ が、β 狩りを行う場合と行わない場合のそれぞれに対して __`alpha` の値の更新方法が異なる__ ので、__それぞれの場合で `alpha` の更新後__ に `ablist_by_score` の要素に __`(α 値, β 値)` を表す tuple を追加する必要がある__ 点に注意すること
* __25、28 行目__：min node の処理を行う場合に 14、17 行目 と同様の処理を行う
* __34 行目__：`nodelist_by_score` を空の list で初期化した後で、`ablist_by_score` を空の list で初期化する

```python
 1  from marubatsu import Marubatsu
 2  from tree import Mbtree
 3
 4  def calc_score_by_ab(self, abroot, debug=False):           
 5      self.count = 0
 6     
 7      def calc_ab_score(node, alpha=float("-inf"), beta=float("inf")):
 8          self.nodelist_by_score.append(node)
 9          self.ablist_by_score.append((alpha, beta))
元と同じなので省略
10                      score = calc_ab_score(childnode, alpha, beta)
11                      self.nodelist_by_score.append(node)
12                      if score >= beta:
13                          alpha = score
14                          self.ablist_by_score.append((alpha, beta))
15                          break
16                      alpha = max(alpha, score)
17                      self.ablist_by_score.append((alpha, beta))
18                  node.score = alpha
19              else:
20                  for childnode in node.children:
21                      score = calc_ab_score(childnode, alpha, beta)
22                      self.nodelist_by_score.append(node)
23                      if score <= alpha:
24                          beta = score
25                          self.ablist_by_score.append((alpha, beta))
26                          break
27                      beta = min(beta, score)
28                      self.ablist_by_score.append((alpha, beta))
29                  node.score = beta
30
31          self.nodelist_by_score.append(node)
32          self.ablist_by_score.append((alpha, beta))
元と同じなので省略        
33      self.nodelist_by_score = []
34      self.ablist_by_score = []
35      calc_ab_score(abroot)
36      dprint(debug, "count =", self.count) 
37    
38  Mbtree.calc_score_by_ab = calc_score_by_ab
```

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

```python
from marubatsu import Marubatsu
from tree import Mbtree

def calc_score_by_ab(self, abroot, debug=False):           
    self.count = 0
      
    def calc_ab_score(node, alpha=float("-inf"), beta=float("inf")):
        self.nodelist_by_score.append(node)
        self.ablist_by_score.append((alpha, beta))
        self.count += 1
        if node.mb.status != Marubatsu.PLAYING:
            self.calc_score_of_node(node)
        else:
            if node.mb.turn == Marubatsu.CIRCLE:
                for childnode in node.children:
                    score = calc_ab_score(childnode, alpha, beta)
                    self.nodelist_by_score.append(node)
                    if score >= beta:
                        alpha = score
                        self.ablist_by_score.append((alpha, beta))
                        break
                    alpha = max(alpha, score)
                    self.ablist_by_score.append((alpha, beta))
                node.score = alpha
            else:
                for childnode in node.children:
                    score = calc_ab_score(childnode, alpha, beta)
                    self.nodelist_by_score.append(node)
                    if score <= alpha:
                        beta = score
                        self.ablist_by_score.append((alpha, beta))
                        break
                    beta = min(beta, score)
                    self.ablist_by_score.append((alpha, beta))
                node.score = beta

        self.nodelist_by_score.append(node)
        self.ablist_by_score.append((alpha, beta))
        node.score_index = len(self.nodelist_by_score) - 1          
        return node.score

    from ai import dprint       
    for node in self.nodelist:
        node.score_index = float("inf")
    self.nodelist_by_score = []
    self.ablist_by_score = []
    calc_ab_score(abroot)
    dprint(debug, "count =", self.count) 
    
Mbtree.calc_score_by_ab = calc_score_by_ab
```
</details>

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

```diff_python
from marubatsu import Marubatsu
from tree import Mbtree

def calc_score_by_ab(self, abroot, debug=False):           
    self.count = 0
      
    def calc_ab_score(node, alpha=float("-inf"), beta=float("inf")):
        self.nodelist_by_score.append(node)
+       self.ablist_by_score.append((alpha, beta))
元と同じなので省略
                    score = calc_ab_score(childnode, alpha, beta)
                    self.nodelist_by_score.append(node)
                    if score >= beta:
                        alpha = score
+                       self.ablist_by_score.append((alpha, beta))
                        break
                    alpha = max(alpha, score)
+                   self.ablist_by_score.append((alpha, beta))
                node.score = alpha
            else:
                for childnode in node.children:
                    score = calc_ab_score(childnode, alpha, beta)
                    self.nodelist_by_score.append(node)
                    if score <= alpha:
                        beta = score
+                       self.ablist_by_score.append((alpha, beta))
                        break
                    beta = min(beta, score)
+                   self.ablist_by_score.append((alpha, beta))
                node.score = beta

        self.nodelist_by_score.append(node)
+       self.ablist_by_score.append((alpha, beta))
元と同じなので省略        
    self.nodelist_by_score = []
+   self.ablist_by_score = []
    calc_ab_score(abroot)
    dprint(debug, "count =", self.count) 
    
Mbtree.calc_score_by_ab = calc_score_by_ab
```
</details>

:::note
__`nodelist_by_score`__ の要素を __`(ノード, α 値, β 値)` に修正__ することで __`ablist_by_score` を追加せず__ にプログラムを修正することもできますが、そのようにした場合は、α 値や β 値を計算しない `calc_score_by_bf` や `calc_score_by_df` 内で `nodelist_by_score` に __`(node, None, None)`__ のような tuple を代入するように修正する必要が生じるため本記事では採用しませんでした。
:::

### Mbtree_Anim クラスの `__init__` メソッドの修正

次に、__α 値と β 値__ を Mbtree_Anim で __どこに表示するか__ を決める必要があります。本記事では、Mbtree_Anim で表示する __Figure の縦幅を 1 増やし__ て、Figure の __最上部に α 値と β 値を表示__ することにします。別の場所に表示したい人は自由に表示場所を変更して下しさい。

まず、下記のプログラムの 5 行目のように、__`__init__`__ メソッドの中で Mbtree_Anim で作成する __Figure の縦幅__ を表す __`height`__ 属性の値を 65 から __66 に 1 増やします__。

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

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

```python
from tree import Mbtree_Anim

def __init__(self, mbtree, isscore=False, size=0.15):
    self.mbtree = mbtree
    self.isscore = isscore
    self.size = size
    self.width = 50
    self.height = 66
    self.nodelist = self.mbtree.nodelist_by_score if isscore else self.mbtree.nodelist 
    self.nodenum = len(self.nodelist)
    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=False, size=0.15):
元と同じなので省略
    self.width = 50
-   self.height = 65
+   self.height = 66
元と同じなので省略

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

### Mbtree_Anim クラスの `update_gui` メソッドの修正

__α 値と β 値の表示__ を行う必要があるのは、__Mbtree_Anim__ で __評価値の計算の手順を表示する際だけ__ なので、Mbtree クラスの `draw_subtree` メソッドではなく、__Mbtree_Anim__ クラスの __`update_gui`__ メソッドを下記のプログラムのように修正することにします。

* __4 行目__：上部に α 値と β 値 を表示できるように、__Axes の表示範囲__ の __上端の y 座標__ を -1 から __-2 に変更__ する。それにあわせて表示範囲の __下端の y 座標も 1 減らす__ 必要がある
* __8 行目__：α 値と β 値を表示するのは、評価値の計算手順を表示する「__`isscore` 属性が `True`__」で、なおかつ「__`mbtree`__ が α 値と β 値を 記録した __`ablist_by_score` 属性を持つ__」場合なので、そのことを判定する。オブジェクトが属性を持つかどうかの判定は、[以前の記事](https://qiita.com/ysgeso/items/b5e388878fef195d8f2c#%E7%B5%84%E3%81%BF%E8%BE%BC%E3%81%BF%E9%96%A2%E6%95%B0-hasattr-%E3%81%AB%E3%82%88%E3%82%8B%E5%B1%9E%E6%80%A7%E3%81%AE%E5%AD%98%E5%9C%A8%E5%88%A4%E5%AE%9A)で説明した組み込み関数 `hasattr` を利用して行うことができる
* __9 ~ 12 行目__：`ablist_by_score` から表示するのフレームの α 値と β 値を取り出し、Figure の上部に表示する。文字の大きさは、Mbtree クラスの `draw_subtree` での文字の表示と同じ大きさに設定した。それぞれの表示位置は筆者が試行錯誤して決めた値なので、別の場所に表示したい場合は自由に変更すること

```python
 1  def update_gui(self):
 2      self.ax.clear()
 3      self.ax.set_xlim(-1, self.width - 1)
 4      self.ax.set_ylim(-2, self.height - 2)   
元と同じなので省略
 5      self.mbtree.draw_subtree(centernode=self.centernode, selectednode=self.selectednode,
 6                               anim_frame=self.play.value, isscore=self.isscore, 
 7                               ax=self.ax, maxdepth=maxdepth, size=self.size)
 8      if self.isscore and hasattr(self.mbtree, "ablist_by_score"):
 9          fontsize = 70 * self.size
10          alpha, beta = self.mbtree.ablist_by_score[self.play.value]
11          self.ax.text(0, -1, f"α: {alpha}", fontsize=fontsize)        
12          self.ax.text(10, -1, f"β: {beta}", fontsize=fontsize)        
13    
14  Mbtree_Anim.update_gui = update_gui
```

<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(-2, self.height - 2)   
    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.isscore and hasattr(self.mbtree, "ablist_by_score"):
        fontsize = 70 * self.size
        alpha, beta = self.mbtree.ablist_by_score[self.play.value]
        self.ax.text(0, -1, f"α: {alpha}", fontsize=fontsize)        
        self.ax.text(10, -1, f"β: {beta}", fontsize=fontsize)        
                            
    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):
    self.ax.clear()
    self.ax.set_xlim(-1, self.width - 1)
-   self.ax.set_ylim(-1, self.height - 1)   
+   self.ax.set_ylim(-2, self.height - 2)   
元と同じなので省略
    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.isscore and hasattr(self.mbtree, "ablist_by_score"):
+       fontsize = 70 * self.size
+       alpha, beta = self.mbtree.ablist_by_score[self.play.value]
+       self.ax.text(0, -1, f"α: {alpha}", fontsize=fontsize)        
+       self.ax.text(10, -1, f"β: {beta}", fontsize=fontsize)        
    
Mbtree_Anim.update_gui = update_gui
```
</details>

上記の修正後に下記のプログラムで Mbtree クラスのインスタンスを作成し、Mbtree_Anim で表示を行うと、Mbtree クラスのインスタンスを作成した際には __`ablist_by_score` を計算しない__ ので実行結果のように上部に α 値も β 値も __表示されません__。

```python
mbtree = Mbtree(algo="df")
Mbtree_Anim(mbtree, isscore=True)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/60e735d4-7269-6931-d289-31003c163ba2.png" width="350">

その後で下記のプログラムで __`calc_score_by_ab`__ を呼び出して αβ 法での評価値の計算を行ったデータに対して Mbtree_Anim を実行すると、実行結果のように __上部に α 値と β 値が表示される__ ことが確認できます。

```python
mbtree.calc_score_by_ab(mbtree.root)
Mbtree_Anim(mbtree, isscore=True)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/5dffff85-0cc0-b3f7-0d40-09bbb5bf47f9.png" width="350">

## ノードの手番の明確化

現状では、表示されたゲーム木の __ノードが__ 〇 の手番の __max ノードであるか__、× の手番の __min ノードであるか__ が __わかりづらい__ という問題があります。そこで、× の手番の __min ノード周辺の背景色を薄い灰色で表示__ して __区別できるようにする__ ことにします。

その表示は Mbtree_Anim でゲーム木を表示する場合だけでなく、__部分木を表示する場合は常に行ったほうがわかりやすい__ と思いましたので、部分木を表示する __Mbtree__ クラスの __`draw_subtree` メソッドを修正__ することにします。

× の手番の min ノード周辺の背景色を薄い灰色で表示するためには、その __表示範囲を決める__ 必要があります。どのような表示範囲にすればよいかについて少し考えてみて下さい。

本記事では、× の手番の __min ノードを表示する周辺の背景色__ を __下図のよう__ に縦に細長い薄い灰色で表示することにします。下図のような薄い灰色は __長方形の図形で表示__ できます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/3556e0d9-9c33-42d1-a98b-49961a3c2a3c.png" width="500">

上図の薄い灰色の長方形の __x 座標の範囲__ は、min ノードの局面の x 座標の範囲を __少し広くしたもの__ です。__深さ depth__ の局面の __x 座標の表示範囲__ は __depth * 5 ~ depth * 5 + 3__ なので、その範囲の左右を 1 ずつ広げた __depth * 5 - 1 ~ depth * 5 + 4__ を薄い灰色の長方形の x 座標の範囲とすることにします。

薄い灰色の長方形の __y 座標の範囲__ については、__Axes の範囲と同じにする__ という方法が考えられます。下図はそのように表示した場合の図です。下図では問題はありませんが、今後 __別のメッセージを表示__ しようとした際に、メッセージが薄い灰色の長方形の上にが __重なって表示__ されてしまい、少し __見づらくなってしまう__ という問題が発生することが考えられます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/dc9647eb-fcbb-09ff-95cf-e0a209b0d886.png" width="300">

そこで、y 座標の範囲を __α 値と β 値の表示を含まない__、__-1 ~ Axes の下端の座標__ とすることにします。下図はそのように表示した場合の図です。上図と見比べてみて下さい。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/590ac3ce-438d-c142-59d0-c15c81a6d2e7.png" width="300">

__Axes の y 座標の表示範囲__ は、Axes の __`get_ylim`__ というメソッドで得ることができ、その返り値は __`(下端の y 座標, 上端 の y 座標)`__ という __tuple__ になります。

`get_ylim` の詳細については、下記のリンク先を参照して下さい。

https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.get_ylim.html

下記は、上図のような灰色の長方形を表示するように __`draw_subtree`__ を修正したプログラムです。__`draw_subtree`__ では下記のプログラムの 5 ~ 10 行目で __Figure と Axes を作成する場合がある__ ので、__その後__ で薄い灰色の長方形を表示する __処理を記述__ する必要があります。

* __12 行目__：`get_ylim` で Axes の下端と上端の y 座標を計算する
* __13 行目__：薄い灰色の長方形の上端の座標を -1 とする
* __14 行目__：x の手番の min ノードの深さは奇数なので、for 文と [`range(1, 10, 2)`](https://qiita.com/ysgeso/items/6ca248133ff4d59a81d4#range-%E3%81%AE%E4%BD%BF%E3%81%84%E6%96%B9) によって奇数の深さの繰り返し処理を行う
* __15、16 行目__：深さ `depth` の min ノードに対する灰色の長方形を表示する。長方形の幅は 5、高さは `bottom - height` で計算できる。なお、whitesmoke は、lightgray よりも薄い灰色を表す色の文字列である

```python
 1  import matplotlib.patches as patches
 2
 3  def draw_subtree(self, centernode=None, selectednode=None, ax=None, anim_frame=None,
 4                   isscore=False, show_bestmove=False, show_score=True, size=0.25, lw=0.8, maxdepth=2):
元と同じなので省略 
 5  if ax is None:
 6      fig, ax = plt.subplots(figsize=(width * size, (height + 1) * size))
 7      ax.set_xlim(0, width)
 8      ax.set_ylim(-1, height)   
 9      ax.invert_yaxis()
10      ax.axis("off") 
11        
12      bottom, top = ax.get_ylim()
13      top = -1
14      for depth in range(1, 10, 2):
15          ax.add_artist(patches.Rectangle(xy=(depth * 5 - 1, top), width=5,
16                                          height=bottom - top, fc="whitesmoke"))            
元と同じなので省略
17            
18  Mbtree.draw_subtree = draw_subtree
```

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

```python
import matplotlib.patches as patches

def draw_subtree(self, centernode=None, selectednode=None, ax=None, anim_frame=None,
                    isscore=False, show_bestmove=False, show_score=True, size=0.25, lw=0.8, maxdepth=2):
    
    def calc_darkness(node):
        if show_bestmove:
            if node.parent is None:
                return 0, show_score
            elif node.mb.last_move in node.parent.bestmoves:
                return 0, show_score
            else:
                return 0.2, show_score
            
        if anim_frame is None:
            return 0, show_score
        index = node.score_index if isscore else node.id
        return (0.5, False) if index > anim_frame else (0, isscore)
    
    self.nodes_by_rect = {}

    if centernode is None:
        centernode = self.root
    self.calc_node_height(N=centernode, maxdepth=maxdepth)
    if show_bestmove:
        width = 5 * 10
    else:
        width = 5 * (maxdepth + 1)
    height = centernode.height
    parent = centernode.parent
    if parent is not None:
        height += (len(parent.children) - 1) * 4
        parent.height = height
    if ax is None:
        fig, ax = plt.subplots(figsize=(width * size, (height + 1) * size))
        ax.set_xlim(0, width)
        ax.set_ylim(-1, height)   
        ax.invert_yaxis()
        ax.axis("off")        
    
    bottom, top = ax.get_ylim()
    top = -1
    for depth in range(1, 10, 2):
        ax.add_artist(patches.Rectangle(xy=(depth * 5 - 1, top), width=5,
                                        height=bottom - top, fc="whitesmoke"))               

    if show_bestmove:
        bestx = 5 * maxdepth + 4
        bestwidth = 50 - bestx
        ax.add_artist(patches.Rectangle(xy=(bestx, -1), width=bestwidth,
                                        height=height + 1, fc="lightgray"))
    
    nodelist = [centernode]
    depth = centernode.depth
    while len(nodelist) > 0 and depth <= maxdepth:        
        dy = 0
        if parent is not None:
            dy = parent.children.index(centernode) * 4
        childnodelist = []
        for node in nodelist:
            if node is None:
                dy += 4
                childnodelist.append(None)
            else:
                dx = 5 * node.depth
                emphasize = node is selectednode
                darkness, show_score = calc_darkness(node)
                rect = node.draw_node(ax=ax, maxdepth=maxdepth, emphasize=emphasize, darkness=darkness,
                                    show_score=show_score, size=size, lw=lw, dx=dx, dy=dy)
                self.nodes_by_rect[rect] = node
                if show_bestmove and depth == maxdepth:
                    bestnode = node
                    while len(bestnode.bestmoves) > 0:
                        bestmove = bestnode.bestmoves[0]
                        bestnode = bestnode.children_by_move[bestmove]
                        dx = 5 * bestnode.depth
                        bestnode.height = 4
                        emphasize = bestnode is selectednode
                        rect = bestnode.draw_node(ax=ax, maxdepth=bestnode.depth, emphasize=emphasize,
                                                show_score=show_score, size=size, lw=lw, dx=dx, dy=dy)
                        self.nodes_by_rect[rect] = bestnode                                          
                    
                dy += node.height
                if len(node.children) > 0:  
                    childnodelist += node.children
                else:
                    childnodelist.append(None)
        depth += 1
        nodelist = childnodelist
        
    if parent is not None:
        dy = 0
        for sibling in parent.children:
            if sibling is not centernode:
                sibling.height = 4
                dx = 5 * sibling.depth
                darkness, show_score  = calc_darkness(sibling)
                rect = sibling.draw_node(ax, maxdepth=sibling.depth, size=size, darkness=darkness,
                                        show_score=show_score, lw=lw, dx=dx, dy=dy)
                self.nodes_by_rect[rect] = sibling
            dy += sibling.height
        dx = 5 * parent.depth
        darkness, show_score  = calc_darkness(parent)
        rect = parent.draw_node(ax, maxdepth=maxdepth, darkness=darkness, 
                                show_score=show_score, size=size, lw=lw, dx=dx, dy=0)
        self.nodes_by_rect[rect] = parent
    
        node = parent
        while node.parent is not None:
            node = node.parent
            node.height = height
            dx = 5 * node.depth
            darkness, show_score  = calc_darkness(node)
            rect = node.draw_node(ax, maxdepth=node.depth, darkness=darkness,
                                show_score=show_score, size=size, lw=lw, dx=dx, dy=0)
            self.nodes_by_rect[rect] = node
            
Mbtree.draw_subtree = draw_subtree
```
</details>

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

```diff_python
import matplotlib.patches as patches

def draw_subtree(self, centernode=None, selectednode=None, ax=None, anim_frame=None,
                 isscore=False, show_bestmove=False, show_score=True, size=0.25, lw=0.8, maxdepth=2):
元と同じなので省略 
    if ax is None:
        fig, ax = plt.subplots(figsize=(width * size, (height + 1) * size))
        ax.set_xlim(0, width)
        ax.set_ylim(-1, height)   
        ax.invert_yaxis()
        ax.axis("off")  
        
+   bottom, top = ax.get_ylim()
+   top = -1
+   for depth in range(1, 10, 2):
+       ax.add_artist(patches.Rectangle(xy=(depth * 5 - 1, top), width=5,
+                                       height=bottom - top, fc="whitesmoke"))                 
元と同じなので省略
            
Mbtree.draw_subtree = draw_subtree
```
</details>

上記の修正後に下記のプログラムを実行すると、実行結果（先ほどと同じ 7 フレーム目の図です）のように __α 値と β 値の行の部分に重ならない範囲__ で min ノードの周囲の背景色が薄い灰色で表示されるようになることが確認できます。

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

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/3556e0d9-9c33-42d1-a98b-49961a3c2a3c.png" width="500">

## α 値 と β 値の表示に関する改良

現状では α 値 と β 値が表示されるだけで、それらが __どのように計算されているかがわかりづらい__ ので、α 値 と β 値に関する表示を改良することにします。どのように改良すればよいかについて少し考えてみて下さい。

### 計算中の α 値 と β 値の明確化

[αβ 法](https://qiita.com/ysgeso/items/f1ec6467a79d135a4af9#%E7%A5%96%E5%85%88%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E8%80%83%E6%85%AE%E3%81%97%E3%81%9F-%CE%B1%CE%B2-%E6%B3%95)では、__max ノード__ の子ノードの評価値によって __α 値__ が、__min ノード__ の子ノードの評価値によって __β 値__ が __計算__ されます。そこで、__赤枠の選択中のノード__ の子ノードの評価値によって __計算する α 値または β 値__ を __赤色で表示__ することにします。

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

* __4、5 行目__：α 値と β 値の文字の色を、選択中のノードの手番によって計算する
* __7、8 行目__：4、5 行目で計算した色で α 値と β 値 を表示するように修正する
 
```python
 1  def update_gui(self):
元と同じなので省略
 2      if self.isscore and hasattr(self.mbtree, "ablist_by_score"):
 3          fontsize = 70 * self.size
 4          acolor = "red" if self.selectednode.mb.turn == Marubatsu.CIRCLE else "black"
 5          bcolor = "black" if self.selectednode.mb.turn == Marubatsu.CIRCLE else "red"
 6          alpha, beta = self.mbtree.ablist_by_score[self.play.value]
 7          self.ax.text(0, -1, f"α: {alpha}", fontsize=fontsize, c=acolor)        
 8          self.ax.text(10, -1, f"β: {beta}", fontsize=fontsize, c=bcolor)        
元と同じなので省略 
 9
10  Mbtree_Anim.update_gui = update_gui
```

<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(-2, self.height - 2)   
    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.isscore and hasattr(self.mbtree, "ablist_by_score"):
        fontsize = 70 * self.size
        acolor = "red" if self.selectednode.mb.turn == Marubatsu.CIRCLE else "black"
        bcolor = "black" if self.selectednode.mb.turn == Marubatsu.CIRCLE else "red"
        alpha, beta = self.mbtree.ablist_by_score[self.play.value]
        self.ax.text(0, -1, f"α: {alpha}", fontsize=fontsize, c=acolor)        
        self.ax.text(10, -1, f"β: {beta}", fontsize=fontsize, c=bcolor)        
                            
    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.isscore and hasattr(self.mbtree, "ablist_by_score"):
        fontsize = 70 * self.size
+       acolor = "red" if self.selectednode.mb.turn == Marubatsu.CIRCLE else "black"
+       bcolor = "black" if self.selectednode.mb.turn == Marubatsu.CIRCLE else "red"
        alpha, beta = self.mbtree.ablist_by_score[self.play.value]
-       self.ax.text(0, -1, f"α: {alpha}", fontsize=fontsize)       
+       self.ax.text(0, -1, f"α: {alpha}", fontsize=fontsize, c=acolor)        
-       self.ax.text(10, -1, f"β: {beta}", fontsize=fontsize)
+       self.ax.text(10, -1, f"β: {beta}", fontsize=fontsize, c=bcolor)
元と同じなので省略 

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

上記の修正後に下記のプログラムを実行すると、実行結果のように α 値と β 値の文字が選択中のノードの種類によって色が変化することが確認できます。

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

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/6e1cb5d6-fd1e-be4f-e233-08b4bd2e858c.png" width="250">　<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/f5abfab2-998c-ddbf-f8e2-c0489206cbd3.png" width="265">

下図は上から順に 8 フレーム目と 9 フレーム目の図です。8 フレーム目で __赤枠のノードの評価値が 1__ と計算されています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/f89f3ecf-de70-8c60-1bf0-2df6024319fd.png" width="500">

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/e36f1690-e43c-bf73-68e3-f37b81bad24a.png" width="500">

その次の __9 フレーム目__ では赤枠のノードは __max ノード__ なので、__子ノードの評価値の最大値__ が __α 値として計算__ され、実際に α 値が -inf（負の無限大）から __1 に正しく更新されている__ ことが確認できます。


### 行う処理を表すメッセージの表示

現状では、上記のように __α 値 と β 値__ が __説明なしで更新__ される点が __分かりづらい__ という問題があります。また、__枝狩りが行われたことがわかりづらい__ という問題もあります。そこで、α 値と β 値の右に __行われた処理の種類を表す__ 以下の表のような __メッセージを表示__ することにします。もっとわかりやすいメッセージを思いついた人は自由に変更して下さい。

|| 行われた処理 | メッセージの例 |
|:-:|:--|:--|
|①| __ノードの処理が開始__<br>(`calc_ab_score` が呼ばれた直後）  | このノードの処理の開始  |
|②| __子ノードの評価値の計算の直後__ |  child score = 0   |
|③| __max node で β 狩りを行う__ | child score = 0 ≧ -1(β 値) による β 狩り  |
|④| __max node で α 値を更新する__  | child score = 0 > -inf(α 値) による α 値の更新  |
|⑤| __max node で α 値を更新しない__  | child score = 0 ≦ 1(α 値) なので α 値は更新しない |
|⑥| __min node で α 狩りを行う__ | child score = 0 ≦ 1(α 値) による α 狩り  |
|⑦| __min node で β 値を更新する__  | child score = 0 < inf(β 値) による β 値の更新  |
|⑧| __min node で β 値を更新しない__  | child score = 0 ≧ -1(β 値) なので β 値は更新しない |
|⑨| __ノードの処理が終了__ | このノードの評価値が 0 で確定 |

上記のメッセージは __`calc_score_by_ab`__ で作成し、__`ablist_by_score`__ の要素に記録するデータを __`(α 値, β 値, メッセージ)`__ という tuple に変更します。

下記はそのように `calc_score_by_ab` を修正したプログラムです。下記の説明では、上記の表のメッセージを左端の列の番号に対応させて説明します。

* __6 行目__：`ablist_by_score` に ① のメッセージを入れた tuple を追加する
* __8、26 行目__：子ノードの評価値の計算が終了した直後の、枝狩りや α 値や β 値の更新を行う前の時点で `ablist_by_score` に ② のメッセージを入れた tuple を追加する
* __11、12 行目__：max node で β 狩りが行われた場合に α 値を更新し、`nodelist_by_score` にノードを追加し、`ablist_by_score` に ③ のメッセージを入れた tuple を追加する
* 14 行目にあった __`alpha = max(alpha, score)`__ の処理は、`score > alpha` の場合とそうでない場合に __異なるメッセージを計算する処理を行う必要がある__ ため、14、16 行目の __if 文で行うように修正した__ 
* __14 ~ 16 行目__：max node で β 狩りが行われず、α 値を更新する場合の ④ のメッセージを `message` に代入し、`alpha` を `score` の値で更新する
* __17、18 行目__：α 値を更新しない場合 の ⑤ のメッセージを `message` に代入する
* __19、20 行目__：`nodelist_by_score` にノードを追加し、`ablist_by_score` に 14 ~ 18 行目で計算したメッセージを入れた tuple を追加する
* __26 ~ 38 行目__：min node に対して上記と同様の修正を行う（⑥ ~ ⑧ のメッセージ）
* __42 行目__：このノードの評価値が確定したので、`ablist_by_score` に ⑨ のメッセージを入れた tuple を追加する

```python
 1  def calc_score_by_ab(self, abroot, debug=False):           
 2      self.count = 0
 3    
 4      def calc_ab_score(node, alpha=float("-inf"), beta=float("inf")):
 5          self.nodelist_by_score.append(node)
 6          self.ablist_by_score.append((alpha, beta, "このノードの処理の開始"))
元と同じなので省略
 7                      self.nodelist_by_score.append(node)
 8                      self.ablist_by_score.append((alpha, beta, f"child score = {score}"))
 9                      if score >= beta:
10                          alpha = score
11                          self.nodelist_by_score.append(node)
12                          self.ablist_by_score.append((alpha, beta, f"child score = {score} ≧ {beta}(β値) によるβ狩り"))
13                          break
14                      if score > alpha:
15                          message = f"child score = {score} > {alpha}(α値)によるα値の更新"
16                          alpha = score
17                      else:
18                          message = f"child score = {score} ≦ {alpha}(α値)なのでα値の更新は行わない"
19                      self.nodelist_by_score.append(node)
20                      self.ablist_by_score.append((alpha, beta, message))
21                  node.score = alpha
22              else:
23                  for childnode in node.children:
24                      score = calc_ab_score(childnode, alpha, beta)
25                      self.nodelist_by_score.append(node)
26                      self.ablist_by_score.append((alpha, beta, f"child score = {score}"))
27                      if score <= alpha:
28                          beta = score
29                          self.nodelist_by_score.append(node)
30                          self.ablist_by_score.append((alpha, beta, f"child score = {score} ≦ {alpha}(α値)によるα狩り"))
31                          break
32                      if score < beta:
33                          message = f"child score = {score} < {beta}(β値) によるβ値の更新"
34                          beta = score
35                      else:
36                          message = f"child score = {score} ≧ {beta}(β値) なのでβ値の更新は行わない"
37                      self.nodelist_by_score.append(node)
38                      self.ablist_by_score.append((alpha, beta, message))
39                  node.score = beta
40
41          self.nodelist_by_score.append(node)
42          self.ablist_by_score.append((alpha, beta, f"このノードの評価値が {node.score} で確定"))
元と同じなので省略
43    
44  Mbtree.calc_score_by_ab = calc_score_by_ab
```

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

```python
def calc_score_by_ab(self, abroot, debug=False):           
    self.count = 0
      
    def calc_ab_score(node, alpha=float("-inf"), beta=float("inf")):
        self.nodelist_by_score.append(node)
        self.ablist_by_score.append((alpha, beta, "このノードの処理の開始"))
        self.count += 1
        if node.mb.status != Marubatsu.PLAYING:
            self.calc_score_of_node(node)
        else:
            if node.mb.turn == Marubatsu.CIRCLE:
                for childnode in node.children:
                    score = calc_ab_score(childnode, alpha, beta)
                    self.nodelist_by_score.append(node)
                    self.ablist_by_score.append((alpha, beta, f"child score = {score}"))
                    if score >= beta:
                        alpha = score
                        self.nodelist_by_score.append(node)
                        self.ablist_by_score.append((alpha, beta, f"child score = {score} ≧ {beta}(β値) によるβ狩り"))
                        break
                    if score > alpha:
                        message = f"child score = {score} > {alpha}(α値)によるα値の更新"
                        alpha = score
                    else:
                        message = f"child score = {score} ≦ {alpha}(α値)なのでα値の更新は行わない"
                    self.nodelist_by_score.append(node)
                    self.ablist_by_score.append((alpha, beta, message))
                node.score = alpha
            else:
                for childnode in node.children:
                    score = calc_ab_score(childnode, alpha, beta)
                    self.nodelist_by_score.append(node)
                    self.ablist_by_score.append((alpha, beta, f"child score = {score}"))
                    if score <= alpha:
                        beta = score
                        self.nodelist_by_score.append(node)
                        self.ablist_by_score.append((alpha, beta, f"child score = {score} ≦ {alpha}(α値)によるα狩り"))
                        break
                    if score < beta:
                        message = f"child score = {score} < {beta}(β値) によるβ値の更新"
                        beta = score
                    else:
                        message = f"child score = {score} ≧ {beta}(β値) なのでβ値の更新は行わない"
                    self.nodelist_by_score.append(node)
                    self.ablist_by_score.append((alpha, beta, message))
                node.score = beta

        self.nodelist_by_score.append(node)
        self.ablist_by_score.append((alpha, beta, f"このノードの評価値が {node.score} で確定"))
        node.score_index = len(self.nodelist_by_score) - 1          
        return node.score

    from ai import dprint       
    for node in self.nodelist:
        node.score_index = float("inf")
    self.nodelist_by_score = []
    self.ablist_by_score = []
    calc_ab_score(abroot)
    dprint(debug, "count =", self.count) 
    
Mbtree.calc_score_by_ab = calc_score_by_ab
```
</details>

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

```diff_python
def calc_score_by_ab(self, abroot, debug=False):           
    self.count = 0
      
    def calc_ab_score(node, alpha=float("-inf"), beta=float("inf")):
        self.nodelist_by_score.append(node)
-       self.ablist_by_score.append((alpha, beta))
+       self.ablist_by_score.append((alpha, beta, "このノードの処理の開始"))
元と同じなので省略
                    self.nodelist_by_score.append(node)
+                   self.ablist_by_score.append((alpha, beta, f"child score = {score}"))
                    if score >= beta:
                        alpha = score
+                       self.nodelist_by_score.append(node)
-                       self.ablist_by_score.append((alpha, beta))
+                       self.ablist_by_score.append((alpha, beta, f"child score = {score} ≧ {beta}(β値) によるβ狩り"))
                        break
-                   alpha = max(alpha, score)
+                   if score > alpha:
+                       message = f"child score = {score} > {alpha}(α値)によるα値の更新"
+                       alpha = score
+                   else:
+                       message = f"child score = {score} ≦ {alpha}(α値)なのでα値の更新は行わない"
+                   self.nodelist_by_score.append(node)
-                   self.ablist_by_score.append((alpha, beta))
+                   self.ablist_by_score.append((alpha, beta, message))
                node.score = alpha
            else:
                for childnode in node.children:
                    score = calc_ab_score(childnode, alpha, beta)
                    self.nodelist_by_score.append(node)
+                   self.ablist_by_score.append((alpha, beta, f"child score = {score}"))
                    if score <= alpha:
                        beta = score
+                       self.nodelist_by_score.append(node)
-                       self.ablist_by_score.append((alpha, beta))
+                       self.ablist_by_score.append((alpha, beta, f"child score = {score} ≦ {alpha}(α値)によるα狩り"))
                        break
-                   beta = min(beta, score)
+                   if score < beta:
+                       message = f"child score = {score} < {beta}(β値) によるβ値の更新"
+                       beta = score
+                   else:
+                       message = f"child score = {score} ≧ {beta}(β値) なのでβ値の更新は行わない"
+                   self.nodelist_by_score.append(node)
-                   self.ablist_by_score.append((alpha, beta))
+                   self.ablist_by_score.append((alpha, beta, message))
                node.score = beta

        self.nodelist_by_score.append(node)
-       self.ablist_by_score.append((alpha, beta))
+       self.ablist_by_score.append((alpha, beta, f"このノードの評価値が {node.score} で確定"))
元と同じなので省略
    
Mbtree.calc_score_by_ab = calc_score_by_ab
```
</details>

次に、__`update_gui`__ を下記のプログラムのように __上記で作成したメッセージを表示__ するように修正します。なお、8 行目で __β 値のメッセージの表示場所__ を __薄い灰色の長方形の位置に合わせて表示__ するように修正しました。

* __4 行目__：2 番の要素に代入されたメッセージを `message` に代入するように修正した
* __8 行目__：β 値の表示位置を薄い灰色の長方形の位置にあわせて 10 から 9 に修正した
* __9 行目__：メッセージを表示するようにした

```python
 1  def update_gui(self):
元と同じなので省略
 2      if self.isscore and hasattr(self.mbtree, "ablist_by_score"):
 3          fontsize = 70 * self.size
 4          alpha, beta, message = self.mbtree.ablist_by_score[self.play.value]
 5          acolor = "red" if self.selectednode.mb.turn == Marubatsu.CIRCLE else "black"
 6          bcolor = "black" if self.selectednode.mb.turn == Marubatsu.CIRCLE else "red"
 7          self.ax.text(0, -1, f"α: {alpha}", fontsize=fontsize, c=acolor)        
 8          self.ax.text(9, -1, f"β: {beta}", fontsize=fontsize, c=bcolor)        
 9          self.ax.text(19, -1, message, fontsize=fontsize)        
元と同じなので省略
10    
11  Mbtree_Anim.update_gui = update_gui
```

<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(-2, self.height - 2)   
    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.isscore and hasattr(self.mbtree, "ablist_by_score"):
        fontsize = 70 * self.size
        alpha, beta, message = self.mbtree.ablist_by_score[self.play.value]
        acolor = "red" if self.selectednode.mb.turn == Marubatsu.CIRCLE else "black"
        bcolor = "black" if self.selectednode.mb.turn == Marubatsu.CIRCLE else "red"
        self.ax.text(0, -1, f"α: {alpha}", fontsize=fontsize, c=acolor)        
        self.ax.text(9, -1, f"β: {beta}", fontsize=fontsize, c=bcolor)        
        self.ax.text(19, -1, message, fontsize=fontsize)             
                            
    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.isscore and hasattr(self.mbtree, "ablist_by_score"):
        fontsize = 70 * self.size
-       alpha, betae = self.mbtree.ablist_by_score[self.play.value]
+       alpha, beta, message = self.mbtree.ablist_by_score[self.play.value]
        acolor = "red" if self.selectednode.mb.turn == Marubatsu.CIRCLE else "black"
        bcolor = "black" if self.selectednode.mb.turn == Marubatsu.CIRCLE else "red"
        self.ax.text(0, -1, f"α: {alpha}", fontsize=fontsize, c=acolor)        
-       self.ax.text(10, -1, f"β: {beta}", fontsize=fontsize, c=bcolor)        
+       self.ax.text(9, -1, f"β: {beta}", fontsize=fontsize, c=bcolor)        
+       self.ax.text(19, -1, message, fontsize=fontsize)           
元と同じなので省略
    
Mbtree_Anim.update_gui = update_gui
```
</details>

### 表示の確認

上記のプログラムが正しく動作するかどうかを、いくつかのフレームの表示で確認することにします。

上記の修正後に下記のプログラムで __`calc_score_by_ab`__ を実行して __`ablist_by_score` 属性の値を計算しなおした `mbtree`__ を Mbtree_Anim で表示し、8 フレーム目まで表示を進めると実行結果が表示されます。8 フレーム目では __赤枠のノードの評価値が 1 で確定__ し、そのことが __メッセージとして正しく表示される__ ことが確認できます。

```python
mbtree.calc_score_by_ab(mbtree.root)
Mbtree_Anim(mbtree, isscore=True)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/54be6322-f9fd-78d2-b47e-1809127afafc.png" width="500">

その次の下図の 9 フレーム目では、__赤枠の子ノードの評価値が 1 と計算された__ ことを表すメッセージが表示されます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/d26ae1dc-a5e5-d521-dc38-2ccf4460becd.png" width="500">

その次の下図の 10 フレーム目では、赤枠の子ノードの評価値が 1 が上図の 9 フレーム目の α 値である -inf（負の無限大）より大きいため、__α 値が更新された__ ことを表す __メッセージが表示__ され、左上の __α 値が実際に 1 に更新__ されます。


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/2c55b864-1b86-efed-6e5c-e2ef922559b7.png" width="500">

:::note
本記事では行いませんが、α 値が更新されたことをさらに明確にするために、α 値の文字の色を別の色で表示したり、太字や下線で表示するという方法も考えられます。
:::

その後の下図の 15 フレーム目では、赤枠の子ノードの評価値が 1 と計算されたことを表すメッセージが表示されます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/6da23980-f42b-fa49-a7b0-47f939bb8e3b.png" width="500">

その次の下図の 16 フレーム目では、赤枠の子ノードの評価値が 1 が上図の 15 フレーム目の α 値である 1 以下であるため __α 値が更新されない__ ことを表す __メッセージが表示__ されます。また、実際に α 値は上図の値から更新されません。


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/efb68c65-1571-702b-1d0c-c7988a9f9458.png" width="500">

その次の下図の 17 フレーム目で __赤枠のノードの評価値が 1 で確定__ し、その次の下図の 18 フレーム目では 赤枠の __子ノードの評価値が 1 と計算__ されたことを表すメッセージがそれぞれ表示されます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/c833160e-9ac5-7040-05dd-bc2b3f5a26ca.png" width="500">

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/96818b3c-acfb-e4a5-ff3f-6e2cc5937164.png" width="500">

その次の下図の 19 フレーム目で赤枠の __子ノードの評価値が α 値である 1 以下__ になったため、__α 狩り__ が行われたことを表す __メッセージが表示__ されます。なお、α 狩りを行った場合は、子ノードの評価値が赤枠のノードの評価値となるため、赤枠のノードの評価値を表す β 値に子ノードの評価値が代入され、図の β 値には 1 が表示されます。


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/d536eec1-f8e6-e39f-d81c-5d63b62ddae8.png" width="500">

その次の下図の 20 フレーム目で __α 狩り__ が行われた結果、__残りの子ノードの評価値の計算が省略__ され、赤枠のノードの __評価値が β 値である 1 で確定__ したことを表す __メッセージが表示__ されます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/414d9ab2-652f-e593-d532-48165d17bd9f.png" width="500">

上記では[先ほどの表](#行う処理を表すメッセージの表示)の全てのメッセージの確認を行っていませんが、② の子ノードの評価値の計算、④ の α 値の更新、⑤ の α 値を更新しない、⑥ の α 狩り、⑨ の評価値の確定のメッセージが __意図通りに表示されることが確認__ できました。興味がある方は、残りのメッセージも正しく表示されることを確認してみて下さい。

# 今回の記事のまとめ

今回の記事では、α 値と β 値の表示、max ノードと min ノードの明確化、各フレームで行われる処理を表すメッセージの表示を実装しました。

これで αβ 法で行われる処理の手順がわかりやすくなりましたが、まだ改良の余地があります。どのような改良の余地があるかについて考えてみて下さい。

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

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

# 次回の記事

https://qiita.com/ysgeso/items/1468226fe9a96ae6a55c