Pythonで〇×ゲームのAIを一から作成する　その77　Dropdown による AI の選択と play メソッドの改良

# 目次と前回の記事

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

https://qiita.com/ysgeso/items/1dbfec99a08b8996f53c

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

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

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

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

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

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

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

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

# `play` メソッドのバグの修正

今回の記事を記述する際に気が付いたのですが、__`play`__ メソッドには __バグ__ があります。それは、__下記__ のプログラムのように、__キーワード引数 `gui`__ を __記述せず__ に __`play`__ メソッドを呼び出して __CUI__ で __〇×ゲーム__ を __遊ぼうとした場合__ に、__実行結果__ のように __Dropdown が表示__ され、さらに __エラーが発生__ する点です。これらの原因について少し考えてみて下さい。

```python
from marubatsu import Marubatsu
from ai import ai1s

mb = Marubatsu()
mb.play(ai=[ai1s, ai1s])
```

実行結果（下図は、画像なので操作することはできません）

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/9da882dc-8564-4a99-bd43-e9a494f4a624.png" width="200">

```
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
Cell In[1], line 5
      2 from ai import ai1s
      4 mb = Marubatsu()
----> 5 mb.play(ai=[ai1s, ai1s])

File c:\Users\ys\ai\marubatsu\077\marubatsu.py:382, in Marubatsu.play(self, ai, ai_dict, params, verbose, seed, gui, size)
    379     fig.canvas.mpl_connect("button_press_event", on_mouse_down)     
    381 self.restart()
--> 382 return self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)

UnboundLocalError: cannot access local variable 'ax' where it is not associated with a value
```

:::note
上記のエラーメッセージは、以下のような意味を持ちます。

* __UnboundLocalError__
束縛されていない（unbound）ローカル（local）な名前に関するエラー
* __cannot access local variable 'ax' where it is not associated with a value__
値（value）が関連付けられていない（associated with）ax というローカル変数（local variable）にアクセス（access）することができない（cannot）
:::

## エラーの原因の検証

__変数__ に __値を代入__ することを、__束縛__ する（bound）、または __値__ を __関連付ける__ などと呼びます。従って、この __エラーメッセージ__ は、__`ax`__ という __ローカル変数__ に __値__ が __代入されていない__ という __意味__ を __表します__。そこで、__`play`__ メソッドの __中__ で、__`ax`__ に __値を代入__ する __処理__ を __探してみる__ と、__下記__ の部分で __行われている__ ことがわかります。

```python
    # gui が True の場合に、ゲーム盤を描画する画像を作成し、イベントハンドラに結びつける
    if gui:
        fig, ax = plt.subplots(figsize=[size, size])
```

__上記__ のプログラムの __`if gui:`__ から、__`ax`__ は、__`gui`__ に __`True` が代入__ されている __場合のみ__ で __値が代入される__ ことが __わかります__。__先程__ のように、__キーワード引数 `gui`__ を __記述せず__ に __`play`__ メソッドを __呼び出す__ と、__`gui`__ には __デフォルト値__ である __`False`__ が __代入__ されるので、エラーメッセージが示すように、__`ax`__ に __値__ が __代入されていない__ ことが __確認__ できました。

## エラーの修正方法

このエラーは、__`play_loop`__ を __呼び出す際__ に、__`ax`__ に __値__ が __代入されていない__ ことが __原因__ なので、__`gui`__ が __`False` の場合__ に、__`ax`__ に __何らかの値__ を __代入__ することで __修正__ できます。そこで、__`ax`__ に __どのような値__ を __代入すればよいか__ について __検討__ するために、__`play_loop`__ の __中__ で、__`ax`__ が __どのように利用__ されているかについて __調べる__ ことにします。

__`play_loop`__ の __中__ で __`ax`__ に __関する処理__ が __行われる__ のは、__下記__ の __4、6 行目__ の部分です。

```python
略
1  if gui:
2      # AI どうしの対戦の場合は画面を描画しない
3      if ai[0] is None or ai[1] is None:
4          self.draw_board(ax)
略
5  if gui:
6      self.draw_board(ax)
略
```

__いずれ__ も、__`gui`__ に __`True`__ が __代入__ されている __場合のみ__ で __実行される処理__ なので、__`gui`__ に __`False`__ が __代入__ されている場合は、__`play_loop`__ の __中__ では __`ax`__ は __利用されない__ ことが __わかります__。そのため、__`gui`__ に __`False`__ が __代入__ されている場合は、__`ax`__ に __何を代入__ しても __かまわない__ ことが __わかりました__。そこで、__本記事__ では __下記__ のプログラムのように、__`gui`__ に __`False` が代入__ されていた場合は、__`ax`__ に __`None` を代入__ することにします。

* __8、9 行目__：__`gui`__ が __`True` でない__、すなわち __`False` の場合__ に __`ax`__ に __`None`__ を __代入する__。__この処理__ を __10 行目より前__ に __実行__ することで、__`play_loop`__ を __呼び出す際__ に、__`ax`__ に __値__ が __必ず代入される__ ようになる

```python
 1  import matplotlib.pyplot as plt
 2  import ipywidgets as widgets
 3  import math
 4
 5  def play(self, ai, ai_dict=None, params=None, verbose=True, seed=None, gui=False, size=3):      
元と同じなので省略
 6      # gui が True の場合に、ゲーム盤を描画する画像を作成し、イベントハンドラに結びつける
 7      if gui:
元と同じなので省略
 8      else:
 9          ax = None
元と同じなので省略
10      return self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
11
12  Marubatsu.play = play 
```

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

```python
import matplotlib.pyplot as plt
import ipywidgets as widgets
import math

def play(self, ai, ai_dict=None, params=None, verbose=True, seed=None, gui=False, size=3):      
    # ai_dict が None の場合は、空の list で置き換える
    if ai_dict is None:
        ai_dict = {}
    # params が None の場合のデフォルト値を設定する
    if params is None:
        params = [{}, {}]

    # seed が None でない場合は、seed を乱数の種として設定する
    if seed is not None:
        random.seed(seed)
    
    # それぞれの手番の担当を表す Dropdown の項目の値を記録する list を初期化する
    select_values = []
    # ai に代入されている内容を ai_dict に追加する
    for i in range(2):
        # ラベルと項目の値を計算する
        if ai[i] is None:
            label = "人間"
            value = "人間"
        else:
            label = ai[i].__name__        
            value = ai[i]
        # value を select_values に常に登録する
        select_values.append(value)
        # value が ai_values に登録済かどうかを判定する
        if value not in ai_dict.values():
            # 項目を登録する
            ai_dict[label] = value
    
    # 〇 と × の Dropdown を作成する   
    dropdown_circle = widgets.Dropdown(
        options=ai_dict,
        description="〇",
        layout=widgets.Layout(width="100px"),
        style={"description_width": "20px"},
        value=select_values[0],
    )
    dropdown_cross = widgets.Dropdown(
        options=ai_dict,
        description="×",
        layout=widgets.Layout(width="100px"),
        style={"description_width": "20px"},
        value=select_values[1],
    )       
    
    # リセットボタンを作成する
    button = widgets.Button(
        description="リセット",
        layout=widgets.Layout(width="100px"),
    )
    
    # 〇 と × の dropdown と リセットボタンを横に配置した HBox を作成し、表示する
    hbox = widgets.HBox([dropdown_circle, dropdown_cross, button])
    display(hbox)
    
    # リセットボタンのイベントハンドラを定義する
    def on_button_clicked(b):
        self.restart()
        self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui) 
        
    # イベントハンドラをリセットボタンに結びつける
    button.on_click(on_button_clicked)

    # gui が True の場合に、ゲーム盤を描画する画像を作成し、イベントハンドラに結びつける
    if gui:
        fig, ax = plt.subplots(figsize=[size, size])
        fig.canvas.toolbar_visible = False
        fig.canvas.header_visible = False
        fig.canvas.footer_visible = False
        fig.canvas.resizable = False          
        
        # ローカル関数としてイベントハンドラを定義する
        def on_mouse_down(event):
            # Axes の上でマウスを押していた場合のみ処理を行う
            if event.inaxes and self.status == Marubatsu.PLAYING:
                x = math.floor(event.xdata)
                y = math.floor(event.ydata)
                self.move(x, y)                
                self.draw_board(ax)

                # 次の手番の処理を行うメソッドを呼び出す
                self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
                
        # fig の画像にマウスを押した際のイベントハンドラを結び付ける
        fig.canvas.mpl_connect("button_press_event", on_mouse_down)     
    else:
        ax = None

    self.restart()
    return self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
 
Marubatsu.play = play 
```
</details>

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

```diff_python
import matplotlib.pyplot as plt
import ipywidgets as widgets
import math

def play(self, ai, ai_dict=None, params=None, verbose=True, seed=None, gui=False, size=3):      
元と同じなので省略
    # gui が True の場合に、ゲーム盤を描画する画像を作成し、イベントハンドラに結びつける
    if gui:
元と同じなので省略
+   else:
+       ax = None
元と同じなので省略
    return self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)

Marubatsu.play = play 
```
</details>

__上記__ の __修正後__ に、__下記__ のプログラムを __実行__ すると、__エラー__ は __発生しなくなります__ が、__実行結果__ のように __CUI__ で〇×ゲームを遊んでいるにも __関わらず__ 、最初に __Dropdown が表示__ されてしまうという __問題__ は __解消されていない__ ことが __わかります__。

```python
mb.play(ai=[ai1s, ai1s])
```

実行結果（下図は、画像なので操作することはできません）

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/9da882dc-8564-4a99-bd43-e9a494f4a624.png" width="200">

```
Turn o
...
...
...
以下略（ai1s どうしの対戦が表示される）
```

## CUI での Dropdown の表示の修正

__`gui`__ に __`False` が代入__ されている場合でも __Dropdown が表示__ されるのは、__`play`__ メソッド内で、__Dropdown の処理__ が __`gui`__ に __代入__ されている __値__ に __関わらず__、__必ず実行__ されるように __記述されている__ からです。__下記__ のプログラムのように、__それらの処理__ を __`if gui:`__ の __ブロックの中__ に __移動__ することで、__この問題__ を __解決__ することが __できます__。なお、__下記__ のプログラムは、__`gui`__ の __ブロックの中__ に __移動__ した __プログラムのみ__ を示します。

```python      
    # gui が True の場合に、ゲーム盤を描画する画像を作成し、イベントハンドラに結びつける
    if gui:
        # それぞれの手番の担当を表す Dropdown の項目の値を記録する list を初期化する
        select_values = []
        # ai に代入されている内容を ai_dict に追加する
        for i in range(2):
            # ラベルと項目の値を計算する
            if ai[i] is None:
                label = "人間"
                value = "人間"
            else:
                label = ai[i].__name__        
                value = ai[i]
            # value を select_values に常に登録する
            select_values.append(value)
            # value が ai_values に登録済かどうかを判定する
            if value not in ai_dict.values():
                # 項目を登録する
                ai_dict[label] = value
        
        # 〇 と × の Dropdown を作成する   
        dropdown_circle = widgets.Dropdown(
            options=ai_dict,
            description="〇",
            layout=widgets.Layout(width="100px"),
            style={"description_width": "20px"},
            value=select_values[0],
        )
        dropdown_cross = widgets.Dropdown(
            options=ai_dict,
            description="×",
            layout=widgets.Layout(width="100px"),
            style={"description_width": "20px"},
            value=select_values[1],
        )       
        
        # リセットボタンを作成する
        button = widgets.Button(
            description="リセット",
            layout=widgets.Layout(width="100px"),
        )
        
        # 〇 と × の dropdown と リセットボタンを横に配置した HBox を作成し、表示する
        hbox = widgets.HBox([dropdown_circle, dropdown_cross, button])
        display(hbox)
        
        # リセットボタンのイベントハンドラを定義する
        def on_button_clicked(b):
            self.restart()
            self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
```

<details><summary><strong>プログラム全体</strong></summary>

```python
def play(self, ai, ai_dict=None, params=None, verbose=True, seed=None, gui=False, size=3):      
    # ai_dict が None の場合は、空の list で置き換える
    if ai_dict is None:
        ai_dict = {}
    # params が None の場合のデフォルト値を設定する
    if params is None:
        params = [{}, {}]

    # seed が None でない場合は、seed を乱数の種として設定する
    if seed is not None:
        random.seed(seed)
    
    # gui が True の場合に、ゲーム盤を描画する画像を作成し、イベントハンドラに結びつける
    if gui:
        # それぞれの手番の担当を表す Dropdown の項目の値を記録する list を初期化する
        select_values = []
        # ai に代入されている内容を ai_dict に追加する
        for i in range(2):
            # ラベルと項目の値を計算する
            if ai[i] is None:
                label = "人間"
                value = "人間"
            else:
                label = ai[i].__name__        
                value = ai[i]
            # value を select_values に常に登録する
            select_values.append(value)
            # value が ai_values に登録済かどうかを判定する
            if value not in ai_dict.values():
                # 項目を登録する
                ai_dict[label] = value
        
        # 〇 と × の Dropdown を作成する   
        dropdown_circle = widgets.Dropdown(
            options=ai_dict,
            description="〇",
            layout=widgets.Layout(width="100px"),
            style={"description_width": "20px"},
            value=select_values[0],
        )
        dropdown_cross = widgets.Dropdown(
            options=ai_dict,
            description="×",
            layout=widgets.Layout(width="100px"),
            style={"description_width": "20px"},
            value=select_values[1],
        )       
        
        # リセットボタンを作成する
        button = widgets.Button(
            description="リセット",
            layout=widgets.Layout(width="100px"),
        )
        
        # 〇 と × の dropdown と リセットボタンを横に配置した HBox を作成し、表示する
        hbox = widgets.HBox([dropdown_circle, dropdown_cross, button])
        display(hbox)
        
        # リセットボタンのイベントハンドラを定義する
        def on_button_clicked(b):
            self.restart()
            self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui) 
            
        # イベントハンドラをリセットボタンに結びつける
        button.on_click(on_button_clicked)

        fig, ax = plt.subplots(figsize=[size, size])
        fig.canvas.toolbar_visible = False
        fig.canvas.header_visible = False
        fig.canvas.footer_visible = False
        fig.canvas.resizable = False          
        
        # ローカル関数としてイベントハンドラを定義する
        def on_mouse_down(event):
            # Axes の上でマウスを押していた場合のみ処理を行う
            if event.inaxes and self.status == Marubatsu.PLAYING:
                x = math.floor(event.xdata)
                y = math.floor(event.ydata)
                self.move(x, y)                
                self.draw_board(ax)

                # 次の手番の処理を行うメソッドを呼び出す
                self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
                
        # fig の画像にマウスを押した際のイベントハンドラを結び付ける
        fig.canvas.mpl_connect("button_press_event", on_mouse_down)     
    else:
        ax = None

    self.restart()
    return self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
 
Marubatsu.play = play
```
</details>

__上記__ の __修正後__ に、__下記__ のプログラムを __実行__ すると、__実行結果__ のように __Dropdown__ は __表示されなく__ なり、__CUI__ で __〇×ゲーム__ が __実行される__ ようになります。

```python
mb.play(ai=[ai1s, ai1s])
```

実行結果

```
Turn o
...
...
...
以下略（ai1s どうしの対戦が表示される）
```

## バグを見逃した原因

__今回のバグ__ を これまで __見逃し__ ていた __原因__ は、__GUI__ で __〇×ゲーム__ を __遊べるよう__ に __`play`__ メソッドを __修正した際__ に、__CUI__ での __`play`__ メソッドの __処理__ が __正しく動作するか__ を __確認しなかった__ ためです。このように、プログラムで __何かを修正__ した場合は、__それまで__ に __行えていた処理__ が __正しく動作するか__ を __確認__ することを __怠らない__ ことが __重要__ です。

# マジックコマンドを Python で実行する方法

__これまで__ のプログラムでは、__`play`__ メソッドの __実引数__ に __`gui=True`__ を __記述__ して __〇×ゲーム__ を __GUI__ で __遊ぶ際__ に、__`%matplotlib widget`__ という __[マジックコマンド](https://qiita.com/ysgeso/items/1920c68f5a1e8a998796#ipympl-%E3%81%AE%E5%88%A9%E7%94%A8%E6%96%B9%E6%B3%95)__ を __実行する必要__ が __ありました__。[以前の記事](https://qiita.com/ysgeso/items/1920c68f5a1e8a998796#ipympl-%E3%81%AE%E5%88%A9%E7%94%A8%E6%96%B9%E6%B3%95)で、__マジックコマンド__ は、__Python__ の __プログラムではない__ ので、__marubatsu.py__ に __記述__ すると __エラーが発生__ するため、__JupyterLab 上__ で __実行__ する __必要がある__ と説明しましたが、__下記__ のプログラムで、__`%matplotlib widget`__ の __マジックコマンド__ を __Python__ の __プログラム__ で __実行できます__。

```python
get_ipython().run_line_magic('matplotlib', 'widget')
```

:::note
__`%matplotlib widget` 以外__ の __マジックコマンド__ も __同様の方法__ で __実行できます__。
:::

:::note
__`get_ipython`__ は、__IPython__ モジュールで __定義__ されていますが、__インポートしなくても利用できる__ ようです。__`get_ipython`__ を __実行__ した際に __エラーが発生__ した場合は、__下記__ のプログラムを __実行__ して __インポート__ して下さい。

```python
from IPython import get_ipython
```
:::

そこで、__下記__ のプログラムの __5 行目__ のように、__`gui`__ に __`True`__ が __代入__ されている __場合__ に __上記__ の __処理を行う__ ように __`play`__ メソッドを __修正__ することにします。

```python
1  def play(self, ai, ai_dict=None, params=None, verbose=True, seed=None, gui=False, size=3):      
元と同じなので省略
2      # gui が True の場合に、ゲーム盤を描画する画像を作成し、イベントハンドラに結びつける
3      if gui:
4          # %matplotlib widget のマジックコマンドを実行する
5          get_ipython().run_line_magic('matplotlib', 'widget')
元と同じなので省略
6
7  Marubatsu.play = play
```

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

```python
def play(self, ai, ai_dict=None, params=None, verbose=True, seed=None, gui=False, size=3):      
    # ai_dict が None の場合は、空の list で置き換える
    if ai_dict is None:
        ai_dict = {}
    # params が None の場合のデフォルト値を設定する
    if params is None:
        params = [{}, {}]
   
    # seed が None でない場合は、seed を乱数の種として設定する
    if seed is not None:
        random.seed(seed)
  
    # gui が True の場合に、ゲーム盤を描画する画像を作成し、イベントハンドラに結びつける
    if gui:
        # %matplotlib widget のマジックコマンドを実行する
        get_ipython().run_line_magic('matplotlib', 'widget')
        
        # それぞれの手番の担当を表す Dropdown の項目の値を記録する list を初期化する
        select_values = []
        # ai に代入されている内容を ai_dict に追加する
        for i in range(2):
            # ラベルと項目の値を計算する
            if ai[i] is None:
                label = "人間"
                value = "人間"
            else:
                label = ai[i].__name__        
                value = ai[i]
            # value を select_values に常に登録する
            select_values.append(value)
            # value が ai_values に登録済かどうかを判定する
            if value not in ai_dict.values():
                # 項目を登録する
                ai_dict[label] = value
        
        # 〇 と × の Dropdown を作成する   
        dropdown_circle = widgets.Dropdown(
            options=ai_dict,
            description="〇",
            layout=widgets.Layout(width="100px"),
            style={"description_width": "20px"},
            value=select_values[0],
        )
        dropdown_cross = widgets.Dropdown(
            options=ai_dict,
            description="×",
            layout=widgets.Layout(width="100px"),
            style={"description_width": "20px"},
            value=select_values[1],
        )       
        
        # リセットボタンを作成する
        button = widgets.Button(
            description="リセット",
            layout=widgets.Layout(width="100px"),
        )
        
        # 〇 と × の dropdown と リセットボタンを横に配置した HBox を作成し、表示する
        hbox = widgets.HBox([dropdown_circle, dropdown_cross, button])
        display(hbox)
        
        # リセットボタンのイベントハンドラを定義する
        def on_button_clicked(b):
            self.restart()
            self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
            
        # イベントハンドラをリセットボタンに結びつける
        button.on_click(on_button_clicked)
  
        fig, ax = plt.subplots(figsize=[size, size])
        fig.canvas.toolbar_visible = False
        fig.canvas.header_visible = False
        fig.canvas.footer_visible = False
        fig.canvas.resizable = False          
        
        # ローカル関数としてイベントハンドラを定義する
        def on_mouse_down(event):
            # Axes の上でマウスを押していた場合のみ処理を行う
            if event.inaxes and self.status == Marubatsu.PLAYING:
                x = math.floor(event.xdata)
                y = math.floor(event.ydata)
                self.move(x, y)                
                self.draw_board(ax)

                # 次の手番の処理を行うメソッドを呼び出す
                self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
                
        # fig の画像にマウスを押した際のイベントハンドラを結び付ける
        fig.canvas.mpl_connect("button_press_event", on_mouse_down)     
    else:
        ax = None

    self.restart()
    return self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)

Marubatsu.play = play
```
</details>

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

```diff_python
def play(self, ai, ai_dict=None, params=None, verbose=True, seed=None, gui=False, size=3):      
元と同じなので省略
    # gui が True の場合に、ゲーム盤を描画する画像を作成し、イベントハンドラに結びつける
    if gui:
        # %matplotlib widget のマジックコマンドを実行する
+       get_ipython().run_line_magic('matplotlib', 'widget')
元と同じなので省略
```
</details>

__上記__ の __修正後__ に、__下記__ のプログラムで __`gui_play`__ を __実行__ すると、__実行結果__ のように、__`%matplotlib widget`__ を __実行しなくても__ GUI で __〇×ゲームを遊べる__ ことが __確認できます__。

```python
from util import gui_play

gui_play()
```

実行結果（下図は、画像なので操作することはできません）

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/1d266fa8-4a8b-aa38-8265-4418e5d484e0.png" width="200">

# Dropdown による AI の選択

__これまで__ に __作成__ した __`play`__ メソッドは、__手番を担当__ する __AI を選択する__ ための __Dropdown__ の __表示__ を __行うだけ__ で、__Dropdown__ の __選択を変更__ しても __何の処理__ も __行われません__。そこで、__Dropdown__ の __選択を変更__ することで、__手番を担当__ する __AI__ を __変更する__ ようにします。

なお、__実際__ には、__Dropdown__ で __人間を選択__ することも __できます__ が、毎回「__Dropdown__ で __AI または人間__ を __選択__ する」のように __表記__ するのは __冗長__ なので、__以後__ は、__人間の選択__ を __省略__ して、「__Dropdown__ で __AI を選択__ する」のように __表記__ します。

## Dropdown の選択の変更を反映させるタイミング

まず、__Dropdown__ の __選択の変更__ を __どの時点__ で __〇×ゲーム__ に __反映させるか__ を __決める必要__ が __あります__。__候補__ としては __下記__ の __3 つ__ が挙げられるでしょう。

1. __Dropdown__ が __変更された時点__ で __即座__ に __変更を反映__ する
2. __ゲーム__ を __リセットした時点__ で __変更を反映__ し、新しい対戦を開始する
3. __Dropdown__ の __変更を適用__ する __ボタンを用意__ し、__押された時__ に __変更を反映__ する

__上記__ の __どれを採用__ しても __構いません__ が、__1__ の「__Dropdown__ が __変更された時点__ で __即座__ に __変更を反映__ する」という __方法__ は、__Dropdown__ の __操作ミス__ によって __意図しない変更__ が行われてしまう __可能性がある__ ので、__本記事__ では __2__ と __3__ の __方法__ を __採用__ することにします。

## ゲームのリセットによる Dropdown の反映

[以前の記事](https://qiita.com/ysgeso/items/51842efc6193ed456ebc#valueindexlabel)で説明したように、__Dropdown__ が __選択中__ の __項目の値__ は、__Dropdown__ の __`value` 属性__ に __代入__ されています。そのため、__下記__ のプログラムのように、__リセットボタン__ の __イベントハンドラ__ である __`on_button_clicked`__ を __修正__ することで、__ボタン__ を __クリックした時__ に、__Dropdown__ に __選択__ されている __AI__ で __新しい対戦__ が __開始される__ ようになります。

* __5 行目__：__〇 の担当__ を表す __Dropdown__  の __`value`__ 属性が __`"人間"`__ の場合は、__`ai[0]`__ に __人間の担当__ を表す __`None` を代入__ する。__それ以外__ の場合は、__`value`__ 属性に __AI__ の __関数オブジェクト__ が __代入されている__ ので、__`value`__ 属性の __値__ を __`ai[0]`__ に __そのまま代入__ する
* __6 行目__：__上記__ と __同様の処理__ を __× の担当__ を表す __Dropdown__ に対して __行う__

```python
1  def play(self, ai, ai_dict=None, params=None, verbose=True, seed=None, gui=False, size=3):      
元と同じなので省略      
2          # リセットボタンのイベントハンドラを定義する
3          def on_button_clicked(b):
4              self.restart()
5              ai[0] = None if dropdown_circle.value == "人間" else dropdown_circle.value 
6              ai[1] = None if dropdown_cross.value == "人間" else dropdown_cross.value 
7              self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
元と同じなので省略
8
9  Marubatsu.play = play
```

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

```python
def play(self, ai, ai_dict=None, params=None, verbose=True, seed=None, gui=False, size=3):      
    # ai_dict が None の場合は、空の list で置き換える
    if ai_dict is None:
        ai_dict = {}
    # params が None の場合のデフォルト値を設定する
    if params is None:
        params = [{}, {}]
   
    # seed が None でない場合は、seed を乱数の種として設定する
    if seed is not None:
        random.seed(seed)
  
    # gui が True の場合に、ゲーム盤を描画する画像を作成し、イベントハンドラに結びつける
    if gui:
        # %matplotlib widget のマジックコマンドを実行する
        get_ipython().run_line_magic('matplotlib', 'widget')
        
        # それぞれの手番の担当を表す Dropdown の項目の値を記録する list を初期化する
        select_values = []
        # ai に代入されている内容を ai_dict に追加する
        for i in range(2):
            # ラベルと項目の値を計算する
            if ai[i] is None:
                label = "人間"
                value = "人間"
            else:
                label = ai[i].__name__        
                value = ai[i]
            # value を select_values に常に登録する
            select_values.append(value)
            # value が ai_values に登録済かどうかを判定する
            if value not in ai_dict.values():
                # 項目を登録する
                ai_dict[label] = value
        
        # 〇 と × の Dropdown を作成する   
        dropdown_circle = widgets.Dropdown(
            options=ai_dict,
            description="〇",
            layout=widgets.Layout(width="100px"),
            style={"description_width": "20px"},
            value=select_values[0],
        )
        dropdown_cross = widgets.Dropdown(
            options=ai_dict,
            description="×",
            layout=widgets.Layout(width="100px"),
            style={"description_width": "20px"},
            value=select_values[1],
        )       
        
        # リセットボタンを作成する
        button = widgets.Button(
            description="リセット",
            layout=widgets.Layout(width="100px"),
        )
        
        # 〇 と × の dropdown と リセットボタンを横に配置した HBox を作成し、表示する
        hbox = widgets.HBox([dropdown_circle, dropdown_cross, button])
        display(hbox)
        
        # リセットボタンのイベントハンドラを定義する
        def on_button_clicked(b):
            self.restart()
            ai[0] = None if dropdown_circle.value == "人間" else dropdown_circle.value 
            ai[1] = None if dropdown_cross.value == "人間" else dropdown_cross.value 
            self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
            
        # イベントハンドラをリセットボタンに結びつける
        button.on_click(on_button_clicked)
  
        fig, ax = plt.subplots(figsize=[size, size])
        fig.canvas.toolbar_visible = False
        fig.canvas.header_visible = False
        fig.canvas.footer_visible = False
        fig.canvas.resizable = False          
        
        # ローカル関数としてイベントハンドラを定義する
        def on_mouse_down(event):
            # Axes の上でマウスを押していた場合のみ処理を行う
            if event.inaxes and self.status == Marubatsu.PLAYING:
                x = math.floor(event.xdata)
                y = math.floor(event.ydata)
                self.move(x, y)                
                self.draw_board(ax)

                # 次の手番の処理を行うメソッドを呼び出す
                self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
                
        # fig の画像にマウスを押した際のイベントハンドラを結び付ける
        fig.canvas.mpl_connect("button_press_event", on_mouse_down)    
    else:
        ax=None

    self.restart()
    return self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)

Marubatsu.play = play
```
</details>

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

```diff_python
def play(self, ai, ai_dict=None, params=None, verbose=True, seed=None, gui=False, size=3):      
元と同じなので省略      
        # リセットボタンのイベントハンドラを定義する
        def on_button_clicked(b):
            self.restart()
+           ai[0] = None if dropdown_circle.value == "人間" else dropdown_circle.value 
+           ai[1] = None if dropdown_cross.value == "人間" else dropdown_cross.value 
            self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
元と同じなので省略

Marubatsu.play = play
```
</details>

__上記__ の __修正後__ に、__下記__ のプログラムで __`gui_play`__ を __実行した後__ で、__Dropdown__ を __変更__ してから __リセットボタン__ を __押す__ と、__変更した AI__ で __新しい対戦__ が __開始__ されます。__実行結果__ は __省略__ しますが、__実際__ に __Dropdown__ に __様々な組み合わせ__ を __選択__ して __確認__ して下さい。

```python
gui_play()
```

## 変更ボタンによる Dropdown の反映

__AI の担当__ を __変更するボタン__ が __行う処理__ は、__ゲーム__ を __リセットする処理__ を __除けば__、__リセットボタン__ が __行う処理__ と __同じ__ です。そのため、__その処理__ は __下記__ のプログラムのように __記述できます__。

なお、__ボタン__ が __2 つ__ に __増えた__ ので、__それぞれ__ の __ボタン__ の __ウィジェット__ を __代入する変数__ の __名前__ を、__`change_button`__、__`reset_button`__ のようにして __区別できる__ ように __しました__。__ボタン__ を __クリック__ した場合の __イベントハンドラ__ の __名前__ も __同様__ です。

* __3 ~ 6 行目__：__変更ボタン__ の __ウィジェット__ を __作成__ する
* __9 行目__：__リセットボタン__ の __ウィジェット__ を __代入__ する __変数の名前__ を __`reset_button`__ に __変更__ する
* __15 行目__：__HBox__ に __変更ボタン__ の __ウィジェット__ を __追加__ する
* __19 ~ 22 行目__：__変更ボタン__ を __クリック__ した際の __イベントハンドラ__ を __定義__ する
* __25 行目__：__リセットボタン__ を __クリック__ した際の __イベントハンドラ__ の __名前__ を __`on_reset_button_clicked`__ に __変更__ する
* __32、33 行目__：それぞれの __ボタン__ をそれぞれの __イベントハンドラ__ に __結びつける__

```python
 1  def play(self, ai, ai_dict=None, params=None, verbose=True, seed=None, gui=False, size=3):      
元と同じなので省略
 2          # 変更ボタンを作成する
 3          change_button = widgets.Button(
 4              description="変更",
 5              layout=widgets.Layout(width="100px"),
 6          )
 7       
 8          # リセットボタンを作成する
 9          reset_button = widgets.Button(
10              description="リセット",
11              layout=widgets.Layout(width="100px"),
12          )
13        
14          # 〇 と × の dropdown と ボタンを横に配置した HBox を作成し、表示する
15        hbox = widgets.HBox([dropdown_circle, dropdown_cross, change_button, reset_button])
16          display(hbox)
17       
18          # 変更ボタンのイベントハンドラを定義する
19          def on_change_button_clicked(b):
20              ai[0] = None if dropdown_circle.value == "人間" else dropdown_circle.value 
21              ai[1] = None if dropdown_cross.value == "人間" else dropdown_cross.value 
22              self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
23
24          # リセットボタンのイベントハンドラを定義する
25          def on_reset_button_clicked(b):
26              self.restart()
27              ai[0] = None if dropdown_circle.value == "人間" else dropdown_circle.value 
28              ai[1] = None if dropdown_cross.value == "人間" else dropdown_cross.value 
29              self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
30            
31          # イベントハンドラをボタンに結びつける
32          change_button.on_click(on_change_button_clicked)
33          reset_button.on_click(on_reset_button_clicked)
元と同じなので省略
34
35  Marubatsu.play = play
```

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

```python
def play(self, ai, ai_dict=None, params=None, verbose=True, seed=None, gui=False, size=3):      
    # ai_dict が None の場合は、空の list で置き換える
    if ai_dict is None:
        ai_dict = {}
    # params が None の場合のデフォルト値を設定する
    if params is None:
        params = [{}, {}]
   
    # seed が None でない場合は、seed を乱数の種として設定する
    if seed is not None:
        random.seed(seed)
  
    # gui が True の場合に、ゲーム盤を描画する画像を作成し、イベントハンドラに結びつける
    if gui:
        # %matplotlib widget のマジックコマンドを実行する
        get_ipython().run_line_magic('matplotlib', 'widget')
        
        # それぞれの手番の担当を表す Dropdown の項目の値を記録する list を初期化する
        select_values = []
        # ai に代入されている内容を ai_dict に追加する
        for i in range(2):
            # ラベルと項目の値を計算する
            if ai[i] is None:
                label = "人間"
                value = "人間"
            else:
                label = ai[i].__name__        
                value = ai[i]
            # value を select_values に常に登録する
            select_values.append(value)
            # value が ai_values に登録済かどうかを判定する
            if value not in ai_dict.values():
                # 項目を登録する
                ai_dict[label] = value
        
        # 〇 と × の Dropdown を作成する   
        dropdown_circle = widgets.Dropdown(
            options=ai_dict,
            description="〇",
            layout=widgets.Layout(width="100px"),
            style={"description_width": "20px"},
            value=select_values[0],
        )
        dropdown_cross = widgets.Dropdown(
            options=ai_dict,
            description="×",
            layout=widgets.Layout(width="100px"),
            style={"description_width": "20px"},
            value=select_values[1],
        )       
        
        # 変更ボタンを作成する
        change_button = widgets.Button(
            description="変更",
            layout=widgets.Layout(width="100px"),
        )
        
        # リセットボタンを作成する
        reset_button = widgets.Button(
            description="リセット",
            layout=widgets.Layout(width="100px"),
        )
        
        # 〇 と × の dropdown と ボタンを横に配置した HBox を作成し、表示する
        hbox = widgets.HBox([dropdown_circle, dropdown_cross, change_button, reset_button])
        display(hbox)
        
        # 変更ボタンのイベントハンドラを定義する
        def on_change_button_clicked(b):
            ai[0] = None if dropdown_circle.value == "人間" else dropdown_circle.value 
            ai[1] = None if dropdown_cross.value == "人間" else dropdown_cross.value 
            self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)

        # リセットボタンのイベントハンドラを定義する
        def on_reset_button_clicked(b):
            self.restart()
            ai[0] = None if dropdown_circle.value == "人間" else dropdown_circle.value 
            ai[1] = None if dropdown_cross.value == "人間" else dropdown_cross.value 
            self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
            
        # イベントハンドラをボタンに結びつける
        change_button.on_click(on_change_button_clicked)
        reset_button.on_click(on_reset_button_clicked)
  
        fig, ax = plt.subplots(figsize=[size, size])
        fig.canvas.toolbar_visible = False
        fig.canvas.header_visible = False
        fig.canvas.footer_visible = False
        fig.canvas.resizable = False          
        
        # ローカル関数としてイベントハンドラを定義する
        def on_mouse_down(event):
            # Axes の上でマウスを押していた場合のみ処理を行う
            if event.inaxes and self.status == Marubatsu.PLAYING:
                x = math.floor(event.xdata)
                y = math.floor(event.ydata)
                self.move(x, y)                
                self.draw_board(ax)

                # 次の手番の処理を行うメソッドを呼び出す
                self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
                
        # fig の画像にマウスを押した際のイベントハンドラを結び付ける
        fig.canvas.mpl_connect("button_press_event", on_mouse_down)     
    else:
        ax = None

    self.restart()
    return self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)

Marubatsu.play = play
```
</details>

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

```diff_python
def play(self, ai, ai_dict=None, params=None, verbose=True, seed=None, gui=False, size=3):      
元と同じなので省略
        # 変更ボタンを作成する
+       change_button = widgets.Button(
+           description="変更",
+           layout=widgets.Layout(width="100px"),
+       )
        
        # リセットボタンを作成する
-       button = widgets.Button(
+       reset_button = widgets.Button(
            description="リセット",
            layout=widgets.Layout(width="100px"),
        )
        
        # 〇 と × の dropdown と ボタンを横に配置した HBox を作成し、表示する
-       hbox = widgets.HBox([dropdown_circle, dropdown_cross, button])
+       hbox = widgets.HBox([dropdown_circle, dropdown_cross, change_button, reset_button])
        display(hbox)
        
        # 変更ボタンのイベントハンドラを定義する
+       def on_change_button_clicked(b):
+           ai[0] = None if dropdown_circle.value == "人間" else dropdown_circle.value 
+           ai[1] = None if dropdown_cross.value == "人間" else dropdown_cross.value 
+           self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)

        # リセットボタンのイベントハンドラを定義する
-       def on_button_clicked(b):
+       def on_reset_button_clicked(b):
            self.restart()
            ai[0] = None if dropdown_circle.value == "人間" else dropdown_circle.value 
            ai[1] = None if dropdown_cross.value == "人間" else dropdown_cross.value 
            self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
            
        # イベントハンドラをボタンに結びつける
+       change_button.on_click(on_change_button_clicked)
-       button.on_click(on_button_clicked)
+       reset_button.on_click(on_reset_button_clicked)
元と同じなので省略

Marubatsu.play = play
```
</details>

__上記__ の __修正後__ に、__下記__ のプログラムで __`gui_play`__ を __実行__ すると、__実行結果__ のように、__変更ボタン__ が __表示される__ ようになります。また、__Dropdown__ を __変更__ してから __変更ボタン__ を __押す__ と、__ゲーム__ が __リセットされず__ に、__AI__ の __担当のみ__ が __変更__ されます。__実行結果__ は __省略__ しますが、__実際__ に __Dropdown__ に __様々な組み合わせ__ を __選択__ して __確認__ して下さい。

```python
gui_play()
```

実行結果（下図は、画像なので操作することはできません）

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/50edc05d-9b85-fc43-6485-a48b581937a5.png" width="200">

### 変更ボタンのイベントハンドラに関する注意点

__変更ボタン__ の __イベントハンドラ__ の __処理__ は、それぞれの __手番を担当__ する __AI を変更__ する __処理__ なので、__下記__ のプログラムの __5 行目__ の __`self_loop`__ を __呼び出す処理__ は __必要ない__ のではないかと __思った人__ は __いないでしょうか__？しかし、下記の __5 行目__ を __削除__ すると __バグが発生__ します。どのようなバグが発生するかについて少し考えてみて下さい。

```python
1  # 変更ボタンのイベントハンドラを定義する
2  def on_change_button_clicked(b):
3      ai[0] = None if dropdown_circle.value == "人間" else dropdown_circle.value 
4      ai[1] = None if dropdown_cross.value == "人間" else dropdown_cross.value 
5      self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
```

確かに、上記の __5 行目__ を __削除しても__ それぞれの __手番__ を __担当する AI__ は __変化__ しますが、__人間の手番__ の時に、__人間から AI に変更__ しても、__変更した AI__ が __着手__ を __行わない__ という __問題が発生__ します。その __理由__ は上記の __5 行目__ が、__現在の手番__ が __AI の場合__ に __着手__ を __選択する処理__ を __行っている__ からです。なお、この問題の __原因__ は、[以前の記事](https://qiita.com/ysgeso/items/e7b226cb567e08ca2767#%E3%83%AA%E3%82%BB%E3%83%83%E3%83%88%E3%83%9C%E3%82%BF%E3%83%B3%E3%81%AE%E3%82%AF%E3%83%AA%E3%83%83%E3%82%AF%E5%BE%8C%E3%81%AB-ai-%E3%81%8C%E7%9D%80%E6%89%8B%E3%82%92%E8%A1%8C%E3%82%8F%E3%81%AA%E3%81%84%E5%95%8F%E9%A1%8C%E3%81%AE%E5%8E%9F%E5%9B%A0)で説明した、__リセットボタン__ の __クリック後__ に __AI__ が __着手を行わない__ 問題と __同じ__ です。

# `play` メソッドの改良

これで、__`play`__ メソッドの __実行後__ に、__手番__ を __担当する AI__ を __Dropdown__ で __変更できる__ ようになりましたが、__`play`__ メソッドにはいくつか __改良__ できる __余地がある__ ので、それらの __改良を行う__ ことにします。改良案についていくつか考えてみて下さい。

## Dropdown に関する処理の改良

__これまで__ の __`play`__ メソッドでは、__Dropdown__ に関する __処理__ を、__2 つ__ の __Dropdown ごと__ に __記述__ してきましたが、この __2 つ__ の __Dropdown__ に __関する処理__ は __ほとんど同じ処理__ なので、__for 文__ を使って __下記__ のプログラムのように __まとめる__ ことが __できます__。

* __3 行目__：__2 つ__ の __Dropdown__ を __代入する変数 `drop_list`__ を __空の list__ で __初期化__ する 
* __7 ~ 16 行目__：__2 つ__ の __Dropdown__ を __作成する処理__ を、__for 文__ の __ブロック内__ に __記述__ する
* __7 行目__：__Dropdown__ の __左に表示__ する __文字列__ を、__`i` の値__ に __応じて計算__ する
* __8 ~ 16 行目__：__Dropdown__ を __作成__ し、__`dropdown_list`__ の __要素__ に __追加__ する
* __11 行目__：キーワード引数 __`description`__ に __7 行目__ で __計算__ した __文字列を指定__ する
* __14 行目__：キーワード引数 __`value`__ に __`select_values[i]`__ を __指定__ する
* __18 行目__：__HBox__ に __`dropdown_list`__ の __各要素__ を __記述__ するように __修正__ する
* __23、24、30、31 行目__：__`ai`__ の __各要素__ に __Dropdown__ の __選択項目の値__ を __代入__ する __処理__ を、__繰り返し処理__ を使って __行う__ ように __修正__ する
```python
 1  def play(self, ai, ai_dict=None, params=None, verbose=True, seed=None, gui=False, size=3):      
元と同じなので省略
 2          # 〇 と × の Dropdown を格納する list
 3          dropdown_list = []
 4          # ai に代入されている内容を ai_dict に追加する
 5          for i in range(2):
元と同じなので省略      
 6              # Dropdown の description を計算する
 7              description = "〇" if i == 0 else "×"
 8              dropdown_list.append(
 9                  widgets.Dropdown(
10                      options=ai_dict,
11                      description=description,
12                      layout=widgets.Layout(width="100px"),
13                      style={"description_width": "20px"},
14                      value=select_values[i],
15                  )
16              )
元と同じなので省略      
17          # 〇 と × の dropdown とボタンを横に配置した HBox を作成し、表示する
18          hbox = widgets.HBox([dropdown_list[0], dropdown_list[1], change_button, reset_button])
19          display(hbox)
20        
21          # 変更ボタンのイベントハンドラを定義する
22          def on_change_button_clicked(b):
23              for i in range(2):
24                  ai[i] = None if dropdown_list[i].value == "人間" else dropdown_list[i].value
25              self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui) 
26
27          # リセットボタンのイベントハンドラを定義する
28          def on_reset_button_clicked(b):
29              self.restart()
30              for i in range(2):
31                  ai[i] = None if dropdown_list[i].value == "人間" else dropdown_list[i].value
32              self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)    元と同じなので省略
33
34  Marubatsu.play = play
```

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

```python
def play(self, ai, ai_dict=None, params=None, verbose=True, seed=None, gui=False, size=3):      
    # ai_dict が None の場合は、空の list で置き換える
    if ai_dict is None:
        ai_dict = {}
    # params が None の場合のデフォルト値を設定する
    if params is None:
        params = [{}, {}]
   
    # seed が None でない場合は、seed を乱数の種として設定する
    if seed is not None:
        random.seed(seed)
  
    # gui が True の場合に、ゲーム盤を描画する画像を作成し、イベントハンドラに結びつける
    if gui:
        # %matplotlib widget のマジックコマンドを実行する
        get_ipython().run_line_magic('matplotlib', 'widget')
        
        # それぞれの手番の担当を表す Dropdown の項目の値を記録する list を初期化する
        select_values = []
        # 〇 と × の Dropdown を格納する list
        dropdown_list = []
        # ai に代入されている内容を ai_dict に追加する
        for i in range(2):
            # ラベルと項目の値を計算する
            if ai[i] is None:
                label = "人間"
                value = "人間"
            else:
                label = ai[i].__name__        
                value = ai[i]
            # value を select_values に常に登録する
            select_values.append(value)
            # value が ai_values に登録済かどうかを判定する
            if value not in ai_dict.values():
                # 項目を登録する
                ai_dict[label] = value
        
            # Dropdown の description を計算する
            description = "〇" if i == 0 else "×"
            dropdown_list.append(
                widgets.Dropdown(
                    options=ai_dict,
                    description=description,
                    layout=widgets.Layout(width="100px"),
                    style={"description_width": "20px"},
                    value=select_values[i],
                )
            )
        
        # 変更ボタンを作成する
        change_button = widgets.Button(
            description="変更",
            layout=widgets.Layout(width="100px"),
        )
        
        # リセットボタンを作成する
        reset_button = widgets.Button(
            description="リセット",
            layout=widgets.Layout(width="100px"),
        )
        
        # 〇 と × の dropdown とボタンを横に配置した HBox を作成し、表示する
        hbox = widgets.HBox([dropdown_list[0], dropdown_list[1], change_button, reset_button])
        display(hbox)
        
        # 変更ボタンのイベントハンドラを定義する
        def on_change_button_clicked(b):
            for i in range(2):
                ai[i] = None if dropdown_list[i].value == "人間" else dropdown_list[i].value
            self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)           

        # リセットボタンのイベントハンドラを定義する
        def on_reset_button_clicked(b):
            self.restart()
            for i in range(2):
                ai[i] = None if dropdown_list[i].value == "人間" else dropdown_list[i].value
            self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
            
        # イベントハンドラをボタンに結びつける
        change_button.on_click(on_change_button_clicked)
        reset_button.on_click(on_reset_button_clicked)
  
        fig, ax = plt.subplots(figsize=[size, size])
        fig.canvas.toolbar_visible = False
        fig.canvas.header_visible = False
        fig.canvas.footer_visible = False
        fig.canvas.resizable = False          
        
        # ローカル関数としてイベントハンドラを定義する
        def on_mouse_down(event):
            # Axes の上でマウスを押していた場合のみ処理を行う
            if event.inaxes and self.status == Marubatsu.PLAYING:
                x = math.floor(event.xdata)
                y = math.floor(event.ydata)
                self.move(x, y)                
                self.draw_board(ax)

                # 次の手番の処理を行うメソッドを呼び出す
                self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
                
        # fig の画像にマウスを押した際のイベントハンドラを結び付ける
        fig.canvas.mpl_connect("button_press_event", on_mouse_down)     
    else:
        ax = None

    self.restart()
    return self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)

Marubatsu.play = play
```
</details>

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

```diff_python
def play(self, ai, ai_dict=None, params=None, verbose=True, seed=None, gui=False, size=3):      
元と同じなので省略
        # 〇 と × の Dropdown を格納する list
+       dropdown_list = []
        # ai に代入されている内容を ai_dict に追加する
        for i in range(2):
元と同じなので省略      
            # Dropdown の description を計算する
+           description = "〇" if i == 0 else "×"
+           dropdown_list.append(
+               widgets.Dropdown(
+                   options=ai_dict,
+                   description=description,
+                   layout=widgets.Layout(width="100px"),
+                   style={"description_width": "20px"},
+                   value=select_values[i],
+               )
+           )

        # 〇 と × の Dropdown を作成する   
-       dropdown_circle = widgets.Dropdown(
-            options=ai_dict,
-            description="〇",
-            layout=widgets.Layout(width="100px"),
-            style={"description_width": "20px"},
-            value=select_values[0],
-        )
-        dropdown_cross = widgets.Dropdown(
-            options=ai_dict,
-            description="×",
-            layout=widgets.Layout(width="100px"),
-            style={"description_width": "20px"},
-            value=select_values[1],
-        )               
元と同じなので省略      
        # 〇 と × の dropdown とボタンを横に配置した HBox を作成し、表示する
-       hbox = widgets.HBox([dropdown_circle, dropdown_cross, change_button, reset_button])
+       hbox = widgets.HBox([dropdown_list[0], dropdown_list[1], change_button, reset_button])
        display(hbox)
        
        # 変更ボタンのイベントハンドラを定義する
        def on_change_button_clicked(b):
-           ai[0] = None if dropdown_circle.value == "人間" else dropdown_circle.value 
-           ai[1] = None if dropdown_cross.value == "人間" else dropdown_cross.value  
+           for i in range(2):
+               ai[i] = None if dropdown_list[i].value == "人間" else dropdown_list[i].value
            self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)

        # リセットボタンのイベントハンドラを定義する
        def on_reset_button_clicked(b):
            self.restart()
-           ai[0] = None if dropdown_circle.value == "人間" else dropdown_circle.value 
-           ai[1] = None if dropdown_cross.value == "人間" else dropdown_cross.value 
+           for i in range(2):
+               ai[i] = None if dropdown_list[i].value == "人間" else dropdown_list[i].value
            self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)        元と同じなので省略

Marubatsu.play = play
```
</details>

__実行結果__ は __先程と同じ__ なので __省略__ しますが、__上記__ の __修正後__ に、__下記__ のプログラムで __`gui_play`__ を __実行__ すると、__正しい処理__ が行われることが __確認__ できます。

```python
gui_play()
```

## 変更ボタンとリセットボタンの処理の統合

__変更ボタン__ と __リセットボタン__ が __行う処理__ の __違い__ は、__ゲームのリセット__ を __行うか__ どうか __だけ__ です。そのため、__リセットボタン__ の __イベントハンドラ__ を __下記__ のプログラムのように __修正__ して __統合__ することが __できます__。

* __5 行目__：__ゲームのリセット__ の __後の処理__ は __変更ボタン__ の __イベントハンドラ__ の __処理と同じ__ なので、__`on_change_button_clicked`__ を __呼び出す__ ように __修正__ する

```python
1  def play(self, ai, ai_dict=None, params=None, verbose=True, seed=None, gui=False, size=3):      
元と同じなので省略
2          # リセットボタンのイベントハンドラを定義する
3          def on_reset_button_clicked(b):
4              self.restart()
5              on_change_button_clicked(b)
元と同じなので省略

Marubatsu.play = play
```

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

```python
def play(self, ai, ai_dict=None, params=None, verbose=True, seed=None, gui=False, size=3):      
    # ai_dict が None の場合は、空の list で置き換える
    if ai_dict is None:
        ai_dict = {}
    # params が None の場合のデフォルト値を設定する
    if params is None:
        params = [{}, {}]
   
    # seed が None でない場合は、seed を乱数の種として設定する
    if seed is not None:
        random.seed(seed)
  
    # gui が True の場合に、ゲーム盤を描画する画像を作成し、イベントハンドラに結びつける
    if gui:
        # %matplotlib widget のマジックコマンドを実行する
        get_ipython().run_line_magic('matplotlib', 'widget')
        
        # それぞれの手番の担当を表す Dropdown の項目の値を記録する list を初期化する
        select_values = []
        # 〇 と × の Dropdown を格納する list
        dropdown_list = []
        # ai に代入されている内容を ai_dict に追加する
        for i in range(2):
            # ラベルと項目の値を計算する
            if ai[i] is None:
                label = "人間"
                value = "人間"
            else:
                label = ai[i].__name__        
                value = ai[i]
            # value を select_values に常に登録する
            select_values.append(value)
            # value が ai_values に登録済かどうかを判定する
            if value not in ai_dict.values():
                # 項目を登録する
                ai_dict[label] = value
        
            # Dropdown の description を計算する
            description = "〇" if i == 0 else "×"
            dropdown_list.append(
                widgets.Dropdown(
                    options=ai_dict,
                    description=description,
                    layout=widgets.Layout(width="100px"),
                    style={"description_width": "20px"},
                    value=select_values[i],
                )
            )
        
        # 変更ボタンを作成する
        change_button = widgets.Button(
            description="変更",
            layout=widgets.Layout(width="100px"),
        )
        
        # リセットボタンを作成する
        reset_button = widgets.Button(
            description="リセット",
            layout=widgets.Layout(width="100px"),
        )
        
        # 〇 と × の dropdown とボタンを横に配置した HBox を作成し、表示する
        hbox = widgets.HBox([dropdown_list[0], dropdown_list[1], change_button, reset_button])
        display(hbox)
        
        # 変更ボタンのイベントハンドラを定義する
        def on_change_button_clicked(b):
            for i in range(2):
                ai[i] = None if dropdown_list[i].value == "人間" else dropdown_list[i].value
            self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)

        # リセットボタンのイベントハンドラを定義する
        def on_reset_button_clicked(b):
            self.restart()
            on_change_button_clicked(b)
            
        # イベントハンドラをボタンに結びつける
        change_button.on_click(on_change_button_clicked)
        reset_button.on_click(on_reset_button_clicked)
  
        fig, ax = plt.subplots(figsize=[size, size])
        fig.canvas.toolbar_visible = False
        fig.canvas.header_visible = False
        fig.canvas.footer_visible = False
        fig.canvas.resizable = False          
        
        # ローカル関数としてイベントハンドラを定義する
        def on_mouse_down(event):
            # Axes の上でマウスを押していた場合のみ処理を行う
            if event.inaxes and self.status == Marubatsu.PLAYING:
                x = math.floor(event.xdata)
                y = math.floor(event.ydata)
                self.move(x, y)                
                self.draw_board(ax)

                # 次の手番の処理を行うメソッドを呼び出す
                self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
                
        # fig の画像にマウスを押した際のイベントハンドラを結び付ける
        fig.canvas.mpl_connect("button_press_event", on_mouse_down)     
    else:
        ax = None

    self.restart()
    return self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)

Marubatsu.play = play
```
</details>

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

```diff_python
def play(self, ai, ai_dict=None, params=None, verbose=True, seed=None, gui=False, size=3):      
元と同じなので省略
        # リセットボタンのイベントハンドラを定義する
        def on_reset_button_clicked(b):
            self.restart()
            on_change_button_clicked(b)
元と同じなので省略

Marubatsu.play = play
```
</details>

__実行結果__ は __先程と同じ__ なので __省略__ しますが、__上記__ の __修正後__ に、__下記__ のプログラムで __`gui_play`__ を __実行__ すると、__正しい処理__ が行われることが __確認__ できます。

```python
gui_play()
```

## 担当する AI の表示

__AI__ を __選択__ する __Dropbox__ は、__選択する項目__ を __変更__ しても、__変更ボタン__ または __リセットボタン__ を __押さない限り__、__ゲーム__ に __反映__ されることは __ありません__。そのため、__Dropbox__ に __選択__ されている __項目__ と、__手番__ を __担当する AI__ が __異なる場合__ が __あります__。

__その状況__ は、__混乱の原因__ となる __可能性がある__ ので、__ゲーム盤__ の __下__ に、例えば「__人間__ VS __ai1s__」のような、__実際__ に行われている __対戦カード__ を __表示する__ ようにします。どのようにプログラムを修正すれば良いかについて少し考えてみて下さい。

__対戦カード__ を __表示__ するためには、__`play`__ メソッドの __仮引数 `ai`__ の __情報__ が __必要__ になりますが、__ゲーム盤__ を __描画__ する __`draw_board`__ メソッドには __その情報__ が __渡されていない__ ので、__そのまま__ では __対戦カード__ を __表示__ することは __できません__。そこで、__下記__ のプログラムのように、__`draw_board`__ メソッドに、__手番__ を __担当する AI__ の __情報を代入__ する __仮引数`ai`__ を __追加__ し、__その情報__ を使って __ゲーム盤の下__ に __対戦カード__ を __表示__ するようにします。

* __4 行目__：それぞれの __手番の担当__ の __文字列__ を __代入する変数__ を __空の list__ で __初期化__ する
* __5、6 行目__：__繰り返し処理__ を使って、それぞれの __手番の担当__ を表す __文字列__ を __計算__ し、__`names`__ の __要素に追加__ する
* __7 行目__：__ゲーム盤__ の __下部の座標__ を __指定__ して、__対戦カード__ を __表示__ する。なお、__この座標__ は __試行錯誤__ して __調整したもの__ である

```python
1  def draw_board(self, ax, ai):
元と同じなので省略
2      # 上部のメッセージを描画する
3      # 対戦カードの文字列を計算する
4      names = []
5      for i in range(2):
6          names.append("人間" if ai[i] is None else ai[i].__name__)
7      ax.text(0, 3.5, f"{names[0]}　VS　{names[1]}", fontsize=20)   
元と同じなので省略
8           
9  Marubatsu.draw_board = draw_board 

```

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

```python
def draw_board(self, ax, ai):
    # Axes の内容をクリアして、これまでの描画内容を削除する
    ax.clear()
    
    # y 軸を反転させる
    ax.invert_yaxis()
    
    # 枠と目盛りを表示しないようにする
    ax.axis("off")   
    
    # 上部のメッセージを描画する
    # 対戦カードの文字列を計算する
    names = []
    for i in range(2):
        names.append("人間" if ai[i] is None else ai[i].__name__)
    ax.text(0, 3.5, f"{names[0]}　VS　{names[1]}", fontsize=20)   
    
    # ゲームの決着がついていない場合は、手番を表示する
    if self.status == Marubatsu.PLAYING:
        text = "Turn " + self.turn
    # 決着がついていれば勝者を表示する
    else:
        text = "winner " + self.status
    ax.text(0, -0.2, text, fontsize=20)
    
    # ゲーム盤の枠を描画する
    for i in range(1, self.BOARD_SIZE):
        ax.plot([0, self.BOARD_SIZE], [i, i], c="k") # 横方向の枠線
        ax.plot([i, i], [0, self.BOARD_SIZE], c="k") # 縦方向の枠線   

    # ゲーム盤のマークを描画する
    for y in range(self.BOARD_SIZE):
        for x in range(self.BOARD_SIZE):
            color = "red" if (x, y) == self.last_move else "black"
            self.draw_mark(ax, x, y, self.board[x][y], color) 
            
Marubatsu.draw_board = draw_board 
```
</details>

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

```diff_python
def draw_board(self, ax, ai):
元と同じなので省略
    # 上部のメッセージを描画する
    # 対戦カードの文字列を計算する
+   names = []
+   for i in range(2):
+       names.append("人間" if ai[i] is None else ai[i].__name__)
+   ax.text(0, 3.5, f"{names[0]}　VS　{names[1]}", fontsize=20)   
元と同じなので省略
            
Marubatsu.draw_board = draw_board 
```
</details>

次に、__`draw_board`__ を __呼び出し__ ている、__`play`__ メソッドと __`play_loop`__ メソッドを __修正__ します。__下記__ は、__修正__ した __`play`__ メソッドで、__9 行目__ で、__`draw_board`__ に __実引数 `ai`__ を __追加__ するという __修正__ を行っています。

```python
 1  def play(self, ai, ai_dict=None, params=None, verbose=True, seed=None, gui=False, size=3):      
元と同じなので省略
 2          # ローカル関数としてイベントハンドラを定義する
 3          def on_mouse_down(event):
 4              # Axes の上でマウスを押していた場合のみ処理を行う
 5              if event.inaxes and self.status == Marubatsu.PLAYING:
 6                  x = math.floor(event.xdata)
 7                  y = math.floor(event.ydata)
 8                  self.move(x, y)                
 9                  self.draw_board(ax, ai)
10
11                  # 次の手番の処理を行うメソッドを呼び出す
12                  self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
元と同じなので省略
13
14  Marubatsu.play = play
```

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

```python
def play(self, ai, ai_dict=None, params=None, verbose=True, seed=None, gui=False, size=3):      
    # ai_dict が None の場合は、空の list で置き換える
    if ai_dict is None:
        ai_dict = {}
    # params が None の場合のデフォルト値を設定する
    if params is None:
        params = [{}, {}]
   
    # seed が None でない場合は、seed を乱数の種として設定する
    if seed is not None:
        random.seed(seed)
  
    # gui が True の場合に、ゲーム盤を描画する画像を作成し、イベントハンドラに結びつける
    if gui:
        # %matplotlib widget のマジックコマンドを実行する
        get_ipython().run_line_magic('matplotlib', 'widget')
        
        # それぞれの手番の担当を表す Dropdown の項目の値を記録する list を初期化する
        select_values = []
        # 〇 と × の Dropdown を格納する list
        dropdown_list = []
        # ai に代入されている内容を ai_dict に追加する
        for i in range(2):
            # ラベルと項目の値を計算する
            if ai[i] is None:
                label = "人間"
                value = "人間"
            else:
                label = ai[i].__name__        
                value = ai[i]
            # value を select_values に常に登録する
            select_values.append(value)
            # value が ai_values に登録済かどうかを判定する
            if value not in ai_dict.values():
                # 項目を登録する
                ai_dict[label] = value
        
            # Dropdown の description を計算する
            description = "〇" if i == 0 else "×"
            dropdown_list.append(
                widgets.Dropdown(
                    options=ai_dict,
                    description=description,
                    layout=widgets.Layout(width="100px"),
                    style={"description_width": "20px"},
                    value=select_values[i],
                )
            )
        
        # 変更ボタンを作成する
        change_button = widgets.Button(
            description="変更",
            layout=widgets.Layout(width="100px"),
        )
        
        # リセットボタンを作成する
        reset_button = widgets.Button(
            description="リセット",
            layout=widgets.Layout(width="100px"),
        )
        
        # 〇 と × の dropdown とボタンを横に配置した HBox を作成し、表示する
        hbox = widgets.HBox([dropdown_list[0], dropdown_list[1], change_button, reset_button])
        display(hbox)
        
        # 変更ボタンのイベントハンドラを定義する
        def on_change_button_clicked(b):
            for i in range(2):
                ai[i] = None if dropdown_list[i].value == "人間" else dropdown_list[i].value
            self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)

        # リセットボタンのイベントハンドラを定義する
        def on_reset_button_clicked(b):
            self.restart()
            on_change_button_clicked(b)
            
        # イベントハンドラをボタンに結びつける
        change_button.on_click(on_change_button_clicked)
        reset_button.on_click(on_reset_button_clicked)
  
        fig, ax = plt.subplots(figsize=[size, size])
        fig.canvas.toolbar_visible = False
        fig.canvas.header_visible = False
        fig.canvas.footer_visible = False
        fig.canvas.resizable = False          
        
        # ローカル関数としてイベントハンドラを定義する
        def on_mouse_down(event):
            # Axes の上でマウスを押していた場合のみ処理を行う
            if event.inaxes and self.status == Marubatsu.PLAYING:
                x = math.floor(event.xdata)
                y = math.floor(event.ydata)
                self.move(x, y)                
                self.draw_board(ax, ai)

                # 次の手番の処理を行うメソッドを呼び出す
                self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
                
        # fig の画像にマウスを押した際のイベントハンドラを結び付ける
        fig.canvas.mpl_connect("button_press_event", on_mouse_down)     
    else:
        ax = None

    self.restart()
    return self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)

Marubatsu.play = play
```
</details>

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

```diff_python
def play(self, ai, ai_dict=None, params=None, verbose=True, seed=None, gui=False, size=3):      
元と同じなので省略
        # ローカル関数としてイベントハンドラを定義する
        def on_mouse_down(event):
            # Axes の上でマウスを押していた場合のみ処理を行う
            if event.inaxes and self.status == Marubatsu.PLAYING:
                x = math.floor(event.xdata)
                y = math.floor(event.ydata)
                self.move(x, y)                
-               self.draw_board(ax)
+               self.draw_board(ax, ai)

                # 次の手番の処理を行うメソッドを呼び出す
                self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
元と同じなので省略

Marubatsu.play = play
```
</details>

下記は、__`play_loop`__ の __修正__ で、__修正箇所__ は __5、7 行目__ です。

```python
1  def play_loop(self, ai, ax, params, verbose, gui):   
元と同じなので省略
2              if gui:
3                  # AI どうしの対戦の場合は画面を描画しない
4                  if ai[0] is None or ai[1] is None:
5                      self.draw_board(ax, ai)
元と同じなので省略
6          if gui:
7              self.draw_board(ax, ai)
元と同じなので省略
8
9  Marubatsu.play_loop = play_loop
```

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

```python
def play_loop(self, ai, ax, params, verbose, gui):   
    # ゲームの決着がついていない間繰り返す
    while self.status == Marubatsu.PLAYING:
        # 現在の手番を表す ai のインデックスを計算する
        index = 0 if self.turn == Marubatsu.CIRCLE else 1
        # ゲーム盤の表示
        if verbose:
            if gui:
                # AI どうしの対戦の場合は画面を描画しない
                if ai[0] is None or ai[1] is None:
                    self.draw_board(ax, ai)
                # 手番を人間が担当する場合は、play メソッドを終了する
                if ai[index] is None:
                    return
            else:
                print(self)
                
        # ai が着手を行うかどうかを判定する
        if ai[index] is not None:
            x, y = ai[index](self, **params[index])
        else:
            # キーボードからの座標の入力
            coord = input("x,y の形式で座標を入力して下さい。exit を入力すると終了します")
            # "exit" が入力されていればメッセージを表示して関数を終了する
            if coord == "exit":
                print("ゲームを終了します")
                return       
            # x 座標と y 座標を要素として持つ list を計算する
            xylist = coord.split(",")
            # xylist の要素の数が 2 ではない場合
            if len(xylist) != 2:
                # エラーメッセージを表示する
                print("x, y の形式ではありません")
                # 残りの while 文のブロックを実行せずに、次の繰り返し処理を行う
                continue
            x, y = xylist
        # (x, y) に着手を行う
        try:
            self.move(int(x), int(y))
        except:
            print("整数の座標を入力して下さい")

    # 決着がついたので、ゲーム盤を表示する
    if verbose:
        if gui:
            self.draw_board(ax, ai)
        else:
            print(self)
            
    return self.status

Marubatsu.play_loop = play_loop
```
</details>

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

```diff_python
def play_loop(self, ai, ax, params, verbose, gui):   
元と同じなので省略
            if gui:
                # AI どうしの対戦の場合は画面を描画しない
                if ai[0] is None or ai[1] is None:
-                   self.draw_board(ax)
+                   self.draw_board(ax, ai)
元と同じなので省略
        if gui:
-           self.draw_board(ax)
+           self.draw_board(ax, ai)
元と同じなので省略

Marubatsu.play_loop = play_loop
```
</details>

__上記__ の __修正後__ に、__下記__ のプログラムで __`gui_play`__ を __実行__ すると、__実行結果__ のように __ゲーム盤の下__ に __対戦カード__ が __表示される__ ようになります。また、__Dropdown__ を __変更__ して __変更ボタン__ や __リセットボタン__ を __押す__ と、__対戦カード__ が __更新される__ ことを __確認__ して下さい。

```python
gui_play()
```

実行結果（下図は、画像なので操作することはできません）

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/1f04f912-fc64-70bb-0de0-479504fbfb01.png" width="300">

## ゲームが決着した際の表示の工夫

__現状__ では、__ゲーム__ が __決着しているか__ どうかが __わかりづらい__ ので、__ゲーム__ が __決着した場合__ は、__ゲームの画像__ の __背景色__ を __変更__ することにします。__Figure__ の __背景色__ は、__下記__ のプログラムのように、__Figure__ の __`set_facecolor`__ メソッドで __変更__ できます。また、__Axes__ が __登録__ されている __Figure__ は、__Axes__ の __`figure`__ 属性に __代入__ されています。

__本記事__ では、__ゲーム__ の __決着がついた場合__ に __背景色__ を __薄い黄色__ を表す __"lightyellow"__ にしますが、__他の色が良い__ と思った人は __自由に変更__ して下さい。

* __6 行目__：__ゲームの決着__ が __ついていない__ 場合は __白__ を、決着が __ついている__ 場合は __薄い黄色__ を表す __文字列を計算__ する
* __7 行目__：__`ax`__ が __登録__ されている __Figure__ の __背景色__ を __6 行目__ で __計算した色__ に __変更__ する

```python
1  def draw_board(self, ax, ai):
元と同じなので省略
2      # 枠と目盛りを表示しないようにする
3      ax.axis("off")   
4    
5      # ゲームの決着がついていた場合は背景色を変える
6      facecolor = "white" if self.status == Marubatsu.PLAYING else "lightyellow"
7      ax.figure.set_facecolor(facecolor)
元と同じなので省略
8            
9  Marubatsu.draw_board = draw_board 
```

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

```python
def draw_board(self, ax, ai):
    # Axes の内容をクリアして、これまでの描画内容を削除する
    ax.clear()
    
    # y 軸を反転させる
    ax.invert_yaxis()
    
    # 枠と目盛りを表示しないようにする
    ax.axis("off")   
    
    # ゲームの決着がついていた場合は背景色を変える
    facecolor = "white" if self.status == Marubatsu.PLAYING else "lightyellow"
    ax.figure.set_facecolor(facecolor)
        
    # 上部のメッセージを描画する
    # 対戦カードの文字列を計算する
    names = []
    for i in range(2):
        names.append("人間" if ai[i] is None else ai[i].__name__)
    ax.text(0, 3.5, f"{names[0]}　VS　{names[1]}", fontsize=20)   
    
    # ゲームの決着がついていない場合は、手番を表示する
    if self.status == Marubatsu.PLAYING:
        text = "Turn " + self.turn
    # 決着がついていれば勝者を表示する
    else:
        text = "winner " + self.status
    ax.text(0, -0.2, text, fontsize=20)
    
    # ゲーム盤の枠を描画する
    for i in range(1, self.BOARD_SIZE):
        ax.plot([0, self.BOARD_SIZE], [i, i], c="k") # 横方向の枠線
        ax.plot([i, i], [0, self.BOARD_SIZE], c="k") # 縦方向の枠線   

    # ゲーム盤のマークを描画する
    for y in range(self.BOARD_SIZE):
        for x in range(self.BOARD_SIZE):
            color = "red" if (x, y) == self.last_move else "black"
            self.draw_mark(ax, x, y, self.board[x][y], color) 
            
Marubatsu.draw_board = draw_board 
```
</details>

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

```diff_python
def draw_board(self, ax, ai):
元と同じなので省略
    # 枠と目盛りを表示しないようにする
    ax.axis("off")   
    
    # ゲームの決着がついていた場合は背景色を変える
+   facecolor = "white" if self.status == Marubatsu.PLAYING else "lightyellow"
+   ax.figure.set_facecolor(facecolor)
元と同じなので省略
            
Marubatsu.draw_board = draw_board 
```
</details>

__上記__ の __修正後__ に、__下記__ のプログラムで __`gui_play`__ を __実行__ し、__ゲーム__ の __決着をつける__ と、__実行結果__ のように __ゲームの画像__ の __背景色__ が __薄い黄色__ になります。

```python
gui_play()
```

実行結果（下図は、画像なので操作することはできません）

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/e345b430-f5aa-94b4-dec8-311834d949ba.png" width="300">

## 引き分けの表示の変更

__現状__ では、__引き分け__ になった場合に __"winner draw"__ という __メッセージ__ が __表示__ されますが、__勝者__（winner）が __draw__ という __メッセージ__ は __変な気がする__ ので、__下記__ のプログラムのように、__"Draw game"__ と __表示__ するように __修正__ します。あと、__細かい修正__ ですが、__違和感があった__ ので、__勝者__ の __メッセージ__ の __"winner"__ の __頭文字__ を __大文字に修正__ しました。

* __6、7 行目__：__引き分け__ の場合の __メッセージ__ を __"Draw game"__ にする
* __10 行目__：__勝者__ の __メッセージ__ の __"winner"__ の __頭文字__ を __大文字に修正__ する

```python
 1  def draw_board(self, ax, ai):
元と同じなので省略
 2      # ゲームの決着がついていない場合は、手番を表示する
 3      if self.status == Marubatsu.PLAYING:
 4          text = "Turn " + self.turn
 5      # 引き分けの場合
 6      elif self.status == Marubatsu.DRAW:
 7          text = "Draw game"
 8      # 決着がついていれば勝者を表示する
 9      else:
10          text = "Winner " + self.status
11      ax.text(0, -0.2, text, fontsize=20)
元と同じなので省略    
12            
13  Marubatsu.draw_board = draw_board 
```

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

```python
def draw_board(self, ax, ai):
    # Axes の内容をクリアして、これまでの描画内容を削除する
    ax.clear()
    
    # y 軸を反転させる
    ax.invert_yaxis()
    
    # 枠と目盛りを表示しないようにする
    ax.axis("off")   
    
    # ゲームの決着がついていた場合は背景色を
    facecolor = "white" if self.status == Marubatsu.PLAYING else "lightyellow"
    ax.figure.set_facecolor(facecolor)
        
    # 上部のメッセージを描画する
    # 対戦カードの文字列を計算する
    names = []
    for i in range(2):
        names.append("人間" if ai[i] is None else ai[i].__name__)
    ax.text(0, 3.5, f"{names[0]}　VS　{names[1]}", fontsize=20)   
    
    # ゲームの決着がついていない場合は、手番を表示する
    if self.status == Marubatsu.PLAYING:
        text = "Turn " + self.turn
    # 引き分けの場合
    elif self.status == Marubatsu.DRAW:
        text = "Draw game"
    # 決着がついていれば勝者を表示する
    else:
        text = "Winner " + self.status
    ax.text(0, -0.2, text, fontsize=20)
    
    # ゲーム盤の枠を描画する
    for i in range(1, self.BOARD_SIZE):
        ax.plot([0, self.BOARD_SIZE], [i, i], c="k") # 横方向の枠線
        ax.plot([i, i], [0, self.BOARD_SIZE], c="k") # 縦方向の枠線   

    # ゲーム盤のマークを描画する
    for y in range(self.BOARD_SIZE):
        for x in range(self.BOARD_SIZE):
            color = "red" if (x, y) == self.last_move else "black"
            self.draw_mark(ax, x, y, self.board[x][y], color) 
            
Marubatsu.draw_board = draw_board 
```
</details>

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

```diff_python
def draw_board(self, ax, ai):
元と同じなので省略
    # ゲームの決着がついていない場合は、手番を表示する
    if self.status == Marubatsu.PLAYING:
        text = "Turn " + self.turn
    # 引き分けの場合
+   elif self.status == Marubatsu.DRAW:
+       text = "Draw game"
    # 決着がついていれば勝者を表示する
    else:
-       text = "winner " + self.status
+       text = "Winner " + self.status
    ax.text(0, -0.2, text, fontsize=20)
元と同じなので省略    
            
Marubatsu.draw_board = draw_board 
```
</details>

__上記__ の __修正後__ に、__下記__ のプログラムで __`gui_play`__ を __実行__ し、__引き分け__ にすると、__実行結果__ のように __"Draw game"__ が __表示__ されるようになります。実行結果は省略しますが、__どちらかが勝利__ した __場合__ は、__先頭__ に __"Winner" が表示__ されることも __確認__ できます。

```python
gui_play()
```

実行結果（下図は、画像なので操作することはできません）

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/9e2a1670-c73c-5f67-0fbf-b40c9bd677e1.png" width="300">

## CUI の場合の処理の確認

最後に、__下記__ のプログラムを __実行__ し、__CUI__ で __〇×ゲーム__ を __遊んだ場合__ に __正しい処理__ が行われることを __確認__ します。なお、実行結果は省略します。

```python
mb.play(ai=[ai1s, ai1s])
```

# 今回の記事のまとめ

今回の記事は、以下の内容を行いました。

* __`play`__ メソッドの __バグを修正__ した
* __`%matplotlib widget`__ の __マジックコマンド__ を __プログラムで実行__ できるようにした
* __Dropdown__ で __選択した AI__ で __対戦が行われる__ ようにした
* __`play`__ メソッドに関する __いくつかの改良__ を行った

本記事で紹介した以外の __`play`__ メソッドの __改良__ を __思いついた人__ は、__余裕があれば__ その __改良__ を __行ってみて下さい__。

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

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

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

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

なお、__`play`__、__`play_loop`__、__`draw_board`__ の __docstring__ の __説明__ が __微妙に間違っていた__ ので __修正__ しました。ただし、__過去の記事__ を __修正__ するのは __大変すぎる__ ので、__過去の記事__ の __docstring__ は __そのまま__ にしてあります。

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

今回の記事では、util.py は変更していません。

# 次回の記事

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