Pythonで〇×ゲームのAIを一から作成する　その91　ゲーム木の視覚化の処理のバグの修正と改良

# 目次と前回の記事

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

https://qiita.com/ysgeso/items/949d18e60939d510c931

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

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

https://github.com/ysgeso/marubatsu/blob/master/091/marubatsu.py

https://github.com/ysgeso/marubatsu/blob/master/091/ai.py

https://github.com/ysgeso/marubatsu/blob/master/091/util.py

https://github.com/ysgeso/marubatsu/blob/master/091/tree.py

## ルールベースの AI の一覧

ルールベースの AI の一覧については、下記の記事を参照して下さい。

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

# `draw_node` のバグの修正

前回の記事で修正した `draw_node` にはいくつかのバグがある事がわかりましたので、最初にその修正を行うことにします。

## 深さが 9 以外の部分木を描画した際のバグ

1 つ目のバグは、`draw_tree` で __深さが 9 以外__ の部分木を描画すると、エラーが発生したり、最も深いノードのエッジの描画がおかしくなる場合があるというものです。下記は、ルートノードから深さ 1 までの部分木を描画するプログラムで、実行結果のような __エラーが発生__ します。また、その際に、実行結果のような画像が描画されます。

```python
from tree import Mbtree

mbtree = Mbtree()
mbtree.draw_tree(maxdepth=1)
```

実行結果

```
略
File c:\Users\ys\ai\marubatsu\091\tree.py:92, in Node.draw_node(self, ax, size, lw, dx, dy)
     90 else:
     91     if len(self.children) > 0:
---> 92         edgeheight = self.height - self.children[-1].height
     94 # 自分自身のノードを (dx, dy) に描画する
     95 Marubatsu_GUI.draw_board(ax, self.mb, lw=lw, dx=dx, dy=dy)

AttributeError: 'Node' object has no attribute 'height'
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/e87ba31d-f5ca-ef7d-b642-1a85d86f1201.png" width="100">

### エラーの原因の考察

エラーメッセージの `in Node.draw_node` から __`draw_node` の中でエラーが発生__ したことがわかります。また、描画される画像から __ルートノードとそのエッジは正しく描画される__ ので、問題は __深さ 1 のノード__ を `draw_node` で __描画した際に発生__ したことが推測できます。

下記は、`draw_node` の定義の一部でです。

```python
1 def draw_node(self, ax=None, size=0.25, lw=0.8, dx=0, dy=0):  
2     width = 8
3     if ax is None:
略
4     else:
5         if len(self.children) > 0:
6             edgeheight = self.height - self.children[-1].height
略
```

先程の、エラーメッセージから、上記の 6 行目で __AttributeError: 'Node' object has no attribute 'height'__ というエラーが発生していることがわかります。このエラーメッセージから、6 行目の `self` または `self.children[-1]` のいずれかに `height` という属性が存在しないことがわかります。

そこで、下記のプログラムで深さ 1 のノードとその子ノードの `height` 属性を表示してみると、実行結果から、深さ 1 のノードの `height` 属性には `4` が代入されていますが、__深さ 2 のノードには `height` 属性が存在しない__ ことが確認できます。

```python
print(mbtree.root.children[0].height)
print(mbtree.root.children[0].children[0].height)
```

実行結果

```
4
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[2], line 2
      1 print(mbtree.root.children[0].height)
----> 2 print(mbtree.root.children[0].children[0].height)

AttributeError: 'Node' object has no attribute 'height'
```

そこで、深さ 2 のノードの `height` 属性が存在しない理由について考察することにします。

ノードの __`height`__ 属性は、下記の `draw_tree` の 4 行目で `calc_node_height` を呼び出すことで、__深さが 0 ~ `maxdepth` までのノード__ に対して計算が行われます。先ほどのプログラムでは __`maxdepth` に `1` が代入__ されていたので、__深さが 0 と 1 のノード__ に対して高さが計算され、 `height` 属性にその値が代入されます。

```python
1  def draw_tree(self, startnode=None, size=0.25, lw=0.8, maxdepth=2):
2     if startnode is None:
3         startnode = self.root
4     self.calc_node_height(maxdepth)
略
```

先程エラーが発生した際の `self` は、深さ 1 のノードなので、__`self.children` は深さ 2 のノード__ を表しますが、上記で考察したように、`height` 属性が計算されたノードは深さ 0 と 1 のノードなので、__深さ 2 のノードには `height` 属性が存在しません__。これが、__AttributeError: 'Node' object has no attribute 'height'__ というエラーが発生した原因です。

なお、[前回の記事](https://qiita.com/ysgeso/items/949d18e60939d510c931#%E6%B7%B1%E3%81%95%E3%81%8C-6-%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%81%8B%E3%82%89%E3%81%AE%E9%83%A8%E5%88%86%E6%9C%A8%E3%81%AE%E6%8F%8F%E7%94%BB%E3%81%AE%E7%A2%BA%E8%AA%8D)で下記のプログラムを記述した際にエラーが発生しなかった原因は、描画する最も深いノードが、__子ノードが絶対に存在しない深さ 9 のノード__ だったからです。

```python
mbtree.draw_tree(mbtree.nodelist_by_depth[6][0], maxdepth=9)
```

深さが 9 のノードを `draw_node` で描画する場合は、下記の 1 行目の条件式が `False` になるので、エラーが発生する 2 行目のプログラムが実行されることはありません。

```python
if len(self.children) > 0:
    edgeheight = self.height - self.children[-1].height
```

### エッジの描画がおかしくなる例

上記ではエラーが発生しましたが、エラーが発生しない場合もあり、その場合は __エッジの描画がおかしくなります__。

例えば、下記のプログラムを実行して __深さが 6 のノードから深さ 9 までの部分木を描画した後__ で、先程と同じプログラムで深さ 2 までの部分木を描画すると、実行結果のように、深さが 1 のノードから子ノードへのエッジがおかしな描画が行われます。

```python
mbtree.draw_tree(mbtree.nodelist_by_depth[6][0], maxdepth=9)
mbtree.draw_tree(maxdepth=1)
```

実行結果（深さ 6 のノードからの画像は前回の記事と同じなので省略します）

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/4a3e35a8-49b8-3aaf-b777-4247852e223c.png" width="100">

このようなことが起きる原因は、以下の通りです。

* `mbtree.draw_tree(mbtree.nodelist_by_depth[6][0], maxdepth=9)` によって、__すべてのノードの高さが計算__ され、`height` 属性にその値が代入される
* `mbtree.draw_tree(maxdepth=1)` によって、__深さが 0 と 1 のノードの高さが計算__ されて `height` 属性に代入されるが、__それ以外の深さのノードに対しては何の処理も行われない__
* 従って、__深さが 2 以上のノードの `height` 属性__ には、`mbtree.draw_tree(mbtree.nodelist_by_depth[6][0], maxdepth=9)` によって計算されたノードの高さの値が __代入されたまま__ である

上記は、下記のプログラムで深さ 1 と 2 のノードの高さを表示することで確認できます。実行結果から、深さ 2 のノードの高さが 14672 という非常に高い値になっていることが確認できます。これは `mbtree.draw_tree(mbtree.nodelist_by_depth[6][0], maxdepth=9)` によって計算された、__ゲーム木全体を描画した場合__ の深さが 2 のノードの高さを表します。

```python
print(mbtree.root.children[0].height)
print(mbtree.root.children[0].children[0].height)
```

実行結果

```
4
14672
```

### 問題の修正

先程のエラーは、下記のプログラムで __最も深いノードから延びるエッジの高さ__ を計算する処理で発生します。また、もう一つの問題はエッジの描画がおかしくなるというものです。

```python
edgeheight = self.height - self.children[-1].height
```

しかし、よく考えてみると、そもそも最も深いノードから延びるエッジを __描画する必要はありません__。従って、この問題は、`draw_tree` で指定した __`maxdepth` の深さのノードを `draw_node` で描画する際__ に、__上記の計算を行わず__、__エッジを描画しないようにする__ という方法で解決できます。そこで、下記のプログラムのように `draw_node` に仮引数 `maxdepth` を追加し、その深さのノードの場合はエッジを描画しないように修正することにします。

* __5 行目__：互換性を考慮して、デフォルト値を `None` とする仮引数 `maxdepth` を追加する
* __7、13 行目__：子ノードが存在し、ノードの深さが `maxdepth` と等しくない場合にエッジの長さの計算と、エッジと子ノードの描画を行うように修正する

```python
 1  from marubatsu import Marubatsu_GUI
 2  from tree import Node
 3  import matplotlib.pyplot as plt
 4
 5  def draw_node(self, ax=None, maxdepth=None, size=0.25, lw=0.8, dx=0, dy=0):  
元と同じなので省略
 6      else:
 7          if len(self.children) > 0 and maxdepth != self.depth:
 8              edgeheight = self.height - self.children[-1].height
 9           
10      # 自分自身のノードを (dx, dy) に描画する
11      Marubatsu_GUI.draw_board(ax, self.mb, lw=lw, dx=dx, dy=dy)
12      # 子ノードが存在する場合に、エッジの線と子ノードを描画する
13      if len(self.children) > 0 and maxdepth != self.depth:   
元と同じなので省略
14            
15  Node.draw_node = draw_node
```

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

```python
from marubatsu import Marubatsu_GUI
from tree import Node
import matplotlib.pyplot as plt

def draw_node(self, ax=None, maxdepth=None, size=0.25, lw=0.8, dx=0, dy=0):  
    width = 8
    if ax is None:
        height = len(self.children) * 4
        fig, ax = plt.subplots(figsize=(width * size, height * size))
        ax.set_xlim(0, width)
        ax.set_ylim(0, height)   
        ax.invert_yaxis()
        ax.axis("off")
        for childnode in self.children:
            childnode.height = 4
        if len(self.children) > 0:
            edgeheight = height - 4
    else:
        if len(self.children) > 0 and maxdepth != self.depth:
            edgeheight = self.height - self.children[-1].height
            
    # 自分自身のノードを (dx, dy) に描画する
    Marubatsu_GUI.draw_board(ax, self.mb, lw=lw, dx=dx, dy=dy)
    # 子ノードが存在する場合に、エッジの線と子ノードを描画する
    if len(self.children) > 0 and maxdepth != self.depth:   
        plt.plot([dx + 3.5, dx + 4, dx + 4], [dy + 1.5, dy + 1.5, dy + 1.5 + edgeheight], c="k", lw=lw)
        for childnode in self.children:
            plt.plot([dx + 4 , dx + 4.5], [dy + 1.5, dy + 1.5], c="k", lw=lw)
            dy += childnode.height
            
Node.draw_node = draw_node
```
</details>

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

```diff_python
from marubatsu import Marubatsu_GUI
from tree import Node
import matplotlib.pyplot as plt

def draw_node(self, ax=None, maxdepth=None, size=0.25, lw=0.8, dx=0, dy=0):  
元と同じなので省略
    else:
-       if len(self.children) > 0:
+       if len(self.children) > 0 and maxdepth != self.depth:
            edgeheight = self.height - self.children[-1].height
            
    # 自分自身のノードを (dx, dy) に描画する
    Marubatsu_GUI.draw_board(ax, self.mb, lw=lw, dx=dx, dy=dy)
    # 子ノードが存在する場合に、エッジの線と子ノードを描画する
-   if len(self.children) > 0:   
+   if len(self.children) > 0 and maxdepth != self.depth:   
元と同じなので省略
            
Node.draw_node = draw_node
```
</details>

次に、下記のプログラムの 10 行目のように、`draw_tree` 内で `draw_node` を呼び出す際に、キーワード引数 `maxdepth=maxdepth` を記述して、`maxdepth` の深さのノードのエッジが表示されないように修正します。

```python
 1  def draw_tree(self, startnode=None, size=0.25, lw=0.8, maxdepth=2):
元と同じなので省略
 2      while len(nodelist) > 0 and depth <= maxdepth:        
 3          dy = 0
 4          childnodelist = []
 5          for node in nodelist:
 6              if node is None:
 7                  dy += 4
 8                  childnodelist.append(None)
 9              else:
10                  node.draw_node(ax=ax, maxdepth=maxdepth, size=size, lw=lw, dx=dx, dy=dy)
元と同じなので省略
11        
12  Mbtree.draw_tree = draw_tree
```

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

```python
def draw_tree(self, startnode=None, size=0.25, lw=0.8, maxdepth=2):
    if startnode is None:
        startnode = self.root
    self.calc_node_height(maxdepth)
    width = 5 * (maxdepth - startnode.depth + 1)
    height = startnode.height
    fig, ax = plt.subplots(figsize=(width * size, height * size))
    ax.set_xlim(0, width)
    ax.set_ylim(0, height)   
    ax.invert_yaxis()
    ax.axis("off")        
    
    nodelist = [startnode]
    depth = startnode.depth
    dx = 0
    while len(nodelist) > 0 and depth <= maxdepth:        
        dy = 0
        childnodelist = []
        for node in nodelist:
            if node is None:
                dy += 4
                childnodelist.append(None)
            else:
                node.draw_node(ax=ax, maxdepth=maxdepth, size=size, lw=lw, dx=dx, dy=dy)
                dy += node.height
                if len(node.children) > 0:  
                    childnodelist += node.children
                else:
                    childnodelist.append(None)
        dx += 5
        depth += 1
        nodelist = childnodelist
        
Mbtree.draw_tree = draw_tree
```
</details>

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

```diff_python
def draw_tree(self, startnode=None, size=0.25, lw=0.8, maxdepth=2):
元と同じなので省略
    while len(nodelist) > 0 and depth <= maxdepth:        
        dy = 0
        childnodelist = []
        for node in nodelist:
            if node is None:
                dy += 4
                childnodelist.append(None)
            else:
-               node.draw_node(ax=ax, size=size, lw=lw, dx=dx, dy=dy)
+               node.draw_node(ax=ax, maxdepth=maxdepth, size=size, lw=lw, dx=dx, dy=dy)
元と同じなので省略
        
Mbtree.draw_tree = draw_tree
```
</details>

上記の修正後に、先程と同じ下記のプログラムを実行することで、実行結果のようにバグが修正されたことが確認できます。

```python
mbtree.draw_tree(maxdepth=1)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/a84443d8-266b-ad5a-a662-9f409265aac4.png" width="100">

## ノードと子ノードの関係だけを描画した場合のバグ

[前回の記事](https://qiita.com/ysgeso/items/949d18e60939d510c931#%E6%B7%B1%E3%81%95-8-%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%81%8B%E3%82%89-draw_node-%E3%81%A7%E6%8F%8F%E7%94%BB%E3%81%95%E3%82%8C%E3%81%9F%E6%B7%B1%E3%81%95-8-%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AE%E6%A4%9C%E8%A8%BC)で `draw_node` で子ノードを描画しないように修正しましたが、そのせいで下記のプログラムのように、キーワード引数 `ax` を記述せずに `draw_node` を呼び出して、[そのノードと子ノードの関係だけを描画](https://qiita.com/ysgeso/items/0bb9d7fd972636d470d5#draw_node-%E3%81%A7%E3%81%AE-figure-%E3%81%AE%E4%BD%9C%E6%88%90%E5%87%A6%E7%90%86%E3%81%AB%E9%96%A2%E3%81%99%E3%82%8B%E4%BF%AE%E6%AD%A3)しようとした際に、__子ノードが描画されなくなる__ という問題が発生します。

```python
mbtree.root.draw_node()
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/e87ba31d-f5ca-ef7d-b642-1a85d86f1201.png" width="100">

### 間違った修正方法

この問題を解決するためには、キーワード引数 `ax` を記述せずに `draw_node` が呼び出された場合は、子ノードを描画するように `draw_node` を修正する必要があります。

そこで、下記のプログラムのように `draw_node` を修正すれば良いと思った人がいるかもしれませんが、下記のプログラムは正しく動作しません。

* __6 行目__：`ax` が `None` の場合のみ、子ノードを描画するように修正する

```python
 1  def draw_node(self, ax=None, maxdepth=None, size=0.25, lw=0.8, dx=0, dy=0):  
元と同じなので省略
 2      # 子ノードが存在する場合に、エッジの線と子ノードを描画する
 3      if len(self.children) > 0 and maxdepth != self.depth:   
 4          plt.plot([dx + 3.5, dx + 4, dx + 4], [dy + 1.5, dy + 1.5, dy + 1.5 + edgeheight], c="k", lw=lw)
 5          for childnode in self.children:
 6              if ax is None:
 7                  Marubatsu_GUI.draw_board(ax, childnode.mb, dx=dx+5, dy=dy, lw=lw)
 8              plt.plot([dx + 4 , dx + 4.5], [dy + 1.5, dy + 1.5], c="k", lw=lw)
 9              dy += childnode.height
10            
11  Node.draw_node = draw_node
```

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

```python
def draw_node(self, ax=None, maxdepth=None, size=0.25, lw=0.8, dx=0, dy=0):  
    width = 8
    if ax is None:
        height = len(self.children) * 4
        fig, ax = plt.subplots(figsize=(width * size, height * size))
        ax.set_xlim(0, width)
        ax.set_ylim(0, height)   
        ax.invert_yaxis()
        ax.axis("off")
        for childnode in self.children:
            childnode.height = 4
        if len(self.children) > 0:
            edgeheight = height - 4
    else:
        if len(self.children) > 0 and maxdepth != self.depth:
            edgeheight = self.height - self.children[-1].height
            
    # 自分自身のノードを (dx, dy) に描画する
    Marubatsu_GUI.draw_board(ax, self.mb, lw=lw, dx=dx, dy=dy)
    # 子ノードが存在する場合に、エッジの線と子ノードを描画する
    if len(self.children) > 0 and maxdepth != self.depth:   
        plt.plot([dx + 3.5, dx + 4, dx + 4], [dy + 1.5, dy + 1.5, dy + 1.5 + edgeheight], c="k", lw=lw)
        for childnode in self.children:
            if ax is None:
                Marubatsu_GUI.draw_board(ax, childnode.mb, dx=dx+5, dy=dy, lw=lw)
            plt.plot([dx + 4 , dx + 4.5], [dy + 1.5, dy + 1.5], c="k", lw=lw)
            dy += childnode.height
            
Node.draw_node = draw_node
```
</details>

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

```diff_python
def draw_node(self, ax=None, maxdepth=None, size=0.25, lw=0.8, dx=0, dy=0):  
元と同じなので省略
    # 子ノードが存在する場合に、エッジの線と子ノードを描画する
    if len(self.children) > 0 and maxdepth != self.depth:   
        plt.plot([dx + 3.5, dx + 4, dx + 4], [dy + 1.5, dy + 1.5, dy + 1.5 + edgeheight], c="k", lw=lw)
        for childnode in self.children:
+           if ax is None:
+               Marubatsu_GUI.draw_board(ax, childnode.mb, dx=dx+5, dy=dy, lw=lw)
            plt.plot([dx + 4 , dx + 4.5], [dy + 1.5, dy + 1.5], c="k", lw=lw)
            dy += childnode.height
            
Node.draw_node = draw_node
```
</details>

実行結果は先ほどと同じなので省略しますが、上記の修正後に、先程と同じ下記のプログラムを実行しても、子ノードは描画されません。子ノードが描画されない原因について少し考えてみて下さい。

```python
mbtree.root.draw_node()
```

### 正しい修正方法

子ノードが描画されない理由は、`ax` に `None` が代入されていた場合に、下記のプログラムの 5 行目で `ax` に `plt.subplots` で作成した Axes が代入されてしまうからです。そのため、先程の `if ax is None` を実行した時点では、__`ax` の値は `None` ではなくなっています__。

```python
1  def draw_node(self, ax=None, maxdepth=None, size=0.25, lw=0.8, dx=0, dy=0):  
2      width = 8
3      if ax is None:
4          height = len(self.children) * 4
5          fig, ax = plt.subplots(figsize=(width * size, height * size))
略        
```

この問題を解決する一つの方法は、__関数が呼び出された時__ に仮引数 `ax` に代入されていた値を __別の変数に代入して取っておく__ というものです。

他の方法としては、仮引数 __`maxdepth` の性質に注目する__ という方法があります。`maxdepth` は `draw_tree` で部分木を描画する際に、最も深いノードのエッジと子ノードを描画しないようにするためのものです。__ノードと子ノードの関係だけを描画__ する場合は、__`maxdepth` の情報は必要がない__ ので、__キーワード引数 `maxdepth` を記述せずに `draw_node` を呼び出す__ ことになります。従って、下記のプログラムのように、__`maxdepth` が `None` の場合に子ノードを描画する__ ようにすれば、この問題を解決することができます。本記事ではこの方法を採用しますが、わかりづらいと思った方はもう一つの方法を採用して下さい。

* __6 行目__：`maxdepth` が `None` の場合のみ、子ノードを描画するように修正する

```python
 1  def draw_node(self, ax=None, maxdepth=None, size=0.25, lw=0.8, dx=0, dy=0):  
元と同じなので省略
 2      # 子ノードが存在する場合に、エッジの線と子ノードを描画する
 3      if len(self.children) > 0 and maxdepth != self.depth:   
 4          plt.plot([dx + 3.5, dx + 4, dx + 4], [dy + 1.5, dy + 1.5, dy + 1.5 + edgeheight], c="k", lw=lw)
 5          for childnode in self.children:
 6              if maxdepth is None:
 7                  Marubatsu_GUI.draw_board(ax, childnode.mb, dx=dx+5, dy=dy, lw=lw)
 8              plt.plot([dx + 4 , dx + 4.5], [dy + 1.5, dy + 1.5], c="k", lw=lw)
 9              dy += childnode.height
10            
11  Node.draw_node = draw_node
```

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

```python
def draw_node(self, ax=None, maxdepth=None, size=0.25, lw=0.8, dx=0, dy=0):  
    width = 8
    if ax is None:
        height = len(self.children) * 4
        fig, ax = plt.subplots(figsize=(width * size, height * size))
        ax.set_xlim(0, width)
        ax.set_ylim(0, height)   
        ax.invert_yaxis()
        ax.axis("off")
        for childnode in self.children:
            childnode.height = 4
        if len(self.children) > 0:
            edgeheight = height - 4
    else:
        if len(self.children) > 0 and maxdepth != self.depth:
            edgeheight = self.height - self.children[-1].height
            
    # 自分自身のノードを (dx, dy) に描画する
    Marubatsu_GUI.draw_board(ax, self.mb, lw=lw, dx=dx, dy=dy)
    # 子ノードが存在する場合に、エッジの線と子ノードを描画する
    if len(self.children) > 0 and maxdepth != self.depth:   
        plt.plot([dx + 3.5, dx + 4, dx + 4], [dy + 1.5, dy + 1.5, dy + 1.5 + edgeheight], c="k", lw=lw)
        for childnode in self.children:
            if maxdepth is None:
                Marubatsu_GUI.draw_board(ax, childnode.mb, dx=dx+5, dy=dy, lw=lw)
            plt.plot([dx + 4 , dx + 4.5], [dy + 1.5, dy + 1.5], c="k", lw=lw)
            dy += childnode.height
            
Node.draw_node = draw_node
```
</details>

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

```diff_python
def draw_node(self, ax=None, maxdepth=None, size=0.25, lw=0.8, dx=0, dy=0):  
元と同じなので省略
    # 子ノードが存在する場合に、エッジの線と子ノードを描画する
    if len(self.children) > 0 and maxdepth != self.depth:   
        plt.plot([dx + 3.5, dx + 4, dx + 4], [dy + 1.5, dy + 1.5, dy + 1.5 + edgeheight], c="k", lw=lw)
        for childnode in self.children:
-           if ax is None:
+           if maxdepth is None:
                Marubatsu_GUI.draw_board(ax, childnode.mb, dx=dx+5, dy=dy, lw=lw)
            plt.plot([dx + 4 , dx + 4.5], [dy + 1.5, dy + 1.5], c="k", lw=lw)
            dy += childnode.height
            
Node.draw_node = draw_node
```
</details>

上記の修正後に、先程と同じ下記のプログラムを実行することで、実行結果のようにバグが修正されたことが確認できます。

```python
mbtree.root.draw_node()
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/a84443d8-266b-ad5a-a662-9f409265aac4.png" width="100">

# `draw_node` の改良

現状の `draw_node` にはいくつか改良の余地があるので、改良することにします。どのような改良の余地があるかについて少し考えてみて下さい。

なお、本記事で紹介する以外の改良方法を思いついた方はぜひ実装してみて下さい。

## 決着がついたノードの区別

現状では、ゲーム木の局面の画像を見ても __決着がついているかどうかの区別がわかりづらい__ 図になっています。そこで、決着がついた局面の __勝敗結果__ を下記のように描画して __一目でわかるようにする__ ことにします。色を変更したい人は自由に変更して下さい。

|  | ゲーム盤の背景色 |
|:--|:--|
| __ゲーム中__  | 白（これまでと同じ）  |
| __〇 の勝利__  | 水色  |
| __× の勝利__  | 薄いピンク  |
| __引き分け__  | 薄い黄色  |

### ゲーム盤の背景色の変更方法

ゲーム盤の背景色を変更するためには、ゲーム盤を描画する位置に、__ゲーム盤と同じ大きさ__ の、__背景色で塗りつぶされた正方形を描画__ します。__その後で枠やマークを描画__ することで、ゲーム盤の背景色を変更することができます。

:::note
上記の正方形の描画は、__枠やマークを描画する前に行う必要がある__ 点に注意して下さい。枠やマークの描画の後で上記の正方形を描画すると、枠やマークの上に重ねて正方形が描画されるため、枠やマークが見えなくなってしまうからです。
:::

matplotlib で正方形を描画するためには、patches モジュールで定義された、__長方形を表す `Rectangle`__ という [Artist](https://qiita.com/ysgeso/items/ab2770693c947c108dd0#artist) を作成する関数を利用します。

下記は `Rectangle` の仮引数 です。

| 仮引数 | 意味 |
|:--|:--|
| __xy__  | 長方形の左上の頂点の座標 (x, y) を表すシーケンス型  |
| __width__   | 長方形の幅  |  
| __height__  | 長方形の高さ  |

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

https://matplotlib.org/stable/api/_as_gen/matplotlib.patches.Rectangle.html

### `draw_board` の修正

ゲーム盤の描画は __`draw_board`__ で行うので、`draw_board` を上記の表のようなゲーム盤を描画するように修正することにします。ただし、互換性を考慮し、`True` が代入されていた場合のみゲーム盤の背景色を変えて結果（result）を表示（show）する __`show_result` という仮引数を追加__ することにします。下記はそのように `draw_board` を修正したプログラムです。

* __5 行目__：デフォルト値を `False` とする、デフォルト引数 `show_result` を追加する
* __7 ~ 18 行目__：`show_result` が `True` の場合のみゲーム盤の背景色を変更する。この処理は、枠やマークの描画の前に行う必要がある
* __8 ~ 14 行目__：`status` 属性の値に応じた背景色を `bgcolor` に代入する
* __16 行目__：ゲーム盤と同じ位置と大きさで、`bgcolor` で塗りつぶした正方形を `Rectangle` で作成する。塗りつぶしの色などの設定方法については[以前の記事](https://qiita.com/ysgeso/items/ab2770693c947c108dd0#%E6%9E%A0%E3%81%A8%E5%A1%97%E3%82%8A%E3%81%A4%E3%81%B6%E3%81%97%E3%81%AE%E8%A8%AD%E5%AE%9A%E6%96%B9%E6%B3%95)を参照すること
* __17 行目__：作成した正方形の Artist を [`add_artist`](https://qiita.com/ysgeso/items/ab2770693c947c108dd0#%E5%86%86%E3%81%AE%E4%BD%9C%E6%88%90%E3%81%A8%E7%99%BB%E9%8C%B2) で Axes に登録して描画する

```python
 1  from marubatsu import Marubatsu
 2  import matplotlib.patches as patches
 3
 4  @staticmethod
 5  def draw_board(ax, mb, show_result=False, dx=0, dy=0, lw=2):
 6      # 結果によってゲーム盤の背景色を変更する
 7      if show_result:
 8          if mb.status == Marubatsu.PLAYING:
 9              bgcolor = "white"
10          elif mb.status == Marubatsu.CIRCLE:
11              bgcolor = "lightcyan"
12          elif mb.status == Marubatsu.CROSS:
13              bgcolor = "lavenderblush"
14          else:
15              bgcolor = "lightyellow"
16          rect = patches.Rectangle(xy=(dx, dy), width=mb.BOARD_SIZE,
17                                   height=mb.BOARD_SIZE, fc=bgcolor)
18          ax.add_patch(rect)
元と同じなので省略    
19
20  Marubatsu_GUI.draw_board = draw_board
```

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

```python
from marubatsu import Marubatsu
import matplotlib.patches as patches

@staticmethod
def draw_board(ax, mb, show_result=False, dx=0, dy=0, lw=2):
    # 結果によってゲーム盤の背景色を変更する
    if show_result:
        if mb.status == Marubatsu.PLAYING:
            bgcolor = "white"
        elif mb.status == Marubatsu.CIRCLE:
            bgcolor = "lightcyan"
        elif mb.status == Marubatsu.CROSS:
            bgcolor = "lavenderblush"
        else:
            bgcolor = "lightyellow"
        rect = patches.Rectangle(xy=(dx, dy), width=mb.BOARD_SIZE,
                                 height=mb.BOARD_SIZE, fc=bgcolor)
        ax.add_patch(rect)
    
    # ゲーム盤の枠を描画する
    for i in range(1, mb.BOARD_SIZE):
        ax.plot([dx, dx + mb.BOARD_SIZE], [dy + i, dy + i], c="k", lw=lw) # 横方向の枠線
        ax.plot([dx + i, dx + i], [dy, dy + mb.BOARD_SIZE], c="k", lw=lw) # 縦方向の枠線

    # ゲーム盤のマークを描画する
    for y in range(mb.BOARD_SIZE):
        for x in range(mb.BOARD_SIZE):
            color = "red" if (x, y) == mb.last_move else "black"
            Marubatsu_GUI.draw_mark(ax, dx + x, dy + y, mb.board[x][y], color, lw=lw)

Marubatsu_GUI.draw_board = draw_board
```
</details>

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

```diff_python
from marubatsu import Marubatsu
import matplotlib.patches as patches

@staticmethod
def draw_board(ax, mb, show_result=False, dx=0, dy=0, lw=2):
    # 結果によってゲーム盤の背景色を変更する
+   if show_result:
+       if mb.status == Marubatsu.PLAYING:
+           bgcolor = "white"
+       elif mb.status == Marubatsu.CIRCLE:
+           bgcolor = "lightcyan"
+       elif mb.status == Marubatsu.CROSS:
+           bgcolor = "lavenderblush"
+       else:
+           bgcolor = "lightyellow"
+       rect = patches.Rectangle(xy=(dx, dy), width=mb.BOARD_SIZE,
+                                height=mb.BOARD_SIZE, fc=bgcolor)
+       ax.add_patch(rect)
元と同じなので省略    

Marubatsu_GUI.draw_board = draw_board
```
</details>

次に、下記のプログラムの 3、9 行目のように `draw_node` の中で、`draw_board` を呼び出す処理に、キーワード引数 `show_result=True` を追加します。

```python
 1  def draw_node(self, ax=None, maxdepth=None, size=0.25, lw=0.8, dx=0, dy=0):  
元と同じなので省略          
 2      # 自分自身のノードを (dx, dy) に描画する
 3      Marubatsu_GUI.draw_board(ax, self.mb, show_result=True, lw=lw, dx=dx, dy=dy)
 4      # 子ノードが存在する場合に、エッジの線と子ノードを描画する
 5      if len(self.children) > 0 and maxdepth != self.depth:   
 6          plt.plot([dx + 3.5, dx + 4, dx + 4], [dy + 1.5, dy + 1.5, dy + 1.5 + edgeheight], c="k", lw=lw)
 7          for childnode in self.children:
 8              if maxdepth is None:
 9                  Marubatsu_GUI.draw_board(ax, childnode.mb, show_result=True, dx=dx+5, dy=dy, lw=lw)
10              plt.plot([dx + 4 , dx + 4.5], [dy + 1.5, dy + 1.5], c="k", lw=lw)
11              dy += childnode.height
12           
13  Node.draw_node = draw_node
```

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

```python
def draw_node(self, ax=None, maxdepth=None, size=0.25, lw=0.8, dx=0, dy=0):  
    width = 8
    if ax is None:
        height = len(self.children) * 4
        fig, ax = plt.subplots(figsize=(width * size, height * size))
        ax.set_xlim(0, width)
        ax.set_ylim(0, height)   
        ax.invert_yaxis()
        ax.axis("off")
        for childnode in self.children:
            childnode.height = 4
        if len(self.children) > 0:
            edgeheight = height - 4
    else:
        if len(self.children) > 0 and maxdepth != self.depth:
            edgeheight = self.height - self.children[-1].height
            
    # 自分自身のノードを (dx, dy) に描画する
    Marubatsu_GUI.draw_board(ax, self.mb, show_result=True, lw=lw, dx=dx, dy=dy)
    # 子ノードが存在する場合に、エッジの線と子ノードを描画する
    if len(self.children) > 0 and maxdepth != self.depth:   
        plt.plot([dx + 3.5, dx + 4, dx + 4], [dy + 1.5, dy + 1.5, dy + 1.5 + edgeheight], c="k", lw=lw)
        for childnode in self.children:
            if maxdepth is None:
                Marubatsu_GUI.draw_board(ax, childnode.mb, show_result=True, dx=dx+5, dy=dy, lw=lw)
            plt.plot([dx + 4 , dx + 4.5], [dy + 1.5, dy + 1.5], c="k", lw=lw)
            dy += childnode.height
            
Node.draw_node = draw_node
```
</details>

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

```diff_python
def draw_node(self, ax=None, maxdepth=None, size=0.25, lw=0.8, dx=0, dy=0):  
元と同じなので省略          
    # 自分自身のノードを (dx, dy) に描画する
-   Marubatsu_GUI.draw_board(ax, self.mb, lw=lw, dx=dx, dy=dy)
+   Marubatsu_GUI.draw_board(ax, self.mb, show_result=True, lw=lw, dx=dx, dy=dy)
    # 子ノードが存在する場合に、エッジの線と子ノードを描画する
    if len(self.children) > 0 and maxdepth != self.depth:   
        plt.plot([dx + 3.5, dx + 4, dx + 4], [dy + 1.5, dy + 1.5, dy + 1.5 + edgeheight], c="k", lw=lw)
        for childnode in self.children:
            if maxdepth is None:
-               Marubatsu_GUI.draw_board(ax, childnode.mb, dx=dx+5, dy=dy, lw=lw)
+               Marubatsu_GUI.draw_board(ax, childnode.mb, show_result=True, dx=dx+5, dy=dy, lw=lw)
            plt.plot([dx + 4 , dx + 4.5], [dy + 1.5, dy + 1.5], c="k", lw=lw)
            dy += childnode.height
            
Node.draw_node = draw_node
```
</details>

上記の修正後に、下記のプログラムを実行することで、実行結果のように決着がついた局面の背景色が変化するようになります。なお、下記は 〇 の勝利、× の勝利、引き分けの全ての局面が含まれる部分木を、様々な部分木を表示するという試行錯誤で見つけました。

```python
mbtree.draw_tree(mbtree.nodelist_by_depth[6][100], maxdepth=9)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/b5df0b71-7141-b7ff-1dc6-992fb6146b5a.png" width="250">

## 部分木の最も深いノードに子ノードが存在する場合の表示

今回の記事の冒頭で、`draw_tree` で表示する部分木の、最も深いノードのエッジと子ノードを描画しないように修正しました。例えば、下記のプログラムで __深さが 8 までの部分木を描画__ すると、実行結果のようなゲーム木が描画されます。

```python
mbtree.draw_tree(mbtree.nodelist_by_depth[6][100], maxdepth=8)
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/a998ad13-2bb7-1ff7-8006-7ff28af23715.png" width="200">

先程決着のついた局面の背景色に色を塗るようにしたので、最も深い __深さが 8 のノード__ に __子ノードが存在するか__ どうかは、__背景色が白いかどうか__ で判別することができますが、直観的ではないので __わかりやすいとはいえない__ でしょう。

そこで、最も深いノードに子ノードが存在する場合は、__子ノードが存在することを表す横棒のエッジを描画する__ ように工夫することにします。

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

* __5 行目__：子ノードが存在するかどうかだけを判定するように修正する
* __6 ~ 12 行目__：ノードの深さが `maxdepth` と等しくない場合は、これまでと同じ方法で、エッジと子ノードを描画する
* __13、14 行目__：ノードの深さが `maxdepth` と等しい場合は、子ノードが存在することを表す、横に 1 本だけのエッジを描画する

```python
 1  def draw_node(self, ax=None, maxdepth=None, size=0.25, lw=0.8, dx=0, dy=0):  
元と同じなので省略
 2      # 自分自身のノードを (dx, dy) に描画する
 3      Marubatsu_GUI.draw_board(ax, self.mb, show_result=True, lw=lw, dx=dx, dy=dy)
 4      # 子ノードが存在する場合に、エッジの線と子ノードを描画する
 5      if len(self.children) > 0:
 6          if maxdepth != self.depth:   
 7              plt.plot([dx + 3.5, dx + 4, dx + 4], [dy + 1.5, dy + 1.5, dy + 1.5 + edgeheight], c="k", lw=lw)
 8              for childnode in self.children:
 9                  if maxdepth is None:
10                       Marubatsu_GUI.draw_board(ax, childnode.mb, show_result=True, dx=dx+5, dy=dy, lw=lw)
11                  plt.plot([dx + 4 , dx + 4.5], [dy + 1.5, dy + 1.5], c="k", lw=lw)
12                  dy += childnode.height
13          else:
14              plt.plot([dx + 3.5, dx + 4.5], [dy + 1.5, dy + 1.5], c="k", lw=lw)
15            
16  Node.draw_node = draw_node
```

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

```python
def draw_node(self, ax=None, maxdepth=None, size=0.25, lw=0.8, dx=0, dy=0):  
    width = 8
    if ax is None:
        height = len(self.children) * 4
        fig, ax = plt.subplots(figsize=(width * size, height * size))
        ax.set_xlim(0, width)
        ax.set_ylim(0, height)   
        ax.invert_yaxis()
        ax.axis("off")
        for childnode in self.children:
            childnode.height = 4
        if len(self.children) > 0:
            edgeheight = height - 4
    else:
        if len(self.children) > 0 and maxdepth != self.depth:
            edgeheight = self.height - self.children[-1].height
            
    # 自分自身のノードを (dx, dy) に描画する
    Marubatsu_GUI.draw_board(ax, self.mb, show_result=True, lw=lw, dx=dx, dy=dy)
    # 子ノードが存在する場合に、エッジの線と子ノードを描画する
    if len(self.children) > 0:
        if maxdepth != self.depth:   
            plt.plot([dx + 3.5, dx + 4, dx + 4], [dy + 1.5, dy + 1.5, dy + 1.5 + edgeheight], c="k", lw=lw)
            for childnode in self.children:
                if maxdepth is None:
                    Marubatsu_GUI.draw_board(ax, childnode.mb, show_result=True, dx=dx+5, dy=dy, lw=lw)
                plt.plot([dx + 4 , dx + 4.5], [dy + 1.5, dy + 1.5], c="k", lw=lw)
                dy += childnode.height
        else:
            plt.plot([dx + 3.5, dx + 4.5], [dy + 1.5, dy + 1.5], c="k", lw=lw)
            
Node.draw_node = draw_node
```
</details>

<details><summary><strong>修正箇所（if maxdepth != self.depth のインデントの修正は省略します）</strong></summary>

```diff_python
def draw_node(self, ax=None, maxdepth=None, size=0.25, lw=0.8, dx=0, dy=0):  
元と同じなので省略
    # 自分自身のノードを (dx, dy) に描画する
    Marubatsu_GUI.draw_board(ax, self.mb, show_result=True, lw=lw, dx=dx, dy=dy)
    # 子ノードが存在する場合に、エッジの線と子ノードを描画する
-   if len(self.children) > 0 and maxdepth != self.depth: 
+   if len(self.children) > 0:
+       if maxdepth != self.depth:   
            plt.plot([dx + 3.5, dx + 4, dx + 4], [dy + 1.5, dy + 1.5, dy + 1.5 + edgeheight], c="k", lw=lw)
            for childnode in self.children:
                if maxdepth is None:
                    Marubatsu_GUI.draw_board(ax, childnode.mb, show_result=True, dx=dx+5, dy=dy, lw=lw)
                plt.plot([dx + 4 , dx + 4.5], [dy + 1.5, dy + 1.5], c="k", lw=lw)
                dy += childnode.height
+       else:
+           plt.plot([dx + 3.5, dx + 4.5], [dy + 1.5, dy + 1.5], c="k", lw=lw)
            
Node.draw_node = draw_node
```
</details>

上記の修正後に、下記のプログラムを実行することで、実行結果のように、最も深いノードに子ノードが存在する場合は、横棒のエッジが表示されるようになります。

```python
mbtree.draw_tree(mbtree.nodelist_by_depth[6][100], maxdepth=8)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/9a82fc62-9487-5d03-5c15-b8ab00fa8770.png" width="200">

:::note
本記事では子ノードの数に関わらず、横棒を 1 本だけ描画しますが、子ノードの数を明確にしたい場合は、以下のような工夫を行うと良いでしょう。

* 横棒の数を変える
* 横棒の太さを変える
* 横棒の右に子ノードの数を数字で表示する
:::

## バランスの良い位置へのノードの描画

現状では、__親ノード__ は、その右の __子ノード一覧の上端に描画__ されますが、__子ノードの一覧の真ん中に描画__ したほうが、__見た目のバランスが良いゲーム木__ になります。具体的には、左下図を右下図のように描画するということです。そのためには、どのようにノードやエッジの描画位置を計算すればよいかについて少し考えてみて下さい。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/9a82fc62-9487-5d03-5c15-b8ab00fa8770.png" width="200">　<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/bcdcbb83-6510-7e84-967e-8a4a03fc52f5.png" width="200">

### ノードの描画位置の計算方法

まず、ルートノードの描画位置を計算する方法を考えることにします。ルートノードは、下図の __赤線の長さの分だけ下にずらして描画__ する必要があります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/7adf71f6-1552-d81a-2b51-cbf240f9b852.png" width="200">

ルートノードは、Figure の上下のちょうど真ん中に描画する必要があるので、図の __2 本ある赤線の長さは等しくなります__。また、Figure の高さはルートノードの `height` 属性に代入されているので、上図から赤線の長さは下記の式で計算することができることがわかります。

(ノードの `height` 属性 - 3) / 2

ノードの `height` 属性は、__子ノードの高さの合計__ を表すので、下図のように、__ルートノード以外の場合__ も、__上記の式と全く同じ式__ で赤線の長さを計算することができます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/abccec41-1b5a-c526-a559-159171c4b707.png" width="200">

下記は、上記の式を使って、ノードを描画する座標をずらして表示するように `draw_node` を修正したプログラムです。

* __3 行目__：ノードを描画する y 座標を先程の式を使って計算する。なお、__`dy` はこの後でエッジなどを描画する際に必要になる__ ので `y` という変数に計算した値を代入した
* __4 行目__：上記で計算した座標にノードを描画する

```python
1  def draw_node(self, ax=None, maxdepth=None, size=0.25, lw=0.8, dx=0, dy=0):  
元と同じなので省略
2      # 自分自身のノードを真ん中の位置になるように (dx, dy) からずらして描画する
3      y = dy + (self.height - 3) / 2
4      Marubatsu_GUI.draw_board(ax, self.mb, show_result=True, lw=lw, dx=dx, dy=y)
元と同じなので省略          
5            
6  Node.draw_node = draw_node
```

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

```python
def draw_node(self, ax=None, maxdepth=None, size=0.25, lw=0.8, dx=0, dy=0):  
    width = 8
    if ax is None:
        height = len(self.children) * 4
        fig, ax = plt.subplots(figsize=(width * size, height * size))
        ax.set_xlim(0, width)
        ax.set_ylim(0, height)   
        ax.invert_yaxis()
        ax.axis("off")
        for childnode in self.children:
            childnode.height = 4
        if len(self.children) > 0:
            edgeheight = height - 4
    else:
        if len(self.children) > 0 and maxdepth != self.depth:
            edgeheight = self.height - self.children[-1].height
            
    # 自分自身のノードを真ん中の位置になるように (dx, dy) からずらして描画する
    y = dy + (self.height - 3) / 2
    Marubatsu_GUI.draw_board(ax, self.mb, show_result=True, lw=lw, dx=dx, dy=y)
    # 子ノードが存在する場合に、エッジの線と子ノードを描画する
    if len(self.children) > 0:
        if maxdepth != self.depth:   
            plt.plot([dx + 3.5, dx + 4, dx + 4], [dy + 1.5, dy + 1.5, dy + 1.5 + edgeheight], c="k", lw=lw)
            for childnode in self.children:
                if maxdepth is None:
                    Marubatsu_GUI.draw_board(ax, childnode.mb, show_result=True, dx=dx+5, dy=dy, lw=lw)
                plt.plot([dx + 4 , dx + 4.5], [dy + 1.5, dy + 1.5], c="k", lw=lw)
                dy += childnode.height
        else:
            plt.plot([dx + 3.5, dx + 4.5], [dy + 1.5, dy + 1.5], c="k", lw=lw)  
            
Node.draw_node = draw_node
```
</details>

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

```diff_python
def draw_node(self, ax=None, maxdepth=None, size=0.25, lw=0.8, dx=0, dy=0):  
元と同じなので省略          
    # 自分自身のノードを真ん中の位置になるように (dx, dy) からずらして描画する
+   y = dy + (self.height - 3) / 2
-   Marubatsu_GUI.draw_board(ax, self.mb, show_result=True, lw=lw, dx=dx, dy=dy)
+   Marubatsu_GUI.draw_board(ax, self.mb, show_result=True, lw=lw, dx=dx, dy=y)
元と同じなので省略          
            
Node.draw_node = draw_node
```
</details>

上記の修正後に、下記のプログラムを実行すると、実行結果のように __ノードがバランスの良い位置に表示される__ ようになります。ただし、エッジの描画の処理は変更していないので、エッジが変な位置に描画されるという問題があります。

```python
mbtree.draw_tree(mbtree.nodelist_by_depth[6][100], maxdepth=8)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/4e77623b-210b-0527-071a-a26035ccad79.png" width="200">

### エッジの描画の手順の変更

これまでは、エッジは[下記の手順](https://qiita.com/ysgeso/items/7cd67869855550d222f5#%E3%82%A8%E3%83%83%E3%82%B8%E3%82%92%E6%8F%8F%E7%94%BB%E3%81%99%E3%82%8B%E5%87%A6%E7%90%86%E3%81%AE%E5%AE%9F%E8%A3%85-%E3%81%9D%E3%81%AE-2)で描画を行いました。

* 下図左の緑の折れ線を描画する
* それぞれの子ノードに対して横線を描画する

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/0044749c-0429-3128-3bf6-f63ea0767212.png" width="150">　<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/bcdcbb83-6510-7e84-967e-8a4a03fc52f5.png" width="180">

ただし、ノードをバランスの良い位置に描画した場合は上図右のようになるため、上図左の緑の線を __一本の折れ線で描画することはできません__。

また、ノードをバランスの良い位置に描画した場合の __エッジの縦線の上端と下端の座標__ をあらかじめ __計算するのは少し面倒__ なので、エッジの描画方法を下記のように変えることにします。なお、下図の赤と青の線は、真ん中の子ノードに対して描画するエッジです。

* 下図の __緑の横線__ を描画する
* それぞれの子ノードに対して、下図の __赤の横線__ を描画する
* それぞれの子ノードに対して、__上にノードがあれば__ 下図の __青の縦線__ を描画する


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/692a53d4-7c48-19ff-7749-aeb4a35e944f.png" width="200">

### 緑の線に対応するエッジの描画

緑の線に対応するエッジは、ノードの描画位置である (dx, y) を基準として、(3.5 + dx, 1.5 + y) から (4 + dx, 1.5 + y) までを `plot` で描画できます。ノードの描画位置の __y 座標が `dy` から `y` に変化__ した点と、縦棒を描画しなくなった点を除けば、元のプログラムと同じです。

下記は、上図の緑の線に対応するエッジを描画するように修正するプログラムです。わかりやすいように、線の太さを 3 にし、実際に緑の線で描画するようにしました。線の太さと色は、エッジが正しく描画されたことが確認できた後で元に戻すことにします。なお、エッジの縦棒を描画する際に利用していた __`edgeheight` は必要がなくなったので削除しました__。

* __4 行目の下__ にあった `edgeheight` を計算する処理を削除する
* __8 行目__：緑の線に対応するエッジを描画する。その際に、縦棒を描画しないようにする

```python
 1  def draw_node(self, ax=None, maxdepth=None, size=0.25, lw=0.8, dx=0, dy=0):  
元と同じなので省略
 2          for childnode in self.children:
 3              childnode.height = 4
 4          # この下にあった edgeheight を計算する処理を削除する
元と同じなので省略
 5      # 子ノードが存在する場合に、エッジの線と子ノードを描画する
 6      if len(self.children) > 0:
 7          if maxdepth != self.depth:   
 8              plt.plot([dx + 3.5, dx + 4], [y + 1.5, y + 1.5], c="g", lw=3)
元と同じなので省略
 9           
10  Node.draw_node = draw_node
```

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

```python
def draw_node(self, ax=None, maxdepth=None, size=0.25, lw=0.8, dx=0, dy=0):  
    width = 8
    if ax is None:
        height = len(self.children) * 4
        fig, ax = plt.subplots(figsize=(width * size, height * size))
        ax.set_xlim(0, width)
        ax.set_ylim(0, height)   
        ax.invert_yaxis()
        ax.axis("off")
        for childnode in self.children:
            childnode.height = 4
            
    # 自分自身のノードを真ん中の位置になるように (dx, dy) からずらして描画する
    y = dy + (self.height - 3) / 2
    Marubatsu_GUI.draw_board(ax, self.mb, show_result=True, lw=lw, dx=dx, dy=y)
    # 子ノードが存在する場合に、エッジの線と子ノードを描画する
    if len(self.children) > 0:
        if maxdepth != self.depth:   
            plt.plot([dx + 3.5, dx + 4], [y + 1.5, y + 1.5], c="g", lw=3)
            for childnode in self.children:
                if maxdepth is None:
                    Marubatsu_GUI.draw_board(ax, childnode.mb, show_result=True, dx=dx+5, dy=dy, lw=lw)
                plt.plot([dx + 4 , dx + 4.5], [dy + 1.5, dy + 1.5], c="k", lw=lw)
                dy += childnode.height
        else:
            plt.plot([dx + 3.5, dx + 4.5], [dy + 1.5, dy + 1.5], c="k", lw=lw)
            
Node.draw_node = draw_node
```
</details>

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

```diff_python
def draw_node(self, ax=None, maxdepth=None, size=0.25, lw=0.8, dx=0, dy=0):  
元と同じなので省略
        for childnode in self.children:
            childnode.height = 4
-       if len(self.children) > 0:
-           edgeheight = height - 4
-   else:
-       if len(self.children) > 0:
-           edgeheight = self.height - self.children[-1].height元と同じなので省略
    # 子ノードが存在する場合に、エッジの線と子ノードを描画する
    if len(self.children) > 0:
        if maxdepth != self.depth:   
-           plt.plot([dx + 3.5, dx + 4, dx + 4], [dy + 1.5, dy + 1.5, dy + 1.5 + edgeheight], c="k", lw=lw)
+           plt.plot([dx + 3.5, dx + 4], [y + 1.5, y + 1.5], c="g", lw=3)
元と同じなので省略
            
Node.draw_node = draw_node
```
</details>

上記の修正後に、下記のプログラムを実行すると、実行結果のように子ノードが存在するノードの右に緑の線が描画され、エッジの縦棒が描画されなくなります。ただし、__最も深いノードの子ノードのエッジが少し上にずれてしまう__ という問題が発生しています。

```python
mbtree.draw_tree(mbtree.nodelist_by_depth[6][100], maxdepth=8)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/71cbdd11-05fe-8914-dfe5-d2d0b68432aa.png" width="300">

### 最も深いノードのエッジの描画の修正

最も深いノードのエッジは、黒色で描画されていることからわかるように、上記で修正したプログラムではなく、下記のプログラムの 3 行目で描画しています。下記の 3 行目の処理を実行すると、上記のように横線がずれてしまう理由について少し考えてみて下さい。

```python
1  if maxdepth != self.depth:   
略
2  else:
3      plt.plot([dx + 3.5, dx + 4.5], [dy + 1.5, dy + 1.5], c="k", lw=lw)
```

上記の 3 行目のプログラムは修正していないので、__横線がずれる理由__ は、横線の左の __ノードが描画される位置が変化したため__ です。ノードの描画位置は、__(height 属性 - 3) / 2__ という式で計算しますが、__最も深いノードの `height` 属性__ には、下記の `calc_node_height` の 4、5 行目の処理によって __`4` が代入されます__。
```python
1  def calc_node_height(self, maxdepth:int):
2      for depth in range(maxdepth, -1, -1):
3          for node in self.nodelist_by_depth[depth]:
4              if depth == maxdepth:
5                  node.height = 4
6              else:
7                  node.calc_height()                  
```

従って、__(height 属性 - 3) / 2__ の計算結果は (4 - 3) / 2 = __0.5 になる__ ため、__最も深いノード__ は、先程の修正が行われる前と比べて __0.5 だけ下にずれて描画が行われます__。一方で、__最も深いノードのエッジの描画位置は修正していない__ ので、先程の図のように、一見すると、エッジのほうが上にずれて描画されるように見えることになります。

従って、この問題は下記のプログラムの 4 行目のように、最も深いノードのエッジを描画する際に、`dy` ではなく、__ノードの描画位置を表す `y` を基準に描画する__ ことで解決することができます。

```python
1  def draw_node(self, ax=None, maxdepth=None, size=0.25, lw=0.8, dx=0, dy=0):  
元と同じなので省略
2          if maxdepth != self.depth:   
元と同じなので省略
3          else:
4              plt.plot([dx + 3.5, dx + 4.5], [y + 1.5, y + 1.5], c="k", lw=lw)
5            
6  Node.draw_node = draw_node
```

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

```python
def draw_node(self, ax=None, maxdepth=None, size=0.25, lw=0.8, dx=0, dy=0):  
    width = 8
    if ax is None:
        height = len(self.children) * 4
        fig, ax = plt.subplots(figsize=(width * size, height * size))
        ax.set_xlim(0, width)
        ax.set_ylim(0, height)   
        ax.invert_yaxis()
        ax.axis("off")
        for childnode in self.children:
            childnode.height = 4
            
    # 自分自身のノードを真ん中の位置になるように (dx, dy) からずらして描画する
    y = dy + (self.height - 3) / 2
    Marubatsu_GUI.draw_board(ax, self.mb, show_result=True, lw=lw, dx=dx, dy=y)
    # 子ノードが存在する場合に、エッジの線と子ノードを描画する
    if len(self.children) > 0:
        if maxdepth != self.depth:   
            plt.plot([dx + 3.5, dx + 4], [y + 1.5, y + 1.5], c="g", lw=3)
            for childnode in self.children:
                if maxdepth is None:
                    Marubatsu_GUI.draw_board(ax, childnode.mb, show_result=True, dx=dx+5, dy=dy, lw=lw)
                plt.plot([dx + 4 , dx + 4.5], [dy + 1.5, dy + 1.5], c="k", lw=lw)
                dy += childnode.height
        else:
            plt.plot([dx + 3.5, dx + 4.5], [y + 1.5, y + 1.5], c="k", lw=lw)
            
Node.draw_node = draw_node
```
</details>

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

```diff_python
def draw_node(self, ax=None, maxdepth=None, size=0.25, lw=0.8, dx=0, dy=0):  
元と同じなので省略
        if maxdepth != self.depth:   
元と同じなので省略
        else:
-           plt.plot([dx + 3.5, dx + 4.5], [dy + 1.5, dy + 1.5], c="k", lw=lw)
+           plt.plot([dx + 3.5, dx + 4.5], [y + 1.5, y + 1.5], c="k", lw=lw)
            
Node.draw_node = draw_node
```
</details>

上記の修正後に、下記のプログラムを実行すると、実行結果のように、最も深いノードのエッジが正しい位置に描画されるようになります。

```python
mbtree.draw_tree(mbtree.nodelist_by_depth[6][100], maxdepth=8)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/f815df2b-361e-54cd-f285-8245640c9730.png" width="200">

### 赤い線に対応するエッジの描画

赤い線に対応するエッジは、それぞれの子ノードが描画される位置に応じて描画する必要があり、子ノードは `dy` に対して __(子ノードの height 属性 - 3) / 2__ だけ下にずらした位置に描画します。従って、下記のプログラムのように修正することで、赤い線に対応するエッジを正しい位置に描画することができるようになります。なお、わかりやすさを重視して、先程と同様に太さが 3 で赤い線で描画するようにしました。

* __5、6 行目__：子ノードへのエッジを描画する座標を計算して描画する

```python
1  def draw_node(self, ax=None, maxdepth=None, size=0.25, lw=0.8, dx=0, dy=0):  
元と同じなので省略
2              for childnode in self.children:
3                  if maxdepth is None:
4                      Marubatsu_GUI.draw_board(ax, childnode.mb, show_result=True, dx=dx+5, dy=dy, lw=lw)
5                  edgey = dy + (childnode.height - 3) / 2 + 1.5
6                  plt.plot([dx + 4 , dx + 4.5], [edgey, edgey], c="r", lw=3)
7                  dy += childnode.height
元と同じなので省略
8            
9  Node.draw_node = draw_node
```

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

```python
def draw_node(self, ax=None, maxdepth=None, size=0.25, lw=0.8, dx=0, dy=0):  
    width = 8
    if ax is None:
        height = len(self.children) * 4
        fig, ax = plt.subplots(figsize=(width * size, height * size))
        ax.set_xlim(0, width)
        ax.set_ylim(0, height)   
        ax.invert_yaxis()
        ax.axis("off")
        for childnode in self.children:
            childnode.height = 4
            
    # 自分自身のノードを真ん中の位置になるように (dx, dy) からずらして描画する
    y = dy + (self.height - 3) / 2
    Marubatsu_GUI.draw_board(ax, self.mb, show_result=True, lw=lw, dx=dx, dy=y)
    # 子ノードが存在する場合に、エッジの線と子ノードを描画する
    if len(self.children) > 0:
        if maxdepth != self.depth:   
            plt.plot([dx + 3.5, dx + 4], [y + 1.5, y + 1.5], c="g", lw=3)
            for childnode in self.children:
                if maxdepth is None:
                    Marubatsu_GUI.draw_board(ax, childnode.mb, show_result=True, dx=dx+5, dy=dy, lw=lw)
                edgey = dy + (childnode.height - 3) / 2 + 1.5
                plt.plot([dx + 4 , dx + 4.5], [edgey, edgey], c="r", lw=3)
                dy += childnode.height
        else:
            plt.plot([dx + 3.5, dx + 4.5], [y + 1.5, y + 1.5], c="k", lw=lw)
            
Node.draw_node = draw_node
```
</details>

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

```diff_python
def draw_node(self, ax=None, maxdepth=None, size=0.25, lw=0.8, dx=0, dy=0):  
元と同じなので省略
            for childnode in self.children:
                if maxdepth is None:
                    Marubatsu_GUI.draw_board(ax, childnode.mb, show_result=True, dx=dx+5, dy=dy, lw=lw)
-               plt.plot([dx + 4 , dx + 4.5], [dy + 1.5, dy + 1.5], c="k", lw=lw)
+               edgey = dy + (childnode.height - 3) / 2 + 1.5
+               plt.plot([dx + 4 , dx + 4.5], [edgey, edgey], c="r", lw=3)
                dy += childnode.height
元と同じなので省略
            
Node.draw_node = draw_node
```
</details>

上記の修正後に、下記のプログラムを実行すると、実行結果のように、子ノードへの赤い横線のエッジが描画されるようになります。

```python
mbtree.draw_tree(mbtree.nodelist_by_depth[6][100], maxdepth=8)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/60f9ba1c-6550-313b-56eb-32e8dff6a0be.png" width="200">

### 青い線に対応するエッジの描画

青い線に対応するエッジは、__一つ前__ の子ノードへの __横線の y 座標から__、__現在__ の子ノードへの __横線の y 座標まで__ の __縦線を引く__ ことで描画することができます。

従って、__一つ前__ の子ノードへの __横線の y 座標を記録しておく__ 必要があります。また、__最初の子ノードには一つ前の子ノードが存在しない__ ので、__青い線を描画する必要がない__ 点にも注意が必要です。どのようなプログラムで青い線に対応するエッジを描画できるかについて少し考えてみて下さい。

下記のような処理を行うことで青い線に対応するエッジを描画することができます。

* 一つ前（previous）の子ノードへの横線の y 座標を __`prevy`__ という変数に代入する
* 最初の子ノードには一つ前の子ノードが存在しないので、__`prevy` を `None` で初期化__ する
* 子ノードの赤い線のエッジを `edgey` の高さに描画した後で、`prevy` に `None` が代入されていない場合に `prevy` から `edgey` までの縦線を描画する
* その後で、__`prevy` に `edgey` を代入して更新__　する

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

* __4 行目__：子ノードに対する繰り返し処理を行う前に、`prevy` を `None` で初期化する
* __10、11 行目__：`prevy` が `None` ではない場合に、(dx + 4, prevy) から (dx + 4, edgey) まで縦線を描画する
* __12 行目__：`prevy` に `edgey` を代入する

```python
 1  def draw_node(self, ax=None, maxdepth=None, size=0.25, lw=0.8, dx=0, dy=0):  
元と同じなので省略
 2          if maxdepth != self.depth:   
 3              plt.plot([dx + 3.5, dx + 4], [y + 1.5, y + 1.5], c="g", lw=3)
 4              prevy = None
 5              for childnode in self.children:
 6                  if maxdepth is None:
 7                      Marubatsu_GUI.draw_board(ax, childnode.mb, show_result=True, dx=dx+5, dy=dy, lw=lw)
 8                  edgey = dy + (childnode.height - 3) / 2 + 1.5
 9                  plt.plot([dx + 4 , dx + 4.5], [edgey, edgey], c="r", lw=3)
10                  if prevy is not None:
11                      plt.plot([dx + 4 , dx + 4], [prevy, edgey], c="b", lw=3)
12                  prevy = edgey
13                  dy += childnode.height
14          else:
15              plt.plot([dx + 3.5, dx + 4.5], [y + 1.5, y + 1.5], c="k", lw=lw)
16            
17  Node.draw_node = draw_node
```

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

```python
def draw_node(self, ax=None, maxdepth=None, size=0.25, lw=0.8, dx=0, dy=0):  
    width = 8
    if ax is None:
        height = len(self.children) * 4
        fig, ax = plt.subplots(figsize=(width * size, height * size))
        ax.set_xlim(0, width)
        ax.set_ylim(0, height)   
        ax.invert_yaxis()
        ax.axis("off")
        for childnode in self.children:
            childnode.height = 4
            
    # 自分自身のノードを真ん中の位置になるように (dx, dy) からずらして描画する
    y = dy + (self.height - 3) / 2
    Marubatsu_GUI.draw_board(ax, self.mb, show_result=True, lw=lw, dx=dx, dy=y)
    # 子ノードが存在する場合に、エッジの線と子ノードを描画する
    if len(self.children) > 0:
        if maxdepth != self.depth:   
            plt.plot([dx + 3.5, dx + 4], [y + 1.5, y + 1.5], c="g", lw=3)
            prevy = None
            for childnode in self.children:
                if maxdepth is None:
                    Marubatsu_GUI.draw_board(ax, childnode.mb, show_result=True, dx=dx+5, dy=dy, lw=lw)
                edgey = dy + (childnode.height - 3) / 2 + 1.5
                plt.plot([dx + 4 , dx + 4.5], [edgey, edgey], c="r", lw=3)
                if prevy is not None:
                    plt.plot([dx + 4 , dx + 4], [prevy, edgey], c="b", lw=3)
                prevy = edgey
                dy += childnode.height
        else:
            plt.plot([dx + 3.5, dx + 4.5], [y + 1.5, y + 1.5], c="k", lw=lw)
            
Node.draw_node = draw_node
```
</details>

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

```diff_python
def draw_node(self, ax=None, maxdepth=None, size=0.25, lw=0.8, dx=0, dy=0):  
元と同じなので省略
        if maxdepth != self.depth:   
            plt.plot([dx + 3.5, dx + 4], [y + 1.5, y + 1.5], c="g", lw=3)
+           prevy = None
            for childnode in self.children:
                if maxdepth is None:
                    Marubatsu_GUI.draw_board(ax, childnode.mb, show_result=True, dx=dx+5, dy=dy, lw=lw)
                edgey = dy + (childnode.height - 3) / 2 + 1.5
                plt.plot([dx + 4 , dx + 4.5], [edgey, edgey], c="r", lw=3)
+               if prevy is not None:
+                   plt.plot([dx + 4 , dx + 4], [prevy, edgey], c="b", lw=3)
+               prevy = edgey
                dy += childnode.height
        else:
            plt.plot([dx + 3.5, dx + 4.5], [y + 1.5, y + 1.5], c="k", lw=lw)
            
Node.draw_node = draw_node
```
</details>

上記の修正後に、下記のプログラムを実行すると、実行結果のように、青い縦線のエッジが描画されるようになります。これでバランスの良いノードの描画の処理の実装は完了です。

```python
mbtree.draw_tree(mbtree.nodelist_by_depth[6][100], maxdepth=8)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/4ebaef13-f24c-0461-3be4-591062f9c8b3.png" width="200">

## ノードと子ノードの関係だけを描画した場合のバグ

今回の記事の最初で、キーワード引数 `ax` を記述せずに `draw_node` を呼び出した場合にバグがあることを示しました。実は、ノードをバランスよく表示するように修正した結果、その時と同様に、下記のプログラムを実行するとおかしな表示が行われます。

```python
mbtree.root.draw_node()
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/28db5d02-c071-3e21-8fa8-0f81dc63bea8.png" width="100">

おかしな点は、以下の通りです。

* ルートノードの局面とその右の緑色のエッジが描画されない
* 子ノードが少し上にずれて描画される

上記のような描画が行われる原因と修正方法について少し考えてみて下さい。

### ルートノードが描画されない原因の検証

ルートノードは `draw_node` の下記の部分で計算されますが、キーワード引数 `ax` を記述せずに `draw_node` を呼び出した場合は、__ノードの高さの計算を行っていない__ ため、下記の __`self.height`__ には、__直前の `draw_tree` で計算したノードの高さ__ が代入されています[^1]。

```python
    y = dy + (self.height - 3) / 2
    Marubatsu_GUI.draw_board(ax, self.mb, show_result=True, lw=lw, dx=dx, dy=y)
```

そのことは、下記のプログラムでルートノードの高さを表示すると、100 万以上の数値が表示されることから確認できます。従って、__ルートノードが描画されなかった理由__ は、`(self.height - 3) / 2` が約 50 万になるため、Axes の __表示範囲の外__ である、y 座標が約 50 万の位置に __ルートノードが描画されたから__ です。

```python
print(mbtree.root.height)
```

実行結果

```
1020672
```

これは、先程下記のプログラムを実行した結果、`draw_tree` の中で、深さ 8 までのゲーム木を描画した際の各ノードの高さが計算され、その時のルートノードの高さが `mbtree.root.height` に代入されたままになっているからです。

```python
mbtree.draw_tree(mbtree.nodelist_by_depth[6][100], maxdepth=8)
```

### 子ノードの描画位置がずれる問題の検証

子ノードの描画は下記のプログラムの 3 行目で行われますが、先程子ノードへのエッジの描画位置を変更したにも関わらず、__子ノードの描画位置をそれにあわせて修正していません__。そのため、子ノードの描画位置がずれて表示されてしまいます。

```python
1  for childnode in self.children:
2      if maxdepth is None:
3          Marubatsu_GUI.draw_board(ax, childnode.mb, show_result=True, dx=dx+5, dy=dy, lw=lw)
```

### バグの修正

上記から、下記のプログラムのように `draw_node` を修正することで、バグを修正することができます。なお、下記では、エッジの色と太さを元に戻す修正も行いましたが、その修正の説明と修正箇所への反映は省略します。

* __5 行目__：`ax` が `None` だった場合は、ノードの高さは 4 行目で計算してローカル変数 `height` に代入済なので、`height` 属性にその高さを代入する[^2]
* __7 行目__：子ノードを描画する際の `y` 座標を計算して `childnodey` に代入する
* __9 行目__：y 座標が `childnodey` の位置に子ノードを描画するように修正する
* __10 行目__：子ノードへのエッジの y 座標を `childnodey` から計算して `edgey` に代入する

```python
 1  def draw_node(self, ax=None, maxdepth=None, size=0.25, lw=0.8, dx=0, dy=0):  
 2      width = 8
 3      if ax is None:
 4          height = len(self.children) * 4
元と同じなので省略
 5          self.height = height
元と同じなので省略
 6              for childnode in self.children:
 7                  childnodey = dy + (childnode.height - 3) / 2
 8                  if maxdepth is None:
 9                      Marubatsu_GUI.draw_board(ax, childnode.mb, show_result=True, dx=dx+5, dy=childnodey, lw=lw)
10                  edgey = childnodey + 1.5
元と同じなので省略
11
12  Node.draw_node = draw_node
```

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

```python
def draw_node(self, ax=None, maxdepth=None, size=0.25, lw=0.8, dx=0, dy=0):  
    width = 8
    if ax is None:
        height = len(self.children) * 4
        fig, ax = plt.subplots(figsize=(width * size, height * size))
        ax.set_xlim(0, width)
        ax.set_ylim(0, height)   
        ax.invert_yaxis()
        ax.axis("off")
        for childnode in self.children:
            childnode.height = 4
        self.height = height
            
    # 自分自身のノードを真ん中の位置になるように (dx, dy) からずらして描画する
    y = dy + (self.height - 3) / 2
    Marubatsu_GUI.draw_board(ax, self.mb, show_result=True, lw=lw, dx=dx, dy=y)
    # 子ノードが存在する場合に、エッジの線と子ノードを描画する
    if len(self.children) > 0:
        if maxdepth != self.depth:   
            plt.plot([dx + 3.5, dx + 4], [y + 1.5, y + 1.5], c="k", lw=lw)
            prevy = None
            for childnode in self.children:
                childnodey = dy + (childnode.height - 3) / 2
                if maxdepth is None:
                    Marubatsu_GUI.draw_board(ax, childnode.mb, show_result=True, dx=dx+5, dy=childnodey, lw=lw)
                edgey = childnodey + 1.5
                plt.plot([dx + 4 , dx + 4.5], [edgey, edgey], c="k", lw=lw)
                if prevy is not None:
                    plt.plot([dx + 4 , dx + 4], [prevy, edgey], c="k", lw=lw)
                prevy = edgey
                dy += childnode.height
        else:
            plt.plot([dx + 3.5, dx + 4.5], [y + 1.5, y + 1.5], c="k", lw=lw)
            
Node.draw_node = draw_node
```
</details>

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

```diff_python
def draw_node(self, ax=None, maxdepth=None, size=0.25, lw=0.8, dx=0, dy=0):  
    width = 8
    if ax is None:
        height = len(self.children) * 4
元と同じなので省略
+       self.height = height
元と同じなので省略
            for childnode in self.children:
+               childnodey = dy + (childnode.height - 3) / 2
                if maxdepth is None:
-                   Marubatsu_GUI.draw_board(ax, childnode.mb, show_result=True, dx=dx+5, dy=dy, lw=lw)
+                   Marubatsu_GUI.draw_board(ax, childnode.mb, show_result=True, dx=dx+5, dy=childnodey, lw=lw)
-               edgey = dy + (childnode.height - 3) / 2 + 1.5
+               edgey = childnodey + 1.5
元と同じなので省略

Node.draw_node = draw_node
```
</details>

上記の修正後に、下記のプログラムを実行すると、実行結果のように、正しい描画が行われるようになることが確認できます。

```python
mbtree.root.draw_node()
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/a04c8ae4-b98b-df87-ef2d-17fb12a66249.png" width="100">

# 今回の記事のまとめ

今回の記事では、ゲーム木の視覚化の処理のバグの修正と、表示の改良を行いました。次回の記事ではゲーム木全体を把握するための視覚化の工夫について紹介する予定です。

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

以下のリンクから、本記事で入力して実行した JupyterLab のファイルを見ることができます。

https://github.com/ysgeso/marubatsu/blob/master/091/marubatsu.ipynb

以下のリンクは、今回の記事で更新した marubatsu.py です。

https://github.com/ysgeso/marubatsu/blob/master/091/marubatsu_new.py

以下のリンクは、今回の記事で更新した tree.py です。

https://github.com/ysgeso/marubatsu/blob/master/091/tree_new.py

# 次回の記事

https://qiita.com/ysgeso/items/0bad81b51c0ccbdbc3c7

[^1]:一度も `draw_tree` を実行していない場合は、`height` 属性に値が代入されていないため、今回の記事の冒頭で発生したエラーと同じエラーが発生します
[^2]:プログラム内の全てのローカル変数 `height` を `self.height` に修正するという方法も考えられますが、プログラムの修正箇所が多くなるので本記事では採用しませんでした