Pythonで〇×ゲームのAIを一から作成する　その70　マウスによる着手の処理

# 目次と前回の記事

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

https://qiita.com/ysgeso/items/975078999ad86bd38876

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

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

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

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

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

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

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

# イベントハンドラ内で、着手を行うプログラム

前回の記事で、__[イベントハンドラ](https://qiita.com/ysgeso/items/1920c68f5a1e8a998796#%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E3%83%8F%E3%83%B3%E3%83%89%E3%83%A9%E3%81%AE%E5%AE%9A%E7%BE%A9)内__ で、__マウス__ が __押された場所__ の __ゲーム盤__ の __マスの座標__ が __計算できる__ ようになったので、今回の記事では __イベントハンドラの中__ で、__そのマス__ に __着手を行うプログラム__ を __記述__ します。

## ローカル関数としてのイベントハンドラの定義

__指定したマス__ に __着手__ する __処理__ を行う __`move`__ メソッドは __`Marubatsu`__ クラスのメソッドなので、__`move`__ メソッドを __利用__ するためには、__着手__ を行う __ゲーム盤__ を表す __`Marubatsu`__ クラスの __インスタンス__ が __必要__ になります。__一般的な関数__ であれば、__`Marubatsu`__ クラスの __インスタンス__ を __代入__ する __仮引数を追加__ すればよいのですが、残念ながら、__イベントハンドラ__ は、__[イベントループ](https://qiita.com/ysgeso/items/1920c68f5a1e8a998796#%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E9%A7%86%E5%8B%95%E5%9E%8B%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E3%81%AE%E4%BB%95%E7%B5%84%E3%81%BF)__ から、__あらかじめ決められた実引数__ を __記述__ して __呼び出される__ という __仕組み__ になっているので、自分で __新しい仮引数__ を __追加__ することは __できません__[^1]。

__`Marubatsu`__ クラスの __インスタンス__ を、__イベントハンドラの中__ で __利用できる__ ようにする __方法__ として、__イベントハンドラ__ を、__`Marubatsu`__ クラスの __メソッドの中__ で、__ローカル関数__ として __定義__ するという方法があります。[以前の記事](https://qiita.com/ysgeso/items/e78980b366e123ca57f1#%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E9%96%A2%E6%95%B0%E3%81%AE%E3%83%96%E3%83%AD%E3%83%83%E3%82%AF%E3%81%AE%E4%B8%AD%E3%81%AE%E5%90%8D%E5%89%8D%E8%A7%A3%E6%B1%BA)で説明したように、__ローカル関数__ の __ブロックの中__ では、その __ローカル関数__ が __定義された関数__（または __メソッド__）の __ブロックの中__ の __名前__ を __そのまま利用__ することが __できます__。

__`draw_board`__ メソッドの __ブロックの中__ に、__ローカル関数__ として __`on_mouse_down` を定義__ することで、__`draw_board`__ メソッドの __仮引数 `self`__ を、__`on_mouse_down`__ の __ブロックの中__ で __利用できます__。__`draw_board`__ メソッドの __仮引数 `self`__ には、__〇×ゲーム__ を表す __`Marubatsu`__ クラスの __インスタンス__ が __代入__ されているので、__下記__ のプログラムのように、__`self`__ という __名前__ を __そのまま利用__ して __`move`__ メソッドを呼び出して __着手を行う__ ことが __できます__。

* __13 ~ 19 行目__：__`on_mouse_down`__ を、__`draw_board`__ の __ローカル関数__ として __定義__ する
* __16、17 行目__：__マウス__ を __押した場所__ の __ゲーム盤の座標__ を __計算__ し、__`x`__ と __`y`__ に __代入__ する
* __18 行目__：__`self`__ には、__`draw_board`__ メソッドを __呼び出し__ た __`Marubatsu`__ クラスの __インスタンス__ が __代入__ されているので、__`move`__ メソッドを __呼び出し__ て __(x, y)__ の __マス__ に __着手を行う__
* __19 行目__：__ゲーム盤の描画__ を __更新__ するために、__`draw_board`__ メソッドを __呼び出す__
* __22 行目__：__画像の上__ で __マウスを押した際__ に __`on_mouse_down`__ の __イベントハンドラ__ を __呼び出すようにする処理__ を、__`on_mouse_down`__ の __定義の後__ に __移動__ する

なお、__`on_mouse_down`__ の __定義__ は、__22 行目__ で __`on_mouse_down`__ を使って __画像__ と __イベントハンドラ__ を __結び付ける処理より__ も __前に記述__ しなければならない点に __注意__ して下さい。

```python
 1  %matplotlib widget
 2  from marubatsu import Marubatsu
 3  import matplotlib.pyplot as plt
 4
 5  def draw_board(self, size=3):
 6      fig, ax = plt.subplots(figsize=[size, size])
 7      fig.canvas.toolbar_visible = False
 8      fig.canvas.header_visible = False
 9      fig.canvas.footer_visible = False
10      fig.canvas.resizable = False       
11
12      # ローカル関数としてイベントハンドラを定義する
13      def on_mouse_down(event):
14          # Axes の上でマウスを押していた場合のみ処理を行う
15          if event.inaxes:
16              x = math.floor(event.xdata)
17              y = math.floor(event.ydata)
18              self.move(x, y)
19              self.draw_board()
20
21      # fig の画像にマウスを押した際のイベントハンドラを結び付ける
22      fig.canvas.mpl_connect("button_press_event", on_mouse_down)  
以下同じなので省略
23
24  Marubatsu.draw_board = draw_board
```

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

```python
%matplotlib widget
from marubatsu import Marubatsu
import matplotlib.pyplot as plt
import math

def draw_board(self, size=3):   
    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:
            x = math.floor(event.xdata)
            y = math.floor(event.ydata)
            self.move(x, y)
            self.draw_board()
            
    # fig の画像にマウスを押した際のイベントハンドラを結び付ける
    fig.canvas.mpl_connect("button_press_event", on_mouse_down)  
            
    # y 軸を反転させる
    ax.invert_yaxis()

    # 枠と目盛りを表示しないようにする
    ax.axis("off")

    # 上部のメッセージを描画する
    # ゲームの決着がついていない場合は、手番を表示する
    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)

    # ゲーム盤を描画する
    plt.show()          

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

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

```diff_python
%matplotlib widget
from marubatsu import Marubatsu
import matplotlib.pyplot as plt

def draw_board(self, size=3):
    fig, ax = plt.subplots(figsize=[size, size])
-   fig.canvas.mpl_connect("button_press_event", on_mouse_down)  
    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:
+           x = math.floor(event.xdata)
+           y = math.floor(event.ydata)
+           self.move(x, y)
+           self.draw_board()
            
    # fig の画像にマウスを押した際のイベントハンドラを結び付ける
+   fig.canvas.mpl_connect("button_press_event", on_mouse_down)  
以下同じなので省略

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

__上記__ の __修正後__ に、__下記__ のプログラムを実行し、__マスの上__ で __マウスを押して__ みて下さい。__下記__ は、__(0, 0)__ の __マスの上__ で __マウスを押した__ 場合の __図__ です。__実行結果__ からわかるように、__(0, 0)__ に __〇 が配置__ された、__新しい画像__ が __描画される__ ようになります。

```python
mb = Marubatsu()
mb.play(ai=[None, None], gui=True)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/4e8431f2-5323-499b-bb54-62fe8d29dba4.png" width="150">

なお、これまでに __グローバル関数__ として定義した __`on_mouse_down`__ は __必要が無くなった__ ので、__marubatsu.py から削除__ します。

## 画像が毎回描画される問題の原因

ゲーム盤の __マスの上__ で __マウスで押す__ ことで __着手__ を __行える__ ようになりましたが、__着手__ を __行うたび__ に __新しい画像__ が __描画__ されるという __問題__ が __あります__。このようなことが起きる __原因__ は、__`draw_board`__ メソッドの __最後__ で __`plt.show()`__ を __実行している__ からです。

[以前の記事](https://qiita.com/ysgeso/items/1920c68f5a1e8a998796#pltshow-%E3%81%AB%E9%96%A2%E3%81%99%E3%82%8B%E6%B3%A8%E6%84%8F%E7%82%B9)で説明したように、__`%matplotlib widget`__ を __実行__ した場合は、__`plt.show()`__ を __実行__ すると、__画像を更新__ するの __ではなく__、__新しい画像__ が __描画__ されてしまうため __一般的__ には __`plt.show()`__ は __実行しません__。そこで、__下記__ のプログラムのように、__`draw_board`__ の __最後__ の __`plt.show()`__ を __削除__ することにします。

```python
def draw_board(self, size=3):
元と同じなので省略
    # 最後に行っていた、この下のゲーム盤を描画する `plot.show()` を削除する

Marubatsu.draw_board = draw_board
```

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

```python
def draw_board(self, size=3):   
    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:
            x = math.floor(event.xdata)
            y = math.floor(event.ydata)
            self.move(x, y)
            self.draw_board()
            
    # fig の画像にマウスを押した際のイベントハンドラを結び付ける
    fig.canvas.mpl_connect("button_press_event", on_mouse_down)  
            
    # y 軸を反転させる
    ax.invert_yaxis()

    # 枠と目盛りを表示しないようにする
    ax.axis("off")

    # 上部のメッセージを描画する
    # ゲームの決着がついていない場合は、手番を表示する
    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, size=3):
元と同じなので省略
    # ゲーム盤を描画する
-   plt.show()          

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

__実行結果__ は __先ほどと同じ__ なので __省略__ しますが、__上記__ の __修正後__ に、__下記__ のプログラムを実行し、__マスの上__ で __マウスを押した__ 際に、相変わらず __新しい画像__ が __描画される__ ことを __確認__ して下さい。このようなことが起きる原因について少し考えてみて下さい。

```python
mb.play(ai=[None, None], gui=True)
```

[以前の記事](https://qiita.com/ysgeso/items/1920c68f5a1e8a998796#%E7%94%BB%E5%83%8F%E3%81%AE%E6%8F%8F%E7%94%BB)で説明したように、__`%matplotlib widget`__ を __実行__ した場合は、__Figure__ を __作成するだけ__ で、__画像が描画される__ ようになります。__上記__ のようなことが起きる __原因__ は、__下記__ のプログラムのように、__`draw_board`__ メソッドの __最初の行__ で、__`subplots`__ メソッドを実行して、__新しい Figure__ を __作成__ しているからです。

```python
def draw_board(self, size=3):   
    fig, ax = plt.subplots(figsize=[size, size])
以下略
```

__画像__ を __更新する__ ためには、__`draw_board`__ で __毎回新しい Figure__ を __作成__ するの __ではなく__、__Figure__ は __一度だけ作成__ し、__以後__ は __その Figure__ に対して __画像__ を __描画する処理__ を __行う必要__ が __あります__。どのように修正すればよいかについて少し考えてみて下さい。

## `play` メソッドでの Figure の作成

まず、__一度だけ__ 行う __Figure の作成__ を、__どこで行うか__ を __考える必要__ があります。__`play`__ メソッドは、__〇×ゲームを遊ぶ際__ に __一度だけ実行__ するので、__`play` メソッド__ の __最初__ で __その処理を行う__ という __方法__ が __良い__ でしょう。

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

そこで、__下記__ のプログラムのように、__`play`__ メソッドの __最初__ で、__`gui=True`__ の場合に __Figure__ を __作成する__ ように __修正__ することにします。その際に、__Figure の設定__ 、__イベントハンドラの定義__ と __結びつけ__ の __処理__ など、__Figure__ に __関する処理__ も __`play`__ メソッドで __行う必要がある__ 点に __注意__ して下さい。

* __10 ~ 27 行目__：__`gui`__ が __`True` の場合__ に __`draw_board`__ に __記述__ されていた __Figure の作成__、__設定__、__イベントハンドラの定義__ と __結び付け__ の処理を __そのまま記述__ する

なお、__`draw_board`__ は、__`play`__ メソッド から __`self.draw_board`__ によって __呼び出される__ ので、__`draw_board`__ の __`self`__ と、__`play`__ メソッドの __`self`__ には __同じ `Marubatsu`__ クラスの __インスタンス__ が __代入__ されています。そのため、__`draw_board`__ の __`on_mouse_down`__ の __定義__ を、__`play`__ メソッドに __移動しても__、__`on_mouse_down`__ の中では __同じ処理__ が __行われます__。

```python
 1  def play(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False):
 2      # seed が None でない場合は、seed を乱数の種として設定する
 3      if seed is not None:
 4          random.seed(seed)
 5   
 6      # 〇×ゲームを再起動する
 7      self.restart()
 8
 9      # gui が True の場合に、ゲーム盤を描画する画像を作成し、イベントハンドラに結びつける
10      if gui:
11          fig, ax = plt.subplots(figsize=[size, size])
12          fig.canvas.toolbar_visible = False
13          fig.canvas.header_visible = False
14          fig.canvas.footer_visible = False
15          fig.canvas.resizable = False  
16        
17          # ローカル関数としてイベントハンドラを定義する
18          def on_mouse_down(event):
19              # Axes の上でマウスを押していた場合のみ処理を行う
20              if event.inaxes:
21                  x = math.floor(event.xdata)
22                  y = math.floor(event.ydata)
23                  self.move(x, y)
24                  self.draw_board()
25        
26          # fig の画像にマウスを押した際のイベントハンドラを結び付ける
27          fig.canvas.mpl_connect("button_press_event", on_mouse_down)     
元と同じなので省略
28            
29  Marubatsu.play = play
```

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

```python
def play(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False):
    # seed が None でない場合は、seed を乱数の種として設定する
    if seed is not None:
        random.seed(seed)
    
    # 〇×ゲームを再起動する
    self.restart()

    # 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:
                x = math.floor(event.xdata)
                y = math.floor(event.ydata)
                self.move(x, y)
                self.draw_board()
        
        # fig の画像にマウスを押した際のイベントハンドラを結び付ける
        fig.canvas.mpl_connect("button_press_event", on_mouse_down)     

    # ゲームの決着がついていない間繰り返す
    while self.status == Marubatsu.PLAYING:
        # ゲーム盤の表示
        if verbose:
            if gui:
                self.draw_board()
                return
            else:
                print(self)
        # 現在の手番を表す ai のインデックスを計算する
        index = 0 if self.turn == Marubatsu.CIRCLE else 1
        # 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()
        else:
            print(self)
            
    return self.status
    
Marubatsu.play = play
```
</details>

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

```diff_python
def play(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False):
    # seed が None でない場合は、seed を乱数の種として設定する
    if seed is not None:
        random.seed(seed)
    
    # 〇×ゲームを再起動する
    self.restart()

    # 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:
+               x = math.floor(event.xdata)
+               y = math.floor(event.ydata)
+               self.move(x, y)
+               self.draw_board()
        
        # fig の画像にマウスを押した際のイベントハンドラを結び付ける
+       fig.canvas.mpl_connect("button_press_event", on_mouse_down)     
元と同じなので省略

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

### `draw_board` の修正

次に、__`draw_board` から__、__`play`__ メソッドに __移動した処理__ を __下記__ のプログラムのように __削除__ する __必要__ が __あります__。

```python
def draw_board(self, size=3): 
    # ここにあった、Figure の作成と設定、イベントハンドラの定義と結び付けを行う処理を削除する

元と同じなので省略

Marubatsu.draw_board = draw_board
```

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

```python
def draw_board(self, size=3):             
    # y 軸を反転させる
    ax.invert_yaxis()

    # 枠と目盛りを表示しないようにする
    ax.axis("off")

    # 上部のメッセージを描画する
    # ゲームの決着がついていない場合は、手番を表示する
    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, size=3):             
-   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:
-           x = math.floor(event.xdata)
-            y = math.floor(event.ydata)
-           self.move(x, y)
-           self.draw_board()
           
    # fig の画像にマウスを押した際のイベントハンドラを結び付ける
-   fig.canvas.mpl_connect("button_press_event", on_mouse_down)  
元と同じなので省略   

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

__上記__ の __修正後__ に、__下記__ のプログラムで __`play`__ メソッドを __実行__ すると、__実行結果__ のように __エラーが発生__ します。__エラーの原因__ について少し考えてみて下さい。

```python
mb.play(ai=[None, None], gui=True)
```

実行結果

```
略
Cell In[5], line 11
      9 # gui が True の場合に、ゲーム盤を描画する画像を作成し、イベントハンドラに結びつける
     10 if gui:
---> 11     fig, ax = plt.subplots(figsize=[size, size])
     12     fig.canvas.toolbar_visible = False
     13     fig.canvas.header_visible = False

NameError: name 'size' is not defined
```

### 仮引数 `size` の移動

__上記__ の __エラー__ は、__`play`__ メソッドの __下記の行__ で、__Figure__ を __作成するため__ に __`subplots`__ メソッドを __呼び出す際__ に __実引数に記述__ した __`size`__ が __定義されていない__ ことが __原因__ です。

```python
        fig, ax = plt.subplots(figsize=[size, size])
```

__この `size`__ は、__`draw_board`__ メソッドの __仮引数__ でしたが、__Figure__ の __作成の処理__ を __`play`__ メソッドに __移動した__ ので、__`play`__ メソッドの __仮引数に移動__ する __必要__ が __あります__。そこで、__下記__ のプログラムのように、__`play`__ メソッドに、__`draw_board`__ メソッドの __仮引数 `size`__ を __そのままの形__ で __デフォルト引数__ として __追加__ することにします。

* __1 行目__：__仮引数__ に __デフォルト値__ を __`3`__ とする __デフォルト引数 `size`__ を __追加__ する

```python
def play(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
以下同じなので省略

Marubatsu.play = play
```

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

```python
def play(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
    # seed が None でない場合は、seed を乱数の種として設定する
    if seed is not None:
        random.seed(seed)
    
    # 〇×ゲームを再起動する
    self.restart()

    # 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:
                x = math.floor(event.xdata)
                y = math.floor(event.ydata)
                self.move(x, y)
                self.draw_board()
        
        # fig の画像にマウスを押した際のイベントハンドラを結び付ける
        fig.canvas.mpl_connect("button_press_event", on_mouse_down)     

    # ゲームの決着がついていない間繰り返す
    while self.status == Marubatsu.PLAYING:
        # ゲーム盤の表示
        if verbose:
            if gui:
                self.draw_board()
                return
            else:
                print(self)
        # 現在の手番を表す ai のインデックスを計算する
        index = 0 if self.turn == Marubatsu.CIRCLE else 1
        # 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()
        else:
            print(self)
            
    return self.status
    
Marubatsu.play = play
```
</details>

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

```diff_python
-def play(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False):
+def play(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
以下同じなので省略

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

__上記__ の __修正後__ に、__下記__ のプログラムで __`play`__ メソッドを __実行__ すると、__実行結果__ のように __別のエラーが発生__ し、__その後__ で __ゲーム盤__ が __描画されていない画像__ が __表示される__ ようになります。__エラーの原因__ について少し考えてみて下さい。

```python
mb.play(ai=[None, None], gui=True)
```

実行結果

```
略
Cell In[6], line 3
      1 def draw_board(self, size=3):             
      2     # y 軸を反転させる
----> 3     ax.invert_yaxis()
      5     # 枠と目盛りを表示しないようにする
      6     ax.axis("off")

NameError: name 'ax' is not defined
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/076d9aaf-11dd-e369-2cb7-f31b68275f40.png" width="200">

### `draw_board` への仮引数 `ax` の追加

__上記__ の __エラー__ は __`draw_board`__ メソッドの中の __`ax.invert_yaxis()`__ を __実行__ する際に、__`ax`__ が __定義されていない__（is not defined）ことが __原因__ です。また、__`draw_board`__ の中で __`ax`__ が __定義されなくなった__ のは、__`ax`__ に __値を代入__ する __下記__ の __`subplots`__ を呼び出す __処理__ を、__`play`__ メソッドに __移動__ したことが __原因__ です。

```python
fig, ax = plt.subplots(figsize=[size, size])
``` 

:::note
上記のように、__定義されていない名前__ を __利用__ するプログラムを __記述__ した場合は、__VSCode__ では、__下図__ のように、__オレンジ色__ の __波線__ が __表示__ されます

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/da5f6423-8b8e-577f-50c6-5273582d1948.png" width="300">

また、__波線の上__ に __マウスを移動__ すると、__下図__ のように、__その原因__ が __表示__ されるので、__波線__ に __気づいた場合__ は __参考にすると良い__ でしょう。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/d81f0eb9-45e9-6462-1355-6cfc9bdb5e2b.png" width="400">

なお、__VSCode__ では、__赤色の波線__ は __文法エラー__ を表すので、__修正しなければ__ プログラムを __実行__ することが __できません__。一方、__オレンジ色の波線__ は何かが __おかしい可能性がある__ という __警告__ なので、__間違っている__ とは __限りません__。また、__オレンジ色の波線__ が __表示されても__、その文が __実行されなければ__ エラーは __発生しません__。
:::

そこで、__`play`__ メソッドの __`ax`__ を __`draw_board`__ メソッドに __伝える__ ことが __できるようにする__ ために、__`draw_board`__ メソッドに __仮引数 `ax`__ を __追加__ し、__`play`__ メソッド内で __`draw_board`__ メソッドを __呼び出す際__ に、__`ax`__ を __実引数__ に __記述__ するように __修正__ します。

__下記__ は、そのように __`draw_board`__ を __修正__ したプログラムです。なお、__仮引数 `size`__ はもう __必要が無くなった__ ので __削除__ しました。

```python
def draw_board(self, ax):  
元と同じなので省略

Marubatsu.draw_board = draw_board
```

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

```python
def draw_board(self, ax):             
    # y 軸を反転させる
    ax.invert_yaxis()

    # 枠と目盛りを表示しないようにする
    ax.axis("off")

    # 上部のメッセージを描画する
    # ゲームの決着がついていない場合は、手番を表示する
    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, size=3):  
+def draw_board(self, ax):  
元と同じなので省略

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

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

* __9、15、22 行目__：__`draw_board`__ を __呼び出す際__ に、__実引数　`ax`__ を __記述__ する
 
```python
 1  def play(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
元と同じなので省略
 2          # ローカル関数としてイベントハンドラを定義する
 3          def on_mouse_down(event):
 4              # Axes の上でマウスを押していた場合のみ処理を行う
 5              if event.inaxes:
 6                  x = math.floor(event.xdata)
 7                  y = math.floor(event.ydata)
 8                  self.move(x, y)
 9                  self.draw_board(ax)
元と同じなので省略
10      # ゲームの決着がついていない間繰り返す
11      while self.status == Marubatsu.PLAYING:
12          # ゲーム盤の表示
13          if verbose:
14              if gui:
15                  self.draw_board(ax)
16                  return
17              else:
18                  print(self)
元と同じなので省略
19      # 決着がついたので、ゲーム盤を表示する
20      if verbose:
21          if gui:
22              self.draw_board(ax)
23          else:
24              print(self)
25            
26      return self.status
27    
28  Marubatsu.play = play
```

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

```python
def play(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
    # seed が None でない場合は、seed を乱数の種として設定する
    if seed is not None:
        random.seed(seed)
    
    # 〇×ゲームを再起動する
    self.restart()

    # 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:
                x = math.floor(event.xdata)
                y = math.floor(event.ydata)
                self.move(x, y)
                self.draw_board(ax)
        
        # fig の画像にマウスを押した際のイベントハンドラを結び付ける
        fig.canvas.mpl_connect("button_press_event", on_mouse_down)     

    # ゲームの決着がついていない間繰り返す
    while self.status == Marubatsu.PLAYING:
        # ゲーム盤の表示
        if verbose:
            if gui:
                self.draw_board(ax)
                return
            else:
                print(self)
        # 現在の手番を表す ai のインデックスを計算する
        index = 0 if self.turn == Marubatsu.CIRCLE else 1
        # 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)
        else:
            print(self)
            
    return self.status
    
Marubatsu.play = play
```
</details>

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

```diff_python
def play(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
元と同じなので省略
        # ローカル関数としてイベントハンドラを定義する
        def on_mouse_down(event):
            # Axes の上でマウスを押していた場合のみ処理を行う
            if event.inaxes:
                x = math.floor(event.xdata)
                y = math.floor(event.ydata)
                self.move(x, y)
-               self.draw_board()
+               self.draw_board(ax)
元と同じなので省略
    # ゲームの決着がついていない間繰り返す
    while self.status == Marubatsu.PLAYING:
        # ゲーム盤の表示
        if verbose:
            if gui:
-               self.draw_board()
+               self.draw_board(ax)
                return
            else:
                print(self)
元と同じなので省略
    # 決着がついたので、ゲーム盤を表示する
    if verbose:
        if gui:
-           self.draw_board()
+           self.draw_board(ax)
        else:
            print(self)
            
    return self.status
    
Marubatsu.play = play
```
</details>

__上記__ の __修正後__ に、__下記__ のプログラムで __`play`__ を実行し、__マスの上__ で __マウスを押す__ と、__マウスを押すたび__ に、__同じ画像__ に __マークが配置__ されるように __なります__ が、__いくつかの点__ で __おかしな描画__ が行われてしまいます。

```python
mb.play(ai=[None, None], gui=True)
```

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

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/4ff5d799-6fc2-472d-8fe1-58228779493a.png" width="150">

__上図__ の __実行結果__ は、__(0, 0)__ の __マスの上__ で __マウスを押した場合__ の __図__ です。__図__ を __よく見る__ と __以下の 3 つ__ の点が __おかしい__ ことが __わかります__。

* マウスを押した __(0, 0) ではなく__、__(0, 2)__ のマスに __マークが表示__ される
* __手番__ を表す __文字列__ が __上部ではなく__、__下部に表示__ される
* __手番__ を表す __文字列__ の __`Turn` の後__ に、__〇__ と __×__ が __重ねて表示__ される

## マークと手番の表示位置が変になる理由の検証と解決

まず、__マーク__ と __手番__ の __表示位置__ が __変になる理由__ を __検証__ することにします。そのためには、__どのような法則__ で __変な位置__ に __表示されるか__ を __調べる必要__ があるので、__上記__ に __続けて (1, 0)__ の __マスの上__ で __マウスを押してみる__ ことにします。下図は __(1, 0)__ の __マスの上__ で __マウス__ を __押した後__ に __表示__ される __図__ です。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/b2aaa290-638f-3740-5c77-828d50269f26.png" width="150">

__図__ から、__今度__ は以下のような表示が行われることが分かります。

* __最初に着手__ した __(0, 0)__ の __〇__ が __正しいマス__ に __表示__ されるようになる
* __今回着手__ した __(1, 0)__ の __×__ が __(1, 2)__ に __表示__ される
* __手番__ を表す __文字列__ が __正しく上部__ に __表示__ されるが、__〇__ と __×__ が __重ねて表示__ される現象は __解消されない__

__勘の良い方__ は __法則__ が __わかったかも__ しれませんが、__続けて (2, 0)__ に __着手__ を行ってみることにします。下図は __(2, 0)__ の __マスの上__ で __マウス__ を __押した後__ に __表示__ される __図__ です。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/6102b4c1-5baf-c1b3-b948-841a8c36f2de.png" width="150">

下図は、__それぞれの着手__ で __表示__ された __画像__ を __並べた__ ものです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/4ff5d799-6fc2-472d-8fe1-58228779493a.png" width="100">　<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/b2aaa290-638f-3740-5c77-828d50269f26.png" width="100">　<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/6102b4c1-5baf-c1b3-b948-841a8c36f2de.png" width="100">
　
__下記__ の __表__ は、__それぞれの着手__ で、__それぞれのマーク__ と __手番の文字列__ が __表示__ された __場所__ を __まとめたもの__ です。__上図__ と __下記の表__ から __法則__ を __考えてみて下さい__。

|  | 1 手目 | 2 手目 | 3 手目 | 手番の文字列 |
|:-:|:-:|:-:|:-:|:-:|
| __(0, 0)__  | (0, 2)  | (0, 0)  | (0, 2)  | 下部 |
| __(1, 0)__  |   | (1, 2)  | (1, 0)  | 上部 |
| __(2, 2)__  |   |   | (2, 0)  | 下部 |

:::note
上記でよくわからない人は、__上記以外__ の __様々なマス__ に __着手__ を行ってみて下さい。
:::

### 表示の法則

__上記__ の __図と表__ から、__着手__ を __行うたび__ に、__マーク__ や __手番__ を表す __文字列__ の __表示位置__ が __上下__ で __反転する__ ことが __分かります__。__そのこと__ に __気づく__ ことが __できれば__、__上下__ を __反転する__ ような __処理__ が __原因__ である __可能性が高い__ ことが __推測できる__ ようになります。

__これまでに記述__ したプログラムの中で、__上下__ を __反転する処理__ は、__`draw_board`__ メソッドの __最初で記述__ した __下記__ プログラムの __3 行目__ の __Axes__ の __y 軸__ を __反転__ する処理です。

```python
1  def draw_board(self, ax):             
2      # y 軸を反転させる
3      ax.invert_yaxis()
4    
5      # 枠と目盛りを表示しないようにする
6      ax.axis("off")
以下略
```

__`draw_board`__ メソッドは、__着手__ を __行うたび__ に __実行される__ ので、__`ax.invert_yaxis()`__ によって、__着手__ を __行うたび__ に __ゲーム盤の画像__ の __上下__ が __反転する__ ことになります。

__Axes__ の __y 軸__ を __反転する処理__ は、[以前の記事](https://qiita.com/ysgeso/items/442db53b5b7e22c1000c#%E8%BB%B8%E3%81%AE%E5%8F%8D%E8%BB%A2)で説明したように、__必要な処理__ ですが、__Figure__ を __作成した際__ に、__一度だけ__ 行えば __十分__ です。従って、__この処理__ を __`play` メソッド__ に __移動__ することで、__問題を解決__ することが __できます__。なお、__上記__ のプログラムの __6 行目__ の __枠と目盛り__ を __表示しない__ ようにする __`ax.axis("off")`__ という __処理__ は、__`draw_board` の中__ で __記述__ しても、元々 __表示されていない枠と目盛り__ を、__もう一度表示しないようにする__ ことになるので、__このままでも__ 特に __問題はありません__ が、__この処理__ も __Figure__ を __作成した際__ に __一度__ だけ __行えば良い処理__ なので、ついでに __`play`__ メソッドに __移動する__ ことにします。

### `play` メソッドと `draw_board` メソッドの修正

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

```python
def draw_board(self, ax):             
    # ここにあった、y 軸の反転処理と、枠と目盛りを表示しないようにする処理を削除する
元と同じなので省略

Marubatsu.draw_board = draw_board
```

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

```python
def draw_board(self, ax):             
    # 上部のメッセージを描画する
    # ゲームの決着がついていない場合は、手番を表示する
    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):             
    # y 軸を反転させる
-   ax.invert_yaxis()
    
    # 枠と目盛りを表示しないようにする
-   ax.axis("off")
元と同じなので省略

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

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

* __6 行目__：__Axes__ の __y 軸__ を __反転する処理__ を __追加__ する
* __9 行目__：__Axes__ の __枠と目盛り__ を __表示しないようにする処理__ を __追加__ する

```python
 1  def play(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
元と同じなので省略       
 2          # fig の画像にマウスを押した際のイベントハンドラを結び付ける
 3          fig.canvas.mpl_connect("button_press_event", on_mouse_down)     
 4
 5          # y 軸を反転させる
 6          ax.invert_yaxis()
 7      
 8          # 枠と目盛りを表示しないようにする
 9          ax.axis("off")
元と同じなので省略       
10   
11  Marubatsu.play = play
```

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

```python
def play(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
    # seed が None でない場合は、seed を乱数の種として設定する
    if seed is not None:
        random.seed(seed)
    
    # 〇×ゲームを再起動する
    self.restart()

    # 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:
                x = math.floor(event.xdata)
                y = math.floor(event.ydata)
                self.move(x, y)
                self.draw_board(ax)
        
        # fig の画像にマウスを押した際のイベントハンドラを結び付ける
        fig.canvas.mpl_connect("button_press_event", on_mouse_down)     

        # y 軸を反転させる
        ax.invert_yaxis()
    
        # 枠と目盛りを表示しないようにする
        ax.axis("off")

    # ゲームの決着がついていない間繰り返す
    while self.status == Marubatsu.PLAYING:
        # ゲーム盤の表示
        if verbose:
            if gui:
                self.draw_board(ax)
                return
            else:
                print(self)
        # 現在の手番を表す ai のインデックスを計算する
        index = 0 if self.turn == Marubatsu.CIRCLE else 1
        # 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)
        else:
            print(self)
            
    return self.status
    
Marubatsu.play = play
```
</details>

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

```diff_python
def play(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
元と同じなので省略       
        # fig の画像にマウスを押した際のイベントハンドラを結び付ける
        fig.canvas.mpl_connect("button_press_event", on_mouse_down)     

        # y 軸を反転させる
+       ax.invert_yaxis()
    
        # 枠と目盛りを表示しないようにする
+       ax.axis("off")
元と同じなので省略       
   
Marubatsu.play = play
```
</details>

__上記__ の __修正後__ に、__下記__ のプログラムで __`play`__ を実行し、__マスの上__ で __マウスを押す__ と、__上下が反転__ する __問題点__ が __修正__ されていることが __確認__ できます。ただし、__手番の文字列__ の中で __〇__ と __×__ が __重なって表示__ される __問題__ は __解決できていません__。

__下記__ の __実行結果__ は、__(0, 0)__、__(1, 0)__、__(0, 2)__ の __順__ で __マスの上__ で __マウスを押した場合__ の図で、__正しいマス__ に __着手__ が __行われている__ ことが __確認__ できます。__実際__ に __JupyterLab 上で__ 様々なマスに着手を行って __確認__ してみて下さい。

```python
mb.play(ai=[None, None], gui=True)
```

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

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/51d80048-e72a-62c0-1f6f-ace1090751b4.png" width="150">

## 〇 と × が重なって表示される問題の原因と解決

__`%matplotlib widget`__ を __実行__ することで、__Figure の内容__ を __描画__ した __画像__ に __対して__、__線__ や __文字列__ などを __後から描画__ して __更新__ することが __できる__ ように __なります__。__その際__ に、当然ですが、__それまで__ に __描画した内容__ は __残り続けます__。

そのため、__`draw_board`__ メソッドを __実行するたび__ に、__ゲーム盤の画像__ を表す __Figure__ に「__ゲーム盤の枠線__」、「__配置されたマーク__」、「__手番の情報__」が __描画__ されますが、__その際__ に __それ以前__ に __描画した内容__ が __自動的__ に __削除される__ ことは __ありません__。

従って、__〇__ と __×__ が __重なって表示__ される __問題__ は、__それ以前__ の __手番__ で __`draw_board`__ メソッドを __実行した際__ に __描画__ した __手番を表す文字列__ の __上__ に、__新しい手番を表す文字列__ が __重ねて描画された__ ことが __原因__ です。

__この問題__ は、__`draw_board`__ メソッドで __ゲーム盤の画像__ を __描画する際__ に、__Figure__ に対して __登録__ した __線や文字列__ などの __[Artist](https://qiita.com/ysgeso/items/ab2770693c947c108dd0#artist) の情報__ を __削除__ することで __解決__ することができます。

__別の言葉__ で説明すると、__ゲーム盤__ を __描画するたび__ に、__それまで__ に __描画した内容__ を __すべて消して__ から、__改めて__ ゲーム盤を __一から描画し直す__ ということです。

__その処理__ は、__Axes__ の __`clear`__ メソッド[^2]を __呼び出す__ ことで __行うことができる__ ので、__その処理__ を、__下記__ のプログラムのように、__`draw_board`__ の __最初に記述__ します。

* __3 行目__：__`ax`__ の __`clear`__ メソッドを __実行__ して、__Axes__ の __内容__ を __クリア__ する

```python
def draw_board(self, ax):             
    # Axes の内容をクリアして、これまでの描画内容を削除する
    ax.clear()
元と同じなので省略

Marubatsu.draw_board = draw_board
```

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

```python
def draw_board(self, ax):             
    # Axes の内容をクリアして、これまでの描画内容を削除する
    ax.clear()
    
    # 上部のメッセージを描画する
    # ゲームの決着がついていない場合は、手番を表示する
    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):             
    # Axes の内容をクリアして、これまでの描画内容を削除する
+   ax.clear()
元と同じなので省略
```
</details>

__上記__ の __修正後__ に、__下記__ のプログラムで __`play`__ を実行すると、__実行結果__ のように、__Axes__ の __枠と目盛__ りが __表示され__、__手番の情報__ が __下部に表示される__ ようになってしまいます。また、__目盛り__ を __よく見る__ ことでも、__y 軸__ の __上下の反転__ が __行われていない__ ことが __わかります__。

```python
mb.play(ai=[None, None], gui=True)
```

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

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/78a9560e-f14f-d869-a0bd-5ec8ad5610a9.png" width="150">

__Axes__ の __`clear`__ メソッドの詳細については、下記のリンク先を参照して下さい。

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

### `clear` メソッドが行う処理

__上記__ のような __問題が発生__ する __理由__ は、Axes の __`clear`__ メソッドが、__Axes__ への __Artist__ の __登録__ の情報 __だけでなく__、__軸の反転__ や、__目盛りの非表示__ など、__Axes__ に対して行った __すべての処理__ を __クリアしてしまう__ からです。

そのため、__Axes__ の __`clear`__ メソッドを __実行した場合__ は、__下記__ のプログラムの __6、9 行目__ のように、__軸の反転__ と __枠と目盛りの非表示__ の __処理__ を __改めて実行__ する __必要__ が __あります__。

```python
 1  def draw_board(self, ax):             
 2     # Axes の内容をクリアして、これまでの描画内容を削除する
 3     ax.clear()
 4   
 5     # y 軸を反転させる
 6     ax.invert_yaxis()
 7   
 8     # 枠と目盛りを表示しないようにする
 9     ax.axis("off")
元と同じなので省略
10
11  Marubatsu.draw_board = draw_board
```
<details><summary><strong>行番号のないプログラム</strong></summary>

```python
def draw_board(self, ax):             
    # Axes の内容をクリアして、これまでの描画内容を削除する
    ax.clear()
    
    # y 軸を反転させる
    ax.invert_yaxis()
    
    # 枠と目盛りを表示しないようにする
    ax.axis("off")
    
    # 上部のメッセージを描画する
    # ゲームの決着がついていない場合は、手番を表示する
    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):             
    # Axes の内容をクリアして、これまでの描画内容を削除する
    ax.clear()
    
    # y 軸を反転させる
+   ax.invert_yaxis()
    
    # 枠と目盛りを表示しないようにする
+   ax.axis("off")
元と同じなので省略

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

__軸の反転__ と __枠と目盛りの削除__ の __処理__ を __`draw_board` で行う__ ようにしたので、__下記__ のプログラムのように、__`play`__ メソッドから __それらの処理__ を __削除__ することにします。

```python
def play(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
元と同じなので省略
        # fig の画像にマウスを押した際のイベントハンドラを結び付ける
        fig.canvas.mpl_connect("button_press_event", on_mouse_down)     

        # この下にあった、軸の反転と枠と目盛りの非表示の処理を削除する
元と同じなので省略
    
Marubatsu.play = play
```

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

```python
def play(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
    # seed が None でない場合は、seed を乱数の種として設定する
    if seed is not None:
        random.seed(seed)
    
    # 〇×ゲームを再起動する
    self.restart()

    # 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:
                x = math.floor(event.xdata)
                y = math.floor(event.ydata)
                self.move(x, y)
                self.draw_board(ax)
        
        # fig の画像にマウスを押した際のイベントハンドラを結び付ける
        fig.canvas.mpl_connect("button_press_event", on_mouse_down)     

    # ゲームの決着がついていない間繰り返す
    while self.status == Marubatsu.PLAYING:
        # ゲーム盤の表示
        if verbose:
            if gui:
                self.draw_board(ax)
                return
            else:
                print(self)
        # 現在の手番を表す ai のインデックスを計算する
        index = 0 if self.turn == Marubatsu.CIRCLE else 1
        # 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)
        else:
            print(self)
            
    return self.status
    
Marubatsu.play = play
```
</details>

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

```diff_python
def play(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
元と同じなので省略
        # fig の画像にマウスを押した際のイベントハンドラを結び付ける
        fig.canvas.mpl_connect("button_press_event", on_mouse_down)     

        # y 軸を反転させる
-       ax.invert_yaxis()
    
        # 枠と目盛りを表示しないようにする
-       ax.axis("off")        
元と同じなので省略
    
Marubatsu.play = play
```
</details>

__上記__ の __修正後__ に、__下記__ のプログラムで __`play`__ メソッドを __実行__ すると、今度は __問題なく__ 〇× ゲームを __遊べるようになっている__ ことが __確認__ できます。__実際__ に __何度か遊んでみて__、__問題が発生__ しないかどうかを __確認__ してみて下さい。

下記の __実行結果__ は、__決着がつく__ まで __着手を行った場合__ の __図__ です。

```python
mb.play(ai=[None, None], gui=True)
```

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

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/977d6b0f-4673-dcb5-a0ac-da94c66fa732.png" width="150">

実は、__上記__ のプログラムには一つ __問題があります__。それが何かを少し考えてみて下さい。

:::note
今回の記事で、先程 __わざわざ `draw_board`__ から __`play` へ移動__ した、__軸の反転などの処理__ を、__再び `draw_board` へ戻す__ ような __手順__ で __〇×ゲームを実装__ したことに __疑問を覚えた人__ が __いるのではないか__ と思います。

__実際__ に、__プログラミング__ に __ある程度慣れた上__ で、__matplotlib__ の __仕組み__ を __よく理解__ していれば、__今回の記事__ のような __まどろっこしい修正__ を __何度も行わず__ に、__一度__ で __正しいプログラム__ を __記述__ する事が __できるかもしれません__。

しかし、__一度別の場所に移動__ した __プログラム__ を、__後から__ 様々な理由で __元の場所に戻す__ ようなことは、__実際__ の __プログラミング__ でも __よくあること__ です。__正直に告白__ すると、__今回の記事__ を __執筆した際__ に、__筆者__ は __実際__ に __軸の反転などの処理__ を、__`play`__ へ __移した後__ で、__移す必要がなかったこと__ に __気づいて__ 元に __戻しました__ 。

__そのようなことの経験__ は、__プログラミング__ の __力の上達__ に __役立つと思いました__ ので、__あえて一度でうまくいく__ プログラムを __紹介するのではなく__、__今回の記事__ のような __回り道になる__ ような __手順__ を __紹介しました__。
:::

:::note
__`clear`__ メソッドは、__Figure__ と __Axes__ の __両方__ に対して __行うことができます__ が、__ゲーム盤__ の __枠__ や __文字列__ を表す __Artist__ は、__`ax.plot()`__ や __`ax.text()`__ によって __行う__ ことからわかるように、__Axes に__ 対して __登録している__ ので、__ゲーム盤の描画__ の __情報__ を表す __Artist__ を __削除する場合__ は、__Axes__ の __`clear`__ メソッドを __実行します__。

__`fig.clear()`__ を __実行__ することでも、__Figure__ から、__ゲーム盤__ の __Artist の情報__ を __削除__ することが __できます__ が、__その場合__ は、__Figure__ に __登録__ された __Axes までも__ が __削除されてしまう__[^3] ため、__Axes__ の __再登録が必要__ になる点に __注意__ が必要です。
:::

__Figure__ の __`clear`__ メソッドの詳細については、下記のリンク先を参照して下さい。

https://matplotlib.org/stable/api/figure_api.html#matplotlib.figure.Figure.clear

## 決着後の着手の禁止

上記のプログラムの __問題__ は、__ゲーム__ の __決着後__ も __着手__ が __行えてしまう__ というものです。例えば、__下図右__ は、__下図左__ で __〇__ が __勝利した後__ に __(2, 1)__ のマスの上で __マウスを押した__ 場合の図で、__実際__ に __ゲームの決着後__ に __着手__ が __行えています__。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/977d6b0f-4673-dcb5-a0ac-da94c66fa732.png" width="150">　<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/cf070158-5b1a-07ef-9367-cc6af8a4f30d.png" width="150">

__`gui=False`__ を __記述__（またはキーワード引数 __`gui` を省略__）して __`play`__ メソッドを __実行した場合__ に、__ゲーム__ の __決着後__ に __着手__ を __行えない理由__ は以下の通りです。

* __決着が付いた時点__ で、__`play`__ メソッドの中の __繰り返し処理__ が __終了__ する
* __イベントハンドラ__ を __登録しない__ ので、__`play`__ メソッドの __処理が終了__ した時点で、__`play`__ メソッドに __関連する処理__ が __完全に終了__ する

一方、__`gui=True`__ を __記述__ して __`play`__ メソッドを __実行した場合__ は、__イベントハンドラ__ を __登録__ するため、__`play`__ メソッドの __処理が終了__ しても、__ゲーム盤__ の __画像の上__ で __マウスを押す__ ことで、__登録__ した __イベントハンドラ__ が __何度でも__ 呼び出されて __実行されます__。

従って、__ゲームの決着後__ に __着手__ を __行えない__ ようにするためには、__下記__ のプログラムのように、__イベントハンドラ__ の __処理の中__ で、__ゲーム__ が __決着していない場合__ のみ __着手を行う__ ような __処理を記述__ する __必要__ が __あります__。

* __5 行目__：__着手を行う場合__ を __判定__ する __条件式__ に、__ゲームの状態__ を表す __`self.status`__ が、__ゲーム__ の __決着がついていない__ ことを表す __`Marubatsu.PLAYING`__ であることを __加える__

```python
 1  def play(self, ai, params=[{}, {}], 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)
元と同じなので省略               
10    
11  Marubatsu.play = play
```

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

```python
def play(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
    # seed が None でない場合は、seed を乱数の種として設定する
    if seed is not None:
        random.seed(seed)
    
    # 〇×ゲームを再起動する
    self.restart()

    # 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)
        
        # fig の画像にマウスを押した際のイベントハンドラを結び付ける
        fig.canvas.mpl_connect("button_press_event", on_mouse_down)     
               

    # ゲームの決着がついていない間繰り返す
    while self.status == Marubatsu.PLAYING:
        # ゲーム盤の表示
        if verbose:
            if gui:
                self.draw_board(ax)
                return
            else:
                print(self)
        # 現在の手番を表す ai のインデックスを計算する
        index = 0 if self.turn == Marubatsu.CIRCLE else 1
        # 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)
        else:
            print(self)
            
    return self.status
    
Marubatsu.play = play
```
</details>

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

```diff_python
def play(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
元と同じなので省略
        # ローカル関数としてイベントハンドラを定義する
        def on_mouse_down(event):
            # Axes の上でマウスを押していた場合で、ゲーム中の場合のみ処理を行う
-           if event.inaxes:
+           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)
元と同じなので省略               
    
Marubatsu.play = play
```
</details>

__上記__ の __修正後__ に、__下記__ のプログラムで __`play`__ メソッドを __実行__ すると、__ゲーム__ の __決着後__ にゲーム盤の __空のマスの上__ で __マウスを押して__ も __着手__ を __行えなくなっている__ ことが __確認__ できます。__実際__ に __何度か遊んで__ みて __確認__ して下さい。なお、__実行結果__ は __省略__ します。

```python
mb.play(ai=[None, None], gui=True)
```

:::note
__本記事__ では __採用しません__ が、__ゲーム__ が __決着した際__ に、__ipywidgets から__、__イベントハンドラ__ の __登録を削除__ することで、__着手__ を __行えなくする__ という __方法__ もあります。

__本記事__ がその方法を __採用しない理由__ は、今後の記事で、__ゲーム__ の __リセットボタン__ を __表示__ することで、__`play`__ メソッドを __新しく実行しなくても__、__新しいゲーム__ を __開始できる__ ようにするからです。__ゲーム__ が __決着した際__ に、__ipywidgets から__、__イベントハンドラ__ の __登録を削除__ すると、リセットボタンを押して __新しいゲーム__ を __開始する際__ に、__再び__ イベントハンドラを __ipywidgets__ に __登録する必要__ が __生じてしまいます__。

なお、__ipywidgets から__、__イベントハンドラ__ の __登録を削除__ する方法は、__`mpl_disconnect`__ というメソッドを __利用__ します。

__`mpl_disconnect`__ の詳細は、下記のリンク先を参照して下さい。
:::

https://matplotlib.org/stable/api/backend_bases_api.html#matplotlib.backend_bases.FigureCanvasBase.mpl_disconnect

# フロー駆動型とイベント駆動型のフローチャート

:::note
2024/04/08：__図__ と __説明__ が __一部間違っていた__ ので __修正__ しました。
:::

__処理の流れ__ が良く __わからなくなっている人__ が __いるかもしれません__ ので、__`gui`__ が __`False`__ の場合に __`play`__ メソッドを実行した際に行われる __[フロー駆動型プログラミング](https://qiita.com/ysgeso/items/1920c68f5a1e8a998796#%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E9%A7%86%E5%8B%95%E5%9E%8B%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0)の処理__ と、__`gui`__ が __`True`__ の場合に __`play`__ メソッドを実行した際に行われる __イベント駆動型プログラミングの処理__ の __フローチャート__ を示します。

__下図__ は、__`play`__ メソッドの __フローチャート__  です。__オレンジ色__ の __長方形__ が、__`play`__ メソッドが __終了する処理__ であることを表します。なお、ゲームの __途中経過__ や、__結果__ を __表示__ する場合の __処理の流れ__ を __示したい__ ので、__`verbose`__ には __`True`__ が __代入__ されているものとして __`verbose`__ に __関する処理__ は __省略__ しました。また、テキストボックスに __exit を入力__ して __ゲーム__ を __途中で終了__ する __処理など__ の __例外的な処理__ も __省略__ しました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/0331668e-4cce-34e9-08ea-ba94c25b1b5e.png" width="350">

## フロー駆動型の `play` メソッドのフローチャート

__CUI__ で __〇×ゲーム__ を __遊ぶ場合__ は、__下記__ の __方法__ で __`play`__ メソッドを __実行__ します。

* __`%matplotlib widget`__ を __実行しない__（既に実行していた場合は、__`%matplotlib inline`__ を __実行__ して __%matplotlib widget__ を __実行する前__ の __状態に戻す__）
* __仮引数 `gui`__ に __対応__ する __実引数__ を __省略__ するか、__`gui=False`__ を __記述__ して実行する

この場合は、__`play`__ メソッドの __フローチャート__ は、__下図__ のような __フロー駆動型__ の処理を __行います__。なお、__図__ の __点線__ と __灰色の図形__ は、その部分が __実行されない__ ことを __表します__。

__フロー駆動型__ では __`%matplotlib widget`__ を __実行しない__ ので、__イベントループ__ は __実行されません__。そのため、__`play`__ メソッドの __処理__ が __終了した時点__ で __すべての処理__ が __終了__ します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/bc2df5b1-d575-eec8-4c79-6f5c07face9f.png" width="350">

## イベント駆動型の `play` メソッドのフローチャート

__GUI__ で __〇×ゲーム__ を __遊ぶ場合__ は、__下記__ の __方法__ で __`play`__ メソッドを __実行__ します。

* __`%matplotlib widget`__ を __実行する__
* __`gui=True`__ を __記述__ して実行する

この場合は、__`play`__ メソッドの __フローチャート__ は、__下図__ の __赤線の処理__ を行い、__イベントハンドラ__ の __登録__ を __行った後__ で、__着手を行う前__ に __`play`__ メソッドの __処理が終了__ します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/216d2298-5210-24aa-00f2-646d19623007.png" width="350">

:::note
図の「__決着がついた？__」が __no になる__ のは、__以下__ のような __理由__ です。

* __ゲーム開始時__ の __局面__ は __決着がついていない__
* __次の繰り返し処理__ が __行われる前__ に __`play`__ メソッドの __処理が終了__ するので、「__決着がついた？__」が __yes__ に __なることはない__
:::

__`%matplotlib widget`__ を __実行する__ と、__下図__ の __フローチャート__ の処理を行う、__イベントループ__ の __処理__ が __実行__ され、__イベント駆動型__ の __処理__ が __行われる__ ようになります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/8615580e-f27d-d2d5-e5aa-27f3985d50f1.png" width="500">

__`play`__ メソッドの __処理の中__ で、__ゲーム盤__ の __画像の上__ で __マウスを押した際__ に __実行__ する __イベントハンドラ__ を __登録__ したので、__その操作__ を __行うたび__ に、__イベントループ__ から、__下図__ の __フローチャート__ の __イベントハンドラ__ の __処理__ が __呼び出され__ て __実行__ されます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/26c7f8f7-a88a-76b3-4c1c-34a63e992670.png" width="200">

__イベントループ__ の __フローチャート__ からわかるように、__イベントループ__ の __処理__ は __終了しない__ ので、__登録__ した __イベントハンドラ__ は、__対応__ する __イベント__ が __発生するたび__ に、__何度でも__ 呼び出されて __実行__ されます。そのため、__`play`__ メソッドが __終了した後__ で、__画像の上__ で __マウス__ を __押すたび__ に __着手を行う__ 処理が __実行__ されます。

## フロー駆動型とイベント駆動型の違いのまとめ

:::note warn
__フロー駆動型__ と __イベント駆動型__ の __違い__ は以下の通りである。

* __フロー駆動型__ は、__実行__ した __プログラムが終了__ すると、__すべての処理__ が __終了する__
* __イベント駆動型__ は、__イベントハンドラ__ を __登録__ することによって、__実行__ した __プログラム__ の __終了後__ に、__イベントループ__ によって、 __登録__ した __イベントハンドラの処理__ が __何度でも実行される__
:::

# 今回の記事のまとめ

今回の記事では、__イベント駆動型プログラミング__ の __手法__ で、__マウスを押す__ ことで __着手を行う__ という、__〇×ゲーム__ の __GUI__ の __入力部分__ を __実装__ し、__GUI__ で __人間どうしの対戦__ が __行える__ ようにしました。__次回の記事__ では、__GUI__ で __AI と対戦__ する __処理を記述__ します。

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

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

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

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

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

# 次回の記事

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

| 更新日時 | 更新内容 |
|:--|:--|
| 2024/4/8 | フローチャートの図と説明が一部間違っていたので修正しました |

[^1]:__イベントハンドラ__ に、__決められた以外__ の __仮引数__ を __勝手に追加__ すると __エラーが発生__ します
[^2]:__`cla`__ というメソッドでも __`clear`__ メソッドと __同じ処理__ を行うことができます
[^3]:__Axes__ も __Figure__ に __登録__ された __Artist の一種__ だからです