Pythonで〇×ゲームのAIを一から作成する　その142　数直線によるαβ法のα値などの視覚化

# 目次と前回の記事

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

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

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

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

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

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

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

# 数直線による α 値、β 値、子ノードの評価値の視覚化

前回の記事では、αβ 法で評価値を計算する手順を Mbtree_Anim で表示する際に、下図のように α 値、β 値、計算した子ノードの評価値、行った処理のメッセージを表示することでわかりやすくするという工夫を行いました。

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

しかし、α 値や β 値などの数字を上図のように __文字で表記__ するのはあまり __直観的ではありません__。そこで、今回の記事では α 値や β 値などを __数直線上に表示__ することで、__直観的にわかりやすく表示__ するという工夫を行なうことにします。

# 数直線の描画

データの種類が 1 種類である、__1 次元の数値__ を __図で表現する方法__ として、直線に等間隔に数値を割り当てるという、__数直線__ が良く使われます。__数直線上に複数の数値を記号で表記__ することで、その __大小関係など__ を __直観的にわかりやすく表現__ することができます。

下図は数直線の例で、数直線上に -1 と 2 という数値を __色のついた円__ で表記しています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/032fbe47-448b-873d-2f17-50180c674ea7.png" width="300">

matplotlib では 2 次元のグラフの軸を表示する機能を持ちますが、筆者が調べた範囲では 1 次元の数直線の軸を表示する機能は持っていないようです[^1]。そのため、本記事では数直線を一からプログラムで描画することにします。

また、前回の記事では α 値や β 値などを Mbtree_Anim の部分木の上部に描画しましたが、数直線をそのように描画すると、数直線を描画する際に必要となる __座標の計算が面倒になる__ ので、__別の Figure を作成__ して __そちらに描画__ することにします。

## 数直線の表示範囲の設定

数直線は負の無限大から正の無限大までの数値を表す直線ですが、実際に __無限の長さの直線を描画__ することは __不可能__ です。そこで、数直線を描画する際には、__どの範囲の数値を描画するかを決める__ 必要があります。

__α 値__ と __β 値__ を __数直線上に描画__ するためには、__α 値と β 値の取りうる範囲__ を表す数直線を描画する必要があります。しかし、__α 値と β 値の初期値__ は __負の無限大と正の無限大__ なので、そのような範囲の数直線を描画する際には __工夫が必要__ になります。本記事では、描画する __数直線の両端__ を __負の無限大__ と __正の無限大__ と __みなす__ という工夫を行なうことにします。

次に、負の無限大と正の無限大 __以外__ の __α 値と β 値の取りうる範囲__ を考えます。α 値と β 値は、決着がついた局面の評価値から計算されるので、その範囲は __決着がついた局面の評価値の範囲__ になります。本記事では決着がついた局面の評価値として、__-1 ~ 1__ までの範囲で計算する方法と、[以前の記事](https://qiita.com/ysgeso/items/0f83bcdf76bac5b0175f#%E8%A9%95%E4%BE%A1%E5%80%A4%E3%81%AE%E3%81%BE%E3%81%A8%E3%82%81)で説明した、最も早く勝利できる合法手を計算する下記の表のような __-2 ~ 3__ までの範囲で計算する方法を紹介しました。

| 局面の深さ | 5 | 6 | 7 | 8 | 9 | 評価値の計算式 |
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| __〇 が勝利した場合の評価値__  | 3  | |  2 | | 1  |（11 - 局面の深さ）÷ 2|
| __× が勝利した場合の評価値__  |   |-2 |   |-1 |   |（局面の深さ - 10）÷ 2|

そこで、本記事では上記の __両方の評価値の範囲に対応できる__ ように、下記と下図のような数直線を描画し、その上に α 値や β 値などを描画することにします。

* 数直線の描画範囲は -3 ~ 4 までの範囲とする。ただし、-3 の左端を負の無限大、4 の右端を正の無限大とみなすことにする
* 数直線の整数の部分に目盛りを描画する
* 目盛りの下に整数の値を描画する。ただし、-3 には -∞ を、4 には ∞ を描画する

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/e17bc28c-29f5-4bd8-8536-7d82adb90969.png" width="200">

:::note
一般的には数直線は正の方向の端に矢印を表記しますが、本記事で数直線を描画する方法で右端に矢印を描画するのは面倒なので本記事では表記しないことにします。

また、下に描画された数値を見れば、どちらの方向が正の方向であるかは明白なので、矢印を描画しなくても大きな問題にはならないと思います。
:::

## 数直線と目盛りの描画

数直線と目盛りの描画は Axes の `plot` などの線を描画するメソッドなどを利用して、別々に描画するという方法がありますが、その場合は __一つ一つの目盛りの両端の座標を指定__ し目盛りの線を描画する必要がある点が __面倒です__。本記事では別の方法として __`plot`__ メソッドの __マーカーを描画する機能__ を利用することで、数直線と目盛りの __両方を同時にまとめて描画__ するという方法を紹介します。

### `plot` メソッドによるマーカーの描画

折れ線を描画する Axes の __`plot`__ メソッドは、それぞれの折れ線の __頂点に__ 円や四角形などの __マーカー（marker）__ と呼ばれる __図形を描画__ することができます。マーカーは、__`plot`__ メソッドを呼び出す際に、__キーワード引数 `marker`__ にマーカーの __種類を表す文字列を記述__ することで描画します。例えば 下記のプログラムのように、__`marker="o"`__ を記述すると __円のマーカー__ が、__`marker="^"`__ を記述すると __三角形のマーカー__ が描画されます。

```python
import matplotlib.pyplot as plt
import japanize_matplotlib

fig, ax = plt.subplots()
ax.plot([0, 1, 2], [0, 2, 1], marker="o")
ax.plot([0, 1, 2], [1, 0, 2], marker="^")
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/b3c817f6-90a3-cfc0-af24-43ef3dc1bc53.png" width="350">

マーカーの種類を表す文字列の一覧については、下記のリンク先を参照して下さい[^2]。

https://matplotlib.org/stable/api/markers_api.html#module-matplotlib.markers

__マーカーの大きさ__ は下記のプログラムのように、キーワード引数 __`markersize`__ または __`ms`__ を記述することで設定することができます。

```python
fig, ax = plt.subplots()
ax.plot([0, 1, 2], [0, 2, 1], marker="o", markersize="20")
ax.plot([0, 1, 2], [1, 0, 2], marker="^", ms="10")
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/187b32e0-b44d-b745-51f7-2719fca9d207.png" width="350">

### マーカー、線の種類、線の色の一括設定

`plot` メソッドでは __マーカー__、__線の種類__、__線の色__ を下記の表のキーワード引数で設定することができます。
| キーワード引数 | 意味 |
|:--|:--|
| __`markerstyle`__ または __`ms`__  | マーカーの種類  |
| __`linestyle`__ または __`ls`__  | 線の種類  |
| __`color`__ または __`c`__  |  線の色 |

また、それぞれの設定は __1 または 2 文字の文字列__ で記述できるようになっています。設定できる文字列の種類については、下記のリンク先（下の方にあります）を参照して下さい。

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

上記の 3 種類の設定は良く行われるので、それらの __3 つの設定__ を __1 つの実引数で簡潔に記述__ できるようになっています。具体的には `plot` メソッドの __3 つ目の実引数__ にそれぞれの __設定を表す文字列を並べた__ 文字列を記述することで設定します。詳細は、上記のリンク先の format strings の所を参照して下さい。

例えば、下記のプログラムのように __`"o--r"`__ と記述した場合は、実行結果のように __円のマーカー（o）__、__破線（--）__、__赤色（r）__ で折れ線が、__`"s:g"`__ と記述した場合は __正方形のマーカー（s）__、__点線（:）__、__緑色（g）__ で折れ線が描画されます。

```python
fig, ax = plt.subplots()
ax.plot([0, 1, 2], [0, 2, 1], "o--r")
ax.plot([0, 1, 2], [1, 0, 2], "s:g")
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/8402f185-d6b0-5a9e-6514-287bdff6b6fe.png" width="350">

:::note
3 つの設定は、マーカーの種類、線種、色の __順番で記述__ することが __推奨されています__ が、それぞれの設定で使われる __文字列は重複しない__ ようになっているので、__順番を変えても構わない__ ようです。例えば `"o-r"` と `"ro-"` は同じ意味を持ちます。

また 3 つの設定を __全て記述する必要はありません__。例えば下記のプログラムの 2 行目のように __`"ｒ"` で色の設定だけを記述__ した場合は __マーカーなしの赤色の実線__ が描画されます。設定を記述しなかった場合は下記のようなルールで描画されます。

* 色の設定を省略した場合は、色が自動的に設定される
* マーカーの設定を省略した場合は、マーカーは描画されない
* 線種の設定を省略した場合は、以下のように描画される
    * マーカーの設定を記述していない場合は実線が描画される
    * マーカーの設定を記述した場合は線は描画されない

下記のプログラムは色の設定だけと、マーカーの設定だけを記述した例です。3 行目では線種が省略され、マーカーが設定されているので実行結果のようにマーカーのみが描画されて線は描画されません。

```python
fig, ax = plt.subplots()
ax.plot([0, 1, 2], [0, 2, 1], "r")
ax.plot([0, 1, 2], [1, 0, 2], "o")
```

実行結果のように

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/2c8d4687-ca58-cbce-aa96-ab0ad7c8790f.png" width="350">

実例はこのすぐ後で紹介するので、ここではプログラムは省略しますが、__マーカーと実線を描画__ する場合は __`o-r`__ のように、実線を表す __`"-"`__ を記述する必要があります。 
:::

### 数直線と目盛りの描画

__目盛り__ は __`plot`__ でマーカーを表す実引数に __`"|"`__ を記述することによって描画される __縦棒のマーカー__ で、下記のプログラムのように __うまく描画__ することができます。なお、1、3、4 行目の __Figure の大きさ__、__Axes の表示範囲の設定__ は、今後の作業で Figure に数直線以外の様々な描画を行う処理を加えた際にうまく表示されるように、__筆者が試行錯誤して設定__ した値です。実際には、数直線を描画するプログラムを作成する際に、__最初からこれらの値をうまく決めることはできません__。これらの値を変更することで、表示される __Figure の大きさ__ や、中の __表示のバランスが変わる__ ので、それらを変えたい人は自由に調整して下さい。

* __1 行目__：Figure を作成する
* __2 行目__：Figure と同じ大きさの Axes を [`add_axes`](https://qiita.com/ysgeso/items/0335cc42055729d0fac3#add_axes-%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%AB%E3%82%88%E3%82%8B-axes-%E3%81%AE%E9%85%8D%E7%BD%AE%E4%BD%8D%E7%BD%AE%E3%81%AE%E8%A8%AD%E5%AE%9A) を利用して作成する[^3]
* __3 ~ 5 行目__：Axes の表示範囲を設定し、軸の表示を行わないようにする
* __7 行目__：折れ線の頂点の __x 座標の一覧__ として __`range`__ を利用して __-3 ~ 4 までの整数__ を表すデータを[^4]、__y 座標の一覧__ として [* 演算子](https://qiita.com/ysgeso/items/2a436b3819bef9a6a2b4#-%E6%BC%94%E7%AE%97%E5%AD%90%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%9F-list-%E3%81%AE%E4%BD%9C%E6%88%90) を利用して __8 つの `0` を要素として持つ list__ を記述することで、(-3, 0)、(-2, 0)、・・・、(4, 0) の座標を結ぶ __数直線が描画__ される。また、__3 つ目の実引数__ に __`"|-k"`__ を記述することで、線が __黒色（k）__ の __実線（-）__ で描画され、頂点に目盛りを表す __縦棒のマーカー（|）__ が描画される。縦棒の長さを変えたい場合は、キーワード引数 `markersize` を記述して自由に調整すること

```python
1  fig = plt.figure(figsize=(5, 1))
2  ax = fig.add_axes([0, 0, 1, 1])
3  ax.set_xlim(-4, 14)
4  ax.set_ylim(-1.5, 1.5)
5  ax.axis("off")
6
7  ax.plot(range(-3, 5), [0] * 8, "|-k")
```

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

```python
fig = plt.figure(figsize=(5, 1))
ax = fig.add_axes([0, 0, 1, 1])
ax.clear()
ax.axis("off")
ax.set_xlim(-4, 14)
ax.set_ylim(-1.5, 1.5)

ax.plot(range(-3, 5), [0] * 8, "|-k")
```
</details>

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/e2cf385e-f21f-6136-8337-937448b84ff1.png" width="350">

# 目盛りの下の数値の描画

matplotlib には軸の付近に軸の数値を描画するメソッドがありますが、そのメソッドでは本記事の数直線のように、両端の数字を ∞ のような __軸の実際の値と異なる値を表示__ することは __できません__。そこで、本記事では下記のプログラムのように、Axes の __`text`__ メソッドで __それぞれの数字__ を __座標を指定して描画__ することにします。

* __1、2 行目__：数直線上で __負の無限大__ と __正の無限大__ を __表す数直__ をそれぞれ変数に代入する
* __3 行目__：数直線と目盛りを描画する処理を、上記の変数を利用したプログラムに修正する。この __修正の目的__ は __数直線の範囲を簡単に変更できるようにする__ ことである
* __4 行目__：負の無限大を表す数値から、正の無限大を表す数値までの整数に対する繰り返し処理を行う
* __5 ~ 10 行目__：数直線の下に描画する文字列を計算する
* __11 行目__：上記で計算した文字列を、Axes の `text` メソッドを利用して目盛りの下に描画する。文字の x 座標は対応する目盛りの座標を指定し、実引数に [`ha="center"`](https://qiita.com/ysgeso/items/903dc60e865ff56191fc#%E5%AF%BE%E6%88%A6%E3%82%AB%E3%83%BC%E3%83%89%E3%81%AE%E8%A1%A8%E7%A4%BA%E5%A0%B4%E6%89%80%E3%81%AE%E5%BE%AE%E4%BF%AE%E6%AD%A3) を記述することでその位置に中央揃えで描画する。y 座標の -1 は筆者が試行錯誤して設定した値なので、変更したい場合は自由に変更すること

```python
元と同じなので省略
 1  minus_inf = -3
 2  plus_inf = 4
 3  ax.plot(range(minus_inf, plus_inf + 1), [0] * (plus_inf + 1 - minus_inf) , "|-k")
 4  for num in range(minus_inf, plus_inf + 1):
 5      if num == minus_inf:
 6          numtext = "-∞"
 7      elif num == plus_inf:
 8          numtext = "∞"
 9      else:
10          numtext = num
11      ax.text(num, -1, numtext, ha="center")    
```

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

```python
fig = plt.figure(figsize=(5, 1))
ax = fig.add_axes([0, 0, 1, 1])
ax.axis("off")
ax.set_xlim(-4, 14)
ax.set_ylim(-1.5, 1.5)

minus_inf = -3
plus_inf = 4
ax.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
    ax.text(num, -1, numtext, ha="center")
```
</details>

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

```diff_python
元と同じなので省略
-ax.plot(range(-3, 5), [0] * 8, "|-k")
+minus_inf = -3
+plus_inf = 4
+ax.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
+   ax.text(num, -1, numtext, ha="center")   
```
</details>

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/c5aa5ef6-ffdb-97c7-34db-bb39650700f0.png" width="350">

上記のプログラムを実行すると、実行結果のように数直線の下に目盛りの数値が意図したとおりに描画されるようになることが確認できます。

# α 値、β 値、子ノードの評価値の描画

次に、数直線上に __α 値、β 値、子ノードの評価値を描画__ します。その際に、どのように描画するかを決める必要があるのでその方法について少し考えてみて下さい。

## それぞれの値を表すマーカーの描画

本記事では、それぞれを __色のついた円で描画__ することにします。色のついた __1 つの円__ は、__`plot`__ メソッドの __マーカー__ を使って描画することができます。これまでは `plot` メソッドの実引数に複数の __頂点の座標の一覧__ を表すデータを記述しましたが、下記のプログラムのように 1 つ目と 2 つ目の実引数に __1 つの座標__ を表す x 座標と y 座標の __データを記述__ し、3 つ目の実引数で __マーカーの指定を記述__ することでその点にマーカーを描画することができます。

```python
fig, ax = plt.subplots()
ax.plot(1, 1, "or") # 赤い円のマーカー
ax.plot(3, 1, "^g") # 緑の三角形のマーカー
ax.plot(2, 2, "sk") # 黒い四角形のマーカー
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/ea553cc2-8af5-866d-9237-da5ab1822510.png" width="350">

下記は、__α 値__、__β 値__、__子ノードの評価値__ をそれぞれ __`alpha`__、__`beta`__、__`score`__ という変数に代入し、それらを数直線上に __円のマーカーで描画__ するプログラムです。本記事では、それぞれの色を赤、青、緑色で描画していますが、色を変えたい人は自由に変更して下さい。

* __1 ~ 3 行目__：α 値、β 値、子ノードの評価値を表す変数に、それぞれ何らかの値を代入する。本記事では -2、正の無限大、0 を代入した
* __4 行目__：__α 値__ を数直線上に __描画する座標（coordinate）を計算__ して __`alphacoord`__ に代入する。__`alpha` とは別の変数__ に計算した __値を代入する理由__ は、この後で __α 値の文字を描画する予定だから__ である。計算は組み込み関数 `max` を利用して、`alpha` に数直線上での負の無限大を表す `minus_inf` より小さい値が代入されていた場合は、`minus_inf` の値にする。なお、__αβ 法の性質__ から __α 値が__ 評価値の最大値である __3 より大きくなることはない__ ので、`alphacoord = min(plus_inf, alphacoord)` のような計算を行う必要はない
* __5 行目__：上記と同様の処理を `beta` に対しても行う
* __6 ~ 8 行目__：数直線上に、α 値、β 値、子ノードの評価値の値を表す円のマーカーを、それぞれ赤、青、緑色で描画する

```python
1  alpha = -2
2  beta = float("inf")
3  score = 0
元と同じなので省略   
4  alphacoord = max(minus_inf, alpha)
5  betacoord = min(plus_inf, beta)
6  ax.plot(alphacoord, 0, "or")
7  ax.plot(betacoord, 0, "ob")
8  ax.plot(score, 0, "og")
```

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

```python
alpha = -2
beta = float("inf")
score = 0

fig = plt.figure(figsize=(5, 1))
ax = fig.add_axes([0, 0, 1, 1])
ax.axis("off")
ax.set_xlim(-4, 14)
ax.set_ylim(-1.5, 1.5)

minus_inf = -3
plus_inf = 4
ax.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
    ax.text(num, -1, numtext, ha="center")
    
alphacoord = max(minus_inf, alpha)
betacoord = min(plus_inf, beta)
ax.plot(alphacoord, 0, "or")
ax.plot(betacoord, 0, "ob")
ax.plot(score, 0, "og")
```
</details>

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

```diff_python
alpha = -2
score = 0
beta = float("inf")
元と同じなので省略   
+alphacoord = max(minus_inf, alpha)
+betacoord = min(plus_inf, beta)
+ax.plot(alphacoord, 0, "or")
+ax.plot(scorecoord, 0, "og")
+ax.plot(beta, 0, "ob")
```
</details>

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/c0504c06-85d7-4e78-0c4d-d8c2fbf20124.png" width="350">

上記のプログラムを実行すると、実行結果のように数直線上の α 値、β 値、子ノードの評価値を表す位置に __マーカーが描画__ されます。その際に、__正の無限大__ が設定された __β 値__ が __正しい位置に描画される__ ことが確認できます。興味がある方は、1 ~ 3 行目を別の値を代入するように変更しても[^5]正しい位置に描画されることを確認して下さい。

## 凡例の描画

数直線上に描画した 3 つのマーカーは色で区別することはできますが、それぞれが __どの値に対応するか__ が __わかりづらい__ という問題があります。そこで、グラフなどでは一般的に下図の右にある __凡例__ を描画するという工夫が良く行われます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/25e9d1db-abab-bed2-9f67-70e11286ec28.png" width="350">

matplotlib では、凡例を下記の方法で簡単に描画することができます。

* __`plot`__ メソッドに、凡例に描画する文字列を表す __キーワード引数 `label`__ を記述する
* Axes の __`legend`__ メソッドを呼び出すと、上記のキーワード引数 `label` で指定したデータが記述された __凡例が描画__ される

:::note
凡例に描画する文字列を表すキーワード引数 `label` は、`plot` 以外の様々なグラフを描画するメソッドでも共通して利用できます。
:::

下記は先ほどの α 値などのマーカーを描画した数直線の図に、凡例を描画するように修正したプログラムです。

* __1 ~ 3 行目__：それぞれの `plot` メソッドにキーワード引数 `label` を記述する
* __4 行目__：Axes の `legend` メソッドを呼び出して凡例を描画する

```python
元と同じなので省略
1  ax.plot(alpha, 0, "or", label="alpha")
2  ax.plot(beta, 0, "ob", label="beta")
3  ax.plot(score, 0, "og", label="score")
4  ax.legend()
```

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

```python
alpha = -2
beta = float("inf")
score = 0

fig = plt.figure(figsize=(5, 1))
ax = fig.add_axes([0, 0, 1, 1])
ax.axis("off")
ax.set_xlim(-4, 14)
ax.set_ylim(-1.5, 1.5)

minus_inf = -3
plus_inf = 4
ax.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
    ax.text(num, -1, numtext, ha="center")
    
alphacoord = max(minus_inf, alpha)
betacoord = min(plus_inf, beta)
ax.plot(alphacoord, 0, "or", label="alpha")
ax.plot(betacoord, 0, "ob", label="beta")
ax.plot(score, 0, "og", label="score")
plt.legend()
```
</details>

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

```diff_python
元と同じなので省略
-ax.plot(alphacoord, 0, "or")
+ax.plot(alphacoord, 0, "or", label="alpha")
-ax.plot(betacoord, 0, "ob")
+ax.plot(betacoord, 0, "ob", label="beta")
-ax.plot(score, 0, "og")
+ax.plot(score, 0, "og", label="score")
+plt.legend()
```
</details>

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/25e9d1db-abab-bed2-9f67-70e11286ec28.png" width="350">

上記のプログラムを実行すると、実行結果のように凡例が右に描画されます。

:::note
__`legend`__ メソッドによって描画される __凡例の位置__ は __自動的__ に空いている場所が探されて __配置が行われます__ が、__自分で凡例の描画位置を設定__ することもできます。詳しくは、下記の Axes の `legend` メソッドのドキュメントを参照して下さい。
:::

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

## 注釈の描画

上記の凡例は、折れ線グラフや棒グラフのような場合はわかりやすいのですが、上記のような __点に対する凡例__ の場合は __思ったほどわかりやすくありません__。

そのような場合は、__Axes__ の __`annotate`__ というメソッドを利用することで、__矢印を使って__ 特定の座標に対する __注釈（annotation）を描画する__ という方法があります。

Axes の __`annotate`__ メソッドは、主に以下の表のような仮引数を持ちます。

| 仮引数 | 意味 |
|:--|:--|
| __`text`__  | 注釈として描画する文字列  |
| __`xy`__  | 注釈の対象となる座標を表す tuple  |
| __`xytext`__  | 注釈の文字列を描画する座標を表す tuple   |
| __`arrowprops`__  | 注釈と注釈の対象となる座標を結ぶ線の矢印の設定を表すデータ。<br>__`{ "arrowstyle": "->"}`__ という dict を記述することで矢印が描画される。<br>なお、この仮引数に対応する実引数を省略すると矢印の線は描画されない  |
| __`ha`__ | `text` メソッドと同様の方法で、文字列の描画の揃えを指定できる |

下記は `annotate` メソッドを利用して、数直線上の __α 値__、__β 値__、__子ノードの評価値__ に __注釈を描画__ するプログラムです。なお、本記事では注釈の中に具体的な値を描画するようにしましたが、具体的な値が必要ないと思った方は描画しないように修正して下さい。

* __1 行目__：`annotate` メソッドの仮引数 `arrowprops` に代入する値は、3 つの `annotate` メソッドを呼び出す処理で共通なので、変数に代入しておく
* __3、4 行目__：`annotate` メソッドを呼びだして、「α = α 値」という注釈を描画する処理を記述する。注釈の __描画位置__ は、__負の無限大__ の座標の __真上__ に、__中央揃えで描画__ するように設定した。なお、先程数直線上の α 値を描画する座標を `alphacoord` という、`alpha` とは別の変数に代入するようにしたのは、__α 値が負の無限大__ だった場合に「α = -3」ではなく __「α = -inf」という注釈を描画__ できるようにするためである
* __6、7、9、10 行目__：β 値と子ノードの評価値に対しても同様の処理を行う。なお、β 値の注釈は正の無限大の座標の真上に、子ノードの評価値の注釈は数直線の中央の座標である `(minus_inf + plus_inf) / 2` の真上に中央揃えで描画するように設定した

```python
元と同じなので省略
 1  arrowprops = { "arrowstyle": "->"}
 2  ax.plot(alphacoord, 0, "or")
 3  ax.annotate(f"α = {alpha}", xy=(alphacoord, 0), xytext=(minus_inf, 1),
 4                arrowprops=arrowprops, ha="center")
 5  ax.plot(betacoord, 0, "ob")
 6  ax.annotate(f"β = {beta}", xy=(betacoord, 0), xytext=(plus_inf, 1),
 7                arrowprops=arrowprops, ha="center")
 8  ax.plot(score, 0, "og")
 9  ax.annotate(f"score = {score}", xy=(score, 0), xytext=((minus_inf + plus_inf) / 2, 1),
10                arrowprops=arrowprops, ha="center")
```

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

```python
alpha = -2
beta = float("inf")
score = 0

fig = plt.figure(figsize=(5, 1))
ax = fig.add_axes([0, 0, 1, 1])
ax.axis("off")
ax.set_xlim(-4, 14)
ax.set_ylim(-1.5, 1.5)

minus_inf = -3
plus_inf = 4
ax.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
    ax.text(num, -1, numtext, ha="center")
    
alphacoord = max(minus_inf, alpha)
betacoord = min(plus_inf, beta)
arrowprops = { "arrowstyle": "->"}
ax.plot(alphacoord, 0, "or")
ax.annotate(f"α = {alpha}", xy=(alphacoord, 0), xytext=(minus_inf, 1),
              arrowprops=arrowprops, ha="center")
ax.plot(betacoord, 0, "ob")
ax.annotate(f"β = {beta}", xy=(betacoord, 0), xytext=(plus_inf, 1),
              arrowprops=arrowprops, ha="center")
ax.plot(score, 0, "og")
ax.annotate(f"score = {score}", xy=(score, 0), xytext=((minus_inf + plus_inf) / 2, 1),
              arrowprops=arrowprops, ha="center")
```
</details>

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

```diff_python
元と同じなので省略
+arrowprops = { "arrowstyle": "->"}
ax.plot(alphacoord, 0, "or")
+ax.annotate(f"α = {alpha}", xy=(alphacoord, 0), xytext=(minus_inf, 1),
+              arrowprops=arrowprops, ha="center")
ax.plot(betacoord, 0, "ob")
+ax.annotate(f"β = {beta}", xy=(betacoord, 0), xytext=(plus_inf, 1),
+              arrowprops=arrowprops, ha="center")
ax.plot(score, 0, "og")
+ax.annotate(f"score = {score}", xy=(score, 0), xytext=((minus_inf + plus_inf) / 2, 1),
+              arrowprops=arrowprops, ha="center")
```
</details>

`annotate` メソッドに関する詳細については、下記のリンク先を参照して下さい。

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

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/2b51e1ab-7a16-8a3c-d8db-a4addfae37e7.png" width="350">

上記のプログラムを実行すると、実行結果のように注釈が描画されるようになります。

# Mbtree_Anim の修正

次に、上記の __数直線の図__ が Mbtree_Anim で描画される __ゲーム木の Figure の上に描画__ されるように Mbtree_Anim を修正することにします。

## Mbtree クラスの `calc_score_by_ab` の修正

Mbtree_Anim の処理で、それぞれの __アニメーションのフレーム__ で α 値、β 値、__子ノードの評価値を数直線上に描画__ するためには、それらの値を __`ab_list_by_score`__ 属性の __要素に記録__ しておく必要があります。しかし、現状では __`ab_list_by_score`__ の要素には __`(α 値, β 値, メッセージ)`__ という tuple が代入されており、__子ノードの評価値__ を表すデータは __記録されていません__。そこで、`ab_list_by_score` の要素に __`(α 値, β 値, 子ノードの評価値, メッセージ)`__ という tuple が代入されるように、__Mbtree__ クラスの __`calc_score_by_ab` を修正__ することにします。なお、この tuple の __子ノードの評価値の要素__ には、子ノードの評価値が __計算された直後のみ__ その __評価値を代入__ し、__それ以外の場合は `None` を代入する__ ことにします。

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

* __12、25 行目__：__子ノードの評価値が計算された直後__ では、`ablist_by_score` に代入する tuple の要素に __子ノードの評価値を代入__ するように修正する
* __9、16、19、29、32、36 行目__：__それ以外__ の場合は、tuple の子ノードの評価値を表す要素に __`None` を代入__ するように修正する

```python
 1  from tree import Mbtree
 2  from marubatsu import Marubatsu
 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, None, "このノードの処理の開始"))
元と同じなので省略
10                      score = calc_ab_score(childnode, alpha, beta)
11                      self.nodelist_by_score.append(node)
12                      self.ablist_by_score.append((alpha, beta, score, f"child score = {score}"))
13                      if score >= beta:
14                          alpha = score
15                          self.nodelist_by_score.append(node)
16                          self.ablist_by_score.append((alpha, beta, None, f"child score = {score} ≧ {beta}(β値) によるβ狩り"))
17                          break
元と同じなので省略
18                      self.nodelist_by_score.append(node)
19                      self.ablist_by_score.append((alpha, beta, None, message))
20                  node.score = alpha
21              else:
22                  for childnode in node.children:
23                      score = calc_ab_score(childnode, alpha, beta)
24                      self.nodelist_by_score.append(node)
25                      self.ablist_by_score.append((alpha, beta, score, f"child score = {score}"))
26                      if score <= alpha:
27                          beta = score
28                          self.nodelist_by_score.append(node)
29                          self.ablist_by_score.append((alpha, beta, None, f"child score = {score} ≦ {alpha}(α値)によるα狩り"))
30                          break
元と同じなので省略
31                      self.nodelist_by_score.append(node)
32                      self.ablist_by_score.append((alpha, beta, None, message))
33                  node.score = beta
34
35          self.nodelist_by_score.append(node)
36          self.ablist_by_score.append((alpha, beta, None, f"このノードの評価値が {node.score} で確定"))
元と同じなので省略
37    
38  Mbtree.calc_score_by_ab = calc_score_by_ab
```

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

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

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, None, "このノードの処理の開始"))
        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, score, f"child score = {score}"))
                    if score >= beta:
                        alpha = score
                        self.nodelist_by_score.append(node)
                        self.ablist_by_score.append((alpha, beta, None, 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, None, 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, score, f"child score = {score}"))
                    if score <= alpha:
                        beta = score
                        self.nodelist_by_score.append(node)
                        self.ablist_by_score.append((alpha, beta, None, 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, None, message))
                node.score = beta

        self.nodelist_by_score.append(node)
        self.ablist_by_score.append((alpha, beta, None, 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
from tree import Mbtree
from marubatsu import Marubatsu

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, None, "このノードの処理の開始"))
元と同じなので省略
                    score = calc_ab_score(childnode, alpha, beta)
                    self.nodelist_by_score.append(node)
-                   self.ablist_by_score.append((alpha, beta, f"child score = {score}"))
+                   self.ablist_by_score.append((alpha, beta, score, 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}(β値) によるβ狩り"))
+                       self.ablist_by_score.append((alpha, beta, None, f"child score = {score} ≧ {beta}(β値) によるβ狩り"))
                        break
元と同じなので省略
                    self.nodelist_by_score.append(node)
-                   self.ablist_by_score.append((alpha, beta, message))
+                   self.ablist_by_score.append((alpha, beta, None, 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}"))
+                   self.ablist_by_score.append((alpha, beta, score, 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}(α値)によるα狩り"))
+                       self.ablist_by_score.append((alpha, beta, None, f"child score = {score} ≦ {alpha}(α値)によるα狩り"))
                        break
元と同じなので省略
                    self.nodelist_by_score.append(node)
-                   self.ablist_by_score.append((alpha, beta, message))
+                   self.ablist_by_score.append((alpha, beta, None, message))
                node.score = beta

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

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

次に、__Mbtree_Anim__ クラスの __`create_widgets`__ メソッドを、下記のプログラムのように __αβ 法の評価値の計算手順を描画する場合のみ__、__Figure を作成する__ 処理を記述します。作成した Figure と Axes は `abfig` と `abax` 属性に代入し、__Figure を作成しなかった場合は `abfig` 属性に `None` を代入__ することにします。

* __9 ~ 12 行目__：この部分は `with plt.ioff()` のブロックの後に記述していたが、この後の 13 ~ 19 行目の記述にあわせて、ここに記述するように修正した
* __13 行目__：αβ 法の評価値の計算手順を描画するかどうかを[前回の記事](https://qiita.com/ysgeso/items/a206729687dfd28bfa9e#mbtree_anim-%E3%82%AF%E3%83%A9%E3%82%B9%E3%81%AE-update_gui-%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%AE%E4%BF%AE%E6%AD%A3)と同様の方法で判定する
* __14、15 行目__：数直線を描画するための Figure と Axes を先程と同様の設定で作成する
* __16 ~ 19 行目__：9 ~ 12 行目と同様に、作成した Figure のツールバーなどが表示されないように設定する
* __20、21 行目__：数直線を描画するための Figure を作成しない場合は、`abfig` 属性に `None` を代入する

```python
 1  from tree import Mbtree_Anim
 2  import ipywidgets as widgets
 3
 4  def create_widgets(self):
元と同じなので省略
 5      with plt.ioff():
 6          self.fig = plt.figure(figsize=[self.width * self.size,
 7                                         self.height * self.size])
 8          self.ax = self.fig.add_axes([0, 0, 1, 1])
 9          self.fig.canvas.toolbar_visible = False
10          self.fig.canvas.header_visible = False
11          self.fig.canvas.footer_visible = False
12          self.fig.canvas.resizable = False
13          if self.isscore and hasattr(self.mbtree, "ablist_by_score"):
14              self.abfig = plt.figure(figsize=(5, 1))
15              self.abax = self.abfig.add_axes([0, 0, 1, 1])
16              self.abfig.canvas.toolbar_visible = False
17              self.abfig.canvas.header_visible = False
18              self.abfig.canvas.footer_visible = False
19              self.abfig.canvas.resizable = False 
20          else:
21              self.abfig = None
22        
23  Mbtree_Anim.create_widgets = create_widgets
```

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

```python
from tree import Mbtree_Anim
import ipywidgets as widgets

def create_widgets(self):
    self.play = widgets.Play(max=self.nodenum - 1, interval=500)
    self.prev_button = self.create_button("<", width=30)
    self.next_button = self.create_button(">", width=30)
    self.frame_slider = widgets.IntSlider(max=self.nodenum - 1, description="frame")
    self.interval_slider = widgets.IntSlider(value=500, min=1, max=2000, description="interval")
    widgets.jslink((self.play, "value"), (self.frame_slider, "value"))    
    widgets.jslink((self.play, "interval"), (self.interval_slider, "value"))

    with plt.ioff():
        self.fig = plt.figure(figsize=[self.width * self.size,
                                        self.height * self.size])
        self.ax = self.fig.add_axes([0, 0, 1, 1])
        self.fig.canvas.toolbar_visible = False
        self.fig.canvas.header_visible = False
        self.fig.canvas.footer_visible = False
        self.fig.canvas.resizable = False 
        if self.isscore and hasattr(self.mbtree, "ablist_by_score"):
            self.abfig = plt.figure(figsize=(5, 1))
            self.abax = self.abfig.add_axes([0, 0, 1, 1])
            self.abfig.canvas.toolbar_visible = False
            self.abfig.canvas.header_visible = False
            self.abfig.canvas.footer_visible = False
            self.abfig.canvas.resizable = False 
        else:
            self.abfig = None
        
Mbtree_Anim.create_widgets = create_widgets
```
</details>

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

```diff_python
from tree import Mbtree_Anim
import ipywidgets as widgets

def create_widgets(self):
元と同じなので省略
    with plt.ioff():
        self.fig = plt.figure(figsize=[self.width * self.size,
                                        self.height * self.size])
        self.ax = self.fig.add_axes([0, 0, 1, 1])
+       self.fig.canvas.toolbar_visible = False
+       self.fig.canvas.header_visible = False
+       self.fig.canvas.footer_visible = False
+       self.fig.canvas.resizable = False 
+       if self.isscore and hasattr(self.mbtree, "ablist_by_score"):
+           self.abfig = plt.figure(figsize=(5, 1))
+           self.abax = self.abfig.add_axes([0, 0, 1, 1])
+           self.abfig.canvas.toolbar_visible = False
+           self.abfig.canvas.header_visible = False
+           self.abfig.canvas.footer_visible = False
+           self.abfig.canvas.resizable = False 
+       else:
+           self.abfig = None
-   self.fig.canvas.toolbar_visible = False
-   self.fig.canvas.header_visible = False
-   self.fig.canvas.footer_visible = False
-   self.fig.canvas.resizable = False         
        
Mbtree_Anim.create_widgets = create_widgets
```
</details>

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

次に、__Mbtree_Anim__ クラスの __`display_widgets`__ メソッドを、下記のプログラムの 5、6 行目のように __`abfig` に Figure が代入__ されている場合のみ、ゲーム木を描画する Figure の上に `abfig` の Figure を配置するように修正します。

```python
1  def display_widgets(self):
2      hbox = widgets.HBox([self.play, self.prev_button, self.next_button, self.frame_slider, self.interval_slider])
3      if self.abfig is None:
4           display(widgets.VBox([hbox, self.fig.canvas])) 
5      else:
6           display(widgets.VBox([hbox, self.abfig.canvas, self.fig.canvas])) 
7
8  Mbtree_Anim.display_widgets = display_widgets
```

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

```python
def display_widgets(self):
    hbox = widgets.HBox([self.play, self.prev_button, self.next_button, self.frame_slider, self.interval_slider])
    if self.abfig is None:
        display(widgets.VBox([hbox, self.fig.canvas])) 
    else:
        display(widgets.VBox([hbox, self.abfig.canvas, self.fig.canvas])) 

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

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

```diff_python
def display_widgets(self):
    hbox = widgets.HBox([self.play, self.prev_button, self.next_button, self.frame_slider, self.interval_slider])
-   display(widgets.VBox([hbox, self.fig.canvas])) 
+   if self.abfig is None:
+       display(widgets.VBox([hbox, self.fig.canvas])) 
+   else:
+       display(widgets.VBox([hbox, self.abfig.canvas, self.fig.canvas])) 

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

### Mbtree_Anim クラスの `update_ab` メソッドの定義

これまでは、Mbtree_Anim クラスの `update_gui` メソッドの中で α 値などに関する描画を行っていましたが、__プログラムが長くなる__ ので __αβ 法の計算手順に関する描画を__ 行う __`update_ab`__ という下記のようなメソッドを定義する事にします。

* __4 ~ 10 行目__：`update_gui` の中で記述されていた、α 値などを描画する処理と同じ。ただし 5 行目のみ、先程行った `ab_list_by_score` に代入された list の要素を修正したのにあわせて、子ノードの評価値を `score` に代入するように修正した
* __12 行目__：数直線を描画する Axes の表示をクリアする
* __13 ~ 41 行目__：先程の数直線や α 値などを描画するプログラムと同じ。ただし、`ax` を `self.abax` に修正し、38 ~ 41 行目では __`score` が `None` ではない場合のみ__ 子ノードの評価値を表すデータを描画するように修正した

```python
 1  from marubatsu import Marubatsu
 2
 3  def update_ab(self):
 4      fontsize = 70 * self.size
 5      alpha, beta, score, message = self.mbtree.ablist_by_score[self.play.value]
 6      acolor = "red" if self.selectednode.mb.turn == Marubatsu.CIRCLE else "black"
 7      bcolor = "black" if self.selectednode.mb.turn == Marubatsu.CIRCLE else "red"
 8      self.ax.text(0, -1, f"α: {alpha}", fontsize=fontsize, c=acolor)        
 9      self.ax.text(9, -1, f"β: {beta}", fontsize=fontsize, c=bcolor)        
10      self.ax.text(19, -1, message, fontsize=fontsize)             
11                          
12      self.abax.clear()
13      self.abax.set_xlim(-4, 14)
14      self.abax.set_ylim(-1.5, 1.5)
15      self.abax.axis("off")
16
17      minus_inf = -3
18      plus_inf = 4
19      self.abax.plot(range(minus_inf, plus_inf + 1), [0] * (plus_inf + 1 - minus_inf) , "|-k")
20      for num in range(minus_inf, plus_inf + 1):
21          if num == minus_inf:
22            numtext = "-∞"
23          elif num == plus_inf:
24              numtext = "∞"
25          else:
26              numtext = num
27          self.abax.text(num, -1, numtext, ha="center")
28          
29      alphacoord = max(minus_inf, alpha)
30      betacoord = min(plus_inf, beta)
31      arrowprops = { "arrowstyle": "->"}
32      self.abax.plot(alphacoord, 0, "or")
33      self.abax.annotate(f"α = {alpha}", xy=(alphacoord, 0), xytext=(minus_inf, 1),
34                         arrowprops=arrowprops, ha="center")
35      self.abax.plot(betacoord, 0, "ob")
36      self.abax.annotate(f"β = {beta}", xy=(betacoord, 0), xytext=(plus_inf, 1),
37                         arrowprops=arrowprops, ha="center")
38      if score is not None:
39          self.abax.plot(score, 0, "og")
40          self.abax.annotate(f"score = {score}", xy=(score, 0), xytext=((minus_inf + plus_inf) / 2, 1),
41                             arrowprops=arrowprops, ha="center")
42                            
43  Mbtree_Anim.update_ab = update_ab
```

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

```python
from marubatsu import Marubatsu

def update_ab(self):
    fontsize = 70 * self.size
    alpha, beta, score, 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)             
                            
    self.abax.clear()
    self.abax.set_xlim(-4, 14)
    self.abax.set_ylim(-1.5, 1.5)
    self.abax.axis("off")

    minus_inf = -3
    plus_inf = 4
    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")
        
    alphacoord = max(minus_inf, alpha)
    betacoord = min(plus_inf, beta)
    arrowprops = { "arrowstyle": "->"}
    self.abax.plot(alphacoord, 0, "or")
    self.abax.annotate(f"α = {alpha}", xy=(alphacoord, 0), xytext=(minus_inf, 1),
                       arrowprops=arrowprops, ha="center")
    self.abax.plot(betacoord, 0, "ob")
    self.abax.annotate(f"β = {beta}", xy=(betacoord, 0), xytext=(plus_inf, 1),
                       arrowprops=arrowprops, ha="center")
    if score is not None:
        self.abax.plot(score, 0, "og")
        self.abax.annotate(f"score = {score}", xy=(score, 0), xytext=((minus_inf + plus_inf) / 2, 1),
                           arrowprops=arrowprops, ha="center")
                            
Mbtree_Anim.update_ab = update_ab
```
</details>

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

```diff_python
from marubatsu import Marubatsu

def update_ab(self):
    fontsize = 70 * self.size
-   alpha, beta, message = self.mbtree.ablist_by_score[self.play.value]
+   alpha, beta, score, 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)             
                            
+   self.abax.clear()
-   ax.set_xlim(-4, 14)
+   self.abax.set_xlim(-4, 14)
-   ax.set_ylim(-1.5, 1.5)
+   self.abax.set_ylim(-1.5, 1.5)
-   ax.axis("off")
+   self.abax.axis("off")

    minus_inf = -3
    plus_inf = 4
-   ax.plot(range(minus_inf, plus_inf + 1), [0] * (plus_inf + 1 - minus_inf) , "|-k")
+   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
-       ax.text(num, -1, numtext, ha="center")
+       self.abax.text(num, -1, numtext, ha="center")
        
    alphacoord = max(minus_inf, alpha)
    betacoord = min(plus_inf, beta)
    arrowprops = { "arrowstyle": "->"}
-   ax.plot(alphacoord, 0, "or")
+   self.abax.plot(alphacoord, 0, "or")
-   ax.annotate(f"α = {alpha}", xy=(alphacoord, 0), xytext=(minus_inf, 1),
+   self.abax.annotate(f"α = {alpha}", xy=(alphacoord, 0), xytext=(minus_inf, 1),
                       arrowprops=arrowprops, ha="center")
-   ax.plot(betacoord, 0, "ob")
+   self.abax.plot(betacoord, 0, "ob")
-   ax.annotate(f"β = {beta}", xy=(betacoord, 0), xytext=(plus_inf, 1),
+   self.abax.annotate(f"β = {beta}", xy=(betacoord, 0), xytext=(plus_inf, 1),
                       arrowprops=arrowprops, ha="center")
-   ax.plot(score, 0, "og")
-   ax.annotate(f"score = {score}", xy=(score, 0), xytext=((minus_inf + plus_inf) / 2, 1),
-                 arrowprops=arrowprops, ha="center")
+   if score is not None:
+       self.abax.plot(score, 0, "og")
+       self.abax.annotate(f"score = {score}", xy=(score, 0), xytext=((minus_inf + plus_inf) / 2, 1),
+                          arrowprops=arrowprops, ha="center")
                            
Mbtree_Anim.update_ab = update_ab
```
</details>

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

最後に、__Mbtree_Anim__ クラスの __`update_gui`__ メソッドを下記のプログラムの 5、6 行目のように、元々あった __α 値などを描画する処理を削除__ し、__`self.abfig` が `None` でない__ 場合のみ上記で定義した __`update_ab` メソッドを呼び出す__ ように修正します。

```python
1  def update_gui(self):
元と同じなので省略
2      self.mbtree.draw_subtree(centernode=self.centernode, selectednode=self.selectednode,
3                               anim_frame=self.play.value, isscore=self.isscore, 
4                               ax=self.ax, maxdepth=maxdepth, size=self.size)
5      if self.abfig is not None:
6          self.update_ab()
元と同じなので省略   
7    
8  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.abfig is not None:
        self.update_ab()
                            
    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.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)    
+   if self.abfig is not None:
+       self.update_ab()
元と同じなので省略   
    
Mbtree_Anim.update_gui = update_gui
```
</details>

## プログラムの動作の確認

上記の修正後に下記のプログラムで αβ 法で評価値を計算した `mbtree` に対して Mbtree_Anim を実行すると、実行結果のように __ゲーム木の上部__ に __数直線と α 値、β 値が描画__ されるようになることが確認できます。

```python
mbtree = Mbtree(algo="df")
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/27c7b900-ed40-d7e4-74f6-b7237299a62e.png" width="500">

下図は赤枠の最初の子ノードの評価値が計算された直後の 9 フレーム目の画像で、__上部に計算された子ノードの評価値が緑色の円で描画__ され、その上に評価値の注釈が描画されるようになったことが確認できます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/a7d6081f-7342-6286-a960-69a0979cbf4d.png" width="500">

下図はその次の 10 フレーム目の画像で、上図の赤枠のノードの __α 値が評価値の値で更新されている__ ことが確認できます。上図から下図に __アニメーションをコマ送りにする__ ことで、__α 値が具体的にどのように変化するかが__ 文字で α 値を描画するよりも __直観的にわかりやすく表現される__ ようになります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/eb282443-c83f-de09-6dac-7eb31d5b6882.png" width="500">

下記の 2 つの図は __α 狩り__ が行われる __前後のフレームの図__ です。上下のフレームの図を見比べた際に、__α 狩りによって β 値が更新__ されたのか、α 狩りが行われずに __子ノードの評価値が β 値未満になったため β 値が更新__ されたのかの __違いがあまり明確ではありません__。そこで、次回の記事では __その違いが直観的にわかりやすくなる__ ように改良することにします。どのように改良すればよいかについて余裕がある方は考えてみて下さい。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/7bf85f74-bf90-94bf-624b-1acfa6012e36.png" width="500">

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/da965581-e3d0-cf8e-fb6d-24f25093e219.png" width="500">

# 今回の記事のまとめ

今回の記事では、__数直線__ を使って α 値、β 値、子ノードの評価値を視覚化することで、__αβ 法の評価値の計算手順__ がより __わかりやすくなるような工夫__ を行ないました。

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

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

# 次回の記事

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

[^1]:軸を表示すると x 軸と y 軸の両方が表示されてしまうようです。matplotlib で数直線を簡単に表示する方法をご存じの方がいればコメントで教えて頂ければ助かります
[^2]:オリジナルのマーカーを作成することもできます。興味がある方は [こちら](https://matplotlib.org/stable/api/_as_gen/matplotlib.markers.MarkerStyle.html#matplotlib.markers.MarkerStyle) のリンク先などを参照して下さい.
[^3]:`add_axes` を利用する理由について忘れた方は、[以前の記事](https://qiita.com/ysgeso/items/0335cc42055729d0fac3#matplotlib-widget-%E3%81%AB%E3%82%88%E3%82%8B-axes-%E3%81%AE%E9%85%8D%E7%BD%AE%E4%BD%8D%E7%BD%AE%E3%81%AE%E5%A4%89%E5%8C%96)を復習して下さい
[^4]:`plot` で折れ線の頂点の __x 座標の一覧を表すデータ__ は、このように直接 __`range` を使って指定__ することができるので、わざわざ `[num for num in range(-3, 5)]` のように list の形式で記述する必要はありません
[^5]:ただし、αβ 法の性質からそれぞれの値は -∞ ≦ `alpha` ≦ 3、-2 ≦ `beta` ≦ ∞、-2 ≦ `score` ≦ 3 になるので、その範囲を超えないような値を設定する必要があります