Pythonで〇×ゲームのAIを一から作成する　その73　重複する処理の統合

# 目次と前回の記事

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

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

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

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

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

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

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

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

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

# 処理の統合

前回の記事で、__リセットボタン__ を __実装__ しましたが、__`play`__、__`on_button_clicked`__、__`on_mouse_down`__ の __中__ で __似たような処理__ が __記述__ されているので、__統合__ することにします。ただし、__3 つの関数__ の __処理__ は __似てはいます__ が、__同じプログラム__ で __記述されていない__ ので、__統合するため__ には __3 つの関数__ で行われる __処理を検証__ する __必要__ が __あります__。

# `play` メソッドと `on_button_clicked` の処理の統合

__3 つの関数__ の __統合__ を __一度に行う__ のは __大変__ なので、最初に __`play`__ メソッドと __`on_button_clicked`__ の __処理__ の __統合__ を行うことにします。

__似たような処理__ を __統合__ するためには、__具体的__ な __共通点__ を __調べる必要__ があるので、__`play`__ メソッドと __`on_button_clicked`__ が __行う処理__ を __整理__ することにします。

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

__`play`__ メソッドが行う __処理__ を __箇条書き__ にすると __以下__ のようになります。

1. __乱数の種__ に __関する処理__ を行う
2. __ゲーム__ を __リセット__ する
3. __Figure__ に __関する処理__ を行う
4. __リセットボタン__ に __関する処理__ を行う
5. __繰り返し処理__ によって __決着がつくまで__ の間 __以下の処理__ を行う
    1. __ゲーム盤の画像__ を __描画__ する 
    2. __現在の手番__ が __AI の場合__ は __AI の着手__ を __選択__ する
    3. __現在の手番__ が __人間 の場合__ は、__関数の処理__ を __終了__ して __イベントループ__ を __再開__ する
6. __繰り返しの処理__ の __終了後__ に __ゲーム盤の画像__ を __描画__ する

__言葉の説明だけ__ では __わかりづらい__ ので、__フローチャート__ で __説明__ します。

### `play` メソッドのフローチャートの整理

__下図__ は、[以前の記事](https://qiita.com/ysgeso/items/c91d5373753253d98f23#%E3%83%95%E3%83%AD%E3%83%BC%E9%A7%86%E5%8B%95%E5%9E%8B%E3%81%A8%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E9%A7%86%E5%8B%95%E5%9E%8B%E3%81%AE%E3%83%95%E3%83%AD%E3%83%BC%E3%83%81%E3%83%A3%E3%83%BC%E3%83%88)で紹介した __`play`__ メソッドの __フローチャート__ です。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/95586e5c-a71b-ef96-a10d-0c17e22726aa.png" width="350">

__GUI__ で __〇×ゲーム__ を __遊ぶ場合__ は、__`gui`__ に __`True`__ が __代入__ されているので、__その場合__ の __フローチャート__ は __下図__ のようになります。なお、背景が __水色__ の __繰り返し処理__ の __中__ の、「__AI の手番？__」の __条件分岐__ は、その __右上__ の「__人間の手番？__」が __no__（False）の __場合__ に __実行される__ ので、__必ず yes__（True）になる点に __注意__ して下さい。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/5476f148-9b32-f594-4513-40b6c1910d59.png" width="350">

__上図__ から、「__`gui` が `True`__」の __条件分岐__ と、__実行されない部分__ を __削除__ して __整理__ すると、__下図__ のような __フローチャート__ になります。なお、__[以前の記事](https://qiita.com/ysgeso/items/c91d5373753253d98f23#%E3%83%95%E3%83%AD%E3%83%BC%E9%A7%86%E5%8B%95%E5%9E%8B%E3%81%A8%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E9%A7%86%E5%8B%95%E5%9E%8B%E3%81%AE%E3%83%95%E3%83%AD%E3%83%BC%E3%83%81%E3%83%A3%E3%83%BC%E3%83%88)__ の __フローチャート__ では、__乱数の種__ に関する __処理__ が __省略__ されていたので、__下図__ には __その処理__ を __追加__ しました。また、__以前の記事__ でフローチャートを示した __時点__ では、まだ __リセットボタン__ に関する __処理__ が __実装されていなかった__ ので、__下図__ には __リセットボタン__ に関する __処理__ を __追加__ しました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/dd5c49ee-621f-9efa-fc90-7a2cd861a4a2.png" width="350">

上図のフローチャートと、先程示した __`play`__ メソッドが行う __処理__ を __見比べて__ みて下さい。

1. __乱数の種__ に __関する処理__ を行う
2. __ゲーム__ を __リセット__ する
3. __Figure__ に __関する処理__ を行う
4. __リセットボタン__ に __関する処理__ を行う
5. __繰り返し処理__ によって __決着がつくまで__ の間 __以下の処理__ を行う
    1. __ゲーム盤の画像__ を __描画__ する 
    2. __現在の手番__ が __AI の場合__ は __AI の着手__ を __選択__ する
    3. __現在の手番__ が __人間 の場合__ は、__関数の処理__ を __終了__ して __イベントループ__ を __再開__ する
6. __繰り返しの処理__ の __終了後__ に __ゲーム盤の画像__ を __描画__ する

## `on_mouse_clicked` が行う処理

下記は、__`on_mouse_clicked`__ の定義です。

```python
        # リセットボタンのイベントハンドラを定義する
        def on_button_clicked(b):
            self.restart()       
            self.draw_board(ax)  
            
            while self.status == Marubatsu.PLAYING:
                # 現在の手番を表す ai のインデックスを計算する
                index = 0 if self.turn == Marubatsu.CIRCLE else 1
                # ai が着手を行うかどうかを判定する
                if ai[index] is not None:                
                    x, y = ai[index](self, **params[index])
                    self.move(x, y) 
                    self.draw_board(ax)                               
                else:
                    # 人間の手番の場合は、イベントハンドラを終了する
                    return
```

上記の __`on_mouse_clicked`__ 行う __処理__ を __箇条書き__ にすると __以下__ のようになります。

1. __ゲーム__ を __リセット__ する
2. __ゲーム盤の画像__ を __描画__ する 
3. __繰り返し処理__ によって __決着がつくまで__ の間 __以下の処理__ を行う
    1. __現在の手番__ が __AI の場合__ は __AI の着手__ を __選択__ し、__ゲーム盤の画像__ を __描画__ する
    2. __現在の手番__ が __人間 の場合__ は、__関数の処理__ を __終了__ して __イベントループ__ を __再開__ する

[前回の記事](https://qiita.com/ysgeso/items/e7b226cb567e08ca2767#%E5%95%8F%E9%A1%8C%E3%81%AE%E6%A4%9C%E8%A8%BC%E3%81%A8%E4%BF%AE%E6%AD%A3-1)で示したように、__上記__ の処理の __フローチャート__ は __下図__ のようになります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/6bb30091-cdc6-aa60-988e-ab00c7b4562f.png" width="350">

## `play` メソッドと `on_button_clicked` の比較

下記は、__`play`__ メソッドと __`on_button_clicked`__ の __処理の手順__ です。それぞれを __見比べて__、__どこ__ が __共通__ し、どこが __異なるか__ について少し考えてみて下さい。

### `play` メソッドの処理の手順

1. __乱数の種__ に __関する処理__ を行う
2. __ゲーム__ を __リセット__ する
3. __Figure__ に __関する処理__ を行う
4. __リセットボタン__ に __関する処理__ を行う
5. __繰り返し処理__ によって __決着がつくまで__ の間 __以下の処理__ を行う
    1. __ゲーム盤の画像__ を __描画__ する 
    2. __現在の手番__ が __AI の場合__ は __AI の着手__ を __選択__ する
    3. __現在の手番__ が __人間 の場合__ は、__関数の処理__ を __終了__ して __イベントループ__ を __再開__ する
6. __繰り返しの処理__ の __終了後__ に __ゲーム盤の画像__ を __描画__ する

### `on_button_clicked` の処理の手順

1. __ゲーム__ を __リセット__ する
2. __ゲーム盤の画像__ を __描画__ する 
3. __繰り返し処理__ によって __決着がつくまで__ の間 __以下の処理__ を行う
    1. __現在の手番__ が __AI の場合__ は __AI の着手__ を __選択__ し、__ゲーム盤の画像__ を __描画__ する
    2. __現在の手番__ が __人間 の場合__ は、__関数の処理__ を __終了__ して __イベントループ__ を __再開__ する

### フローチャートの比較

__わかりづらい__ と思った方は、下図の、__`play`__ メソッドと __`on_button_clicked`__ の __フローチャート__ を横に並べて __比較__ してみると良いでしょう。__行われる処理__ が __良く似ている__ ことが __わかる__ のではないかと思います。なお、__`play`__ メソッドの __処理の説明文__ の __一部__ は、__`on_button_clicked`__ の __表記__ に __合わせました__。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/05150d4a-60d5-1212-1298-39dc6ce1841f.png" width="700">

### 処理の共通点と違い

上記から、__それぞれの処理__ には __以下__ の __共通点__ があることが __わかります__。

* __ゲーム盤__ を __最初__ に __リセット__ する
* __決着がつくまで__、__繰り返し処理__ を行う
* __繰り返し__ 処理の __中__ で、__AI の手番__ の場合は __AI__ が __着手を行う__
* __繰り返し__ 処理の __中__ で、__人間の手番__ の場合は __関数の処理__ を __終了__ する

また、__以下__ の __違い__ があることが __わかります__。ただし、__GUI__ で __〇×ゲーム__ を __遊ぶ場合__ は、__`play`__ メソッドの __返り値__ は __利用しない__ ので、__返り値の違い__ は __重要__ では __ありません__。

* __`play`__ メソッドは、__乱数の種__ の __処理__ と、__Figure__ と __リセットボタン__ に関する __処理を行う__
* __画像__ を __描画__ する __タイミング__ が __異なる__
* __`play`__ メソッドは __繰り返し処理__ の __終了時__ に __返り値__ として __勝敗結果__ を返す

上記の「__画像__ を __描画__ する __タイミング__ が __異なる__」という __違い__ から、__一見すると__ 両者は __異なる処理__ を行っているように __見えるかもしれません__ が、__画像の描画__ に関しては、__`play`__ メソッドと __`on_button_clicked`__ は __全く同じ処理__ を行います。そのことを示します。

## 画像の描画の処理の検証

__〇×ゲーム__ で __ゲーム盤の描画__ を __行う必要__ が __生じる__ のは、__下記__ の __場合__ です。

* __ゲーム__ を __開始した時__
* __着手__ を __行った時__

また、__GUI__ で __〇×ゲーム__ を __遊ぶ__ 際に、__人間__ が __着手__ を行った場合は、__`on_mouse_down`__ の __イベントハンドラ内__ で __ゲーム盤の描画__ を __行います__。そのため、__`play`__ メソッドと __`on_button_clicked`__ では、__AI の着手__ に対してのみ __画像の描画__ が __行われます__。

__`play`__ メソッドと __`on_button_clicked`__ は、__上記__ の処理を、__下記__ の __タイミング__ で __行います__。なお、下記の「__最後の着手__」とは、__ゲーム__ の __決着がつく着手__ の事を __表します__。

|  | `play` | `on_button_clicked` |
|:--|:--|:--|
| __ゲームを開始した時__  | __最初__ の __繰り返し__ 処理 の __直後__  |  __繰り返し__ 処理の __直前__ |
| __最後以外の AI の着手__  | __次__ の __繰り返し__ 処理の __直後__ | __着手__ を行った __直後__  |
| __最後の AI の着手__  | __繰り返し__ 処理の __終了後__   | __着手__ を行った __直後__  |

わかりづらいと思いますので、__フローチャート__ で __説明__ します。

### ゲームを開始した時の画像の描画のタイミング

__下図__ は __ゲーム__ を __開始した時__ の __画像の描画__ が行われる __タイミング__ を表します。__赤線__ が __画像__ が __描画されるまで__ に行われた __処理__、__黄色の処理__ が __画像__ の __描画処理__ を表します。

__ゲーム開始時__ の時点では __決着__ は __ついていない__ ので、__いずれの場合__ も、__必ずゲーム盤__ の __描画__ に関しては __同じ処理__ が __行われる__ ことが __確認__ できます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/82cc42b0-10a5-47eb-99cf-72969f9367aa.png" width="700">

### AI が最初に着手を行った時の画像の描画のタイミング

下図は、__上記の後__ で __AI__ が __着手した場合__ に __画像の描画__ が行われる __タイミング__ を表します。__水色の線__ は、__AI__ が __着手を選択__ した __回__ の __次__ の __繰り返し__ 処理で __行われた処理__ であることを表します。なお、__AI の着手__ は、__最後の着手ではない__ ものとします。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/3eb94e6d-81ce-d1b7-c89d-ef4e19780d48.png" width="700">

__図__ からわかるように、__`play`__ メソッドと __`on_button_clicked`__ では、__下記__ のように __画像__ を __描画__ する __タイミング__ が __異なります__ が、__いずれ__ も __着手__ を __行った後__ で、__次__ の「__AI の手番？__」の __判定__ を __行う前__ に __ゲーム盤の描画__ が __行われる__ ことが __わかります__。

| `play` | `on_button_clicked` |
|:--|:--|
| __次__ の __繰り返し__ 処理の __直後__  | __その回__ の __繰り返し__ 処理で __着手__ を行った __直後__ |

### AI が 2 手目以降に着手を行った時の画像の描画のタイミング

下図は、__AI__ が __2 手目以降__ に __着手した場合__ に __画像の描画__ が行われる __タイミング__ を表します。__緑の線__ は、__AI__ が __着手を選択__ した __回__ の __前__ の __繰り返し__ 処理で __行われた処理__ であることを表します。なお、__AI の着手__ は、__最後の着手ではない__ ものとします。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/5626a1ee-d189-e560-59d9-d0be49184353.png" width="700">

__`play`__ メソッドの __図__ は、__先程の図__ と __全く同じ__ です。また、__`on_button_clicked`__ も、__AI__ が __着手__ を __選択した直後__ に __ゲーム盤の画像__ を __描画__ している点では __先程と同じ__ です。従って、先程と同様に、__`play`__ メソッドと __`on_button_clicked`__ では、__下記__ のタイミングで __画像__ を __描画__ し、__いずれ__ も __着手__ を __行った後__ で、__次__ の「__AI の手番？__」の __判定__ を __行う前__ に __ゲーム盤の描画__ が __行われる__ ことが __わかります__。

| `play` | `on_button_clicked` |
|:--|:--|
| __次__ の __繰り返し__ 処理の __直後__  | __その回__ の __繰り返し__ 処理で __着手__ を行った __直後__ |

### 人間の手番が回ってきた場合の処理

下図は、__人間の手番__ が __回ってきた場合__ に行われる __処理__ です。__人間の手番__ が __1 手目__ の場合と __2 手目以降__ の場合では、__処理の流れ__ が __若干異なり__ ますが、__本質的__ には __変わらない__ ので、__2 手目以降__ に __着手した場合__ に __画像の描画__ が行われる __タイミング__ を示します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/b24c58ec-92b5-7dac-7330-3208d448a2ad.png" width="700">

__いずれの場合__ も __ゲーム盤の描画__ を __行わず__ に __関数の処理__ が __終了__ することが __わかります__。

### AI が最後の着手を行った時の画像の描画のタイミング

下図は、__AI__ が __最後の着手__ を __行った場合__ に __画像の描画__ が行われる __タイミング__ を表します。__今回__ の図は、__画像の描画__ が __行われた後__ で、__関数__ の __処理が終了するまで__ の __流れ__ を __示しました__。__線の色__ は、__緑__、__赤__、__青__ の __順__ で __処理が行われる__ ことを __表します__。


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/d84f839d-05b1-0454-d8f8-9d4bbafafbe7.png" width="700">

__図__ から、__`play`__ メソッドは、__繰り返し__ の __処理__ の __終了後__ に __ゲーム盤の画像__ を __描画__ しますが、__`on_button_clicked`__ では、__これまでと同様__ に、__着手__ を __行った直後__ に __描画__ します。__いずれの場合__ も __関数の処理__ が __終了するまで__ の間に __ゲーム盤__ を __描画__ することが __わかります__。

| `play` | `on_button_clicked` |
|:--|:--|
| __繰り返し__ 処理が __終了__ した __直後__  | __その回__ の __繰り返し__ 処理で __着手__ を行った __直後__ |

### まとめ

__上記__ から、__いずれの場合__ でも、__`play`__ メソッドと __`on_button_clicked`__ は __ゲームの開始時__ と、__AI__ が __着手を行った後__ に __ゲーム盤__ の __画像を描画する__ という、__同じ処理__ を __行う__ ことが __確認__ できました。下記は、__両者__ が __同じ処理を行う__ ことを __言葉で説明__ したものです。

* __`play`__ メソッドは、__繰り返し__ 処理の __中__ で、__着手__ を __行う前__ に __画像を描画__ する。そのため、__ゲーム開始時__ の __描画処理__ を __繰り返し処理__ の中で __行う__ ことが __できる__。一方、__最後__ の __AI の着手__ に対する __描画処理__ は __繰り返し__ 処理の __中__ で __行えない__ ので、__繰り返し__ 処理の __終了後__ に __その処理__ を __行う必要__ がある
* __`on_button_clicked`__ は、__繰り返し__ 処理の __中__ で、__着手__ を行った __直後__ に __画像を描画__ する。そのため、__着手__ に対する __描画処理__ を __繰り返し処理__ の中で __行う__ ことが __できる__。一方、__ゲーム開始時__ の __描画処理__ は __繰り返し__ 処理の __中__ で __行えない__ ので、__繰り返し__ 処理の __前__ に __その処理__ を __行う必要__ がある

## `play_common` の定義

__上記の考察__ から、__`play`__ メソッド と __`on_button_clicked`__ が __行う処理__ の __違い__ は、__`play`__ メソッドが __繰り返し__ 処理の __前__ に __行う__「__乱数の種__ に __関する処理__」と「__Figure__ と __リセットボタン__ に __関する処理__」__だけ__ である事が __わかりました__。

そこで、__両者__ で __共通する処理__ を行う __メソッドを定義__ することにします。その __メソッドの名前__ は、__共通__ するという __意味__ を表す __common__ という __英単語__ を使って、__`play_common`__ という __名前にする__ ことにします。

### どちらの処理を `play_common` に記述すべきか

次に、__`play_common`__ に __記述__ する __プログラム__ を、__`play`__ メソッドと __`on_button_clicked`__ の __どちら__ の __プログラム__ から __抜き出し__ て __記述__ するかを __考える必要__ があります。__上記__ で示したように、どちらも __同じ処理を行う__ のだから、__どちらでも良い__ のではないかと __思う人__ が __いるかもしれません__ が、__そうではありません__。その理由について少し考えてみて下さい。

__`play`__ メソッドは、__`gui`__ に __`False`__ が __代入__ されて __実行__ される __場合__ が __あります__。一方、__`on_button_clicked`__ は、__`gui`__ に __`True`__ が __代入__ された場合に __イベントハンドラ__ として __登録__ されるため、__`gui`__ に __`True`__ が __代入__ されている場合 __のみ実行__ されます。そのため、__`play`__ メソッドと __`on_button_clicked`__ の __両方__ から __`play_common`__ メソッドを呼び出して __利用する場合__ は、__`play`__ メソッドの __内容を記述__ しなければ、__`play`__ メソッドで __`gui`__ に __`False`__ が __代入__ されている場合の処理に __対応できない__ という __問題が発生__ します。

そこで、__`play_common`__ を、__`play`__ メソッドから 「__乱数の種__ に __関する処理__」と「__Figure__ と __リセットボタン__ に __関する処理__」__以外__ の __処理__ を __抜き出した__、__下記__ のプログラムのように __定義__ します。__`play_common`__ の __仮引数__ は、__とりあえず `play`__ メソッドと __同じものにする__ ことにしました。その __理由__ は __この後__ で __説明__  します。

```python
from marubatsu import Marubatsu
import matplotlib.pyplot as plt
import japanize_matplotlib
import ipywidgets as widgets
import math

def play_common(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
    # 〇×ゲームを再起動する
    self.restart()

    # ゲームの決着がついていない間繰り返す
    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)
                # 手番を人間が担当する場合は、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)
        else:
            print(self)
            
    return self.status
    
Marubatsu.play_common = play_common
```

### `play_common` の不必要な仮引数の削除

__上記__ のプログラムを __VSCode__ に __記述__ すると、__関数の定義__ が、__下図__ のように __表示__ されます。__VSCode__ では、__仮引数__ が __その関数__ の __ブロックの中__ で一度も __利用されていない__ 場合は、__薄い色の文字__ で __表示__ されます。__下図__ では、__`seed`__ と __`size`__  が __薄い色で表示__ されます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/9f93e138-717f-4cfd-f216-6799bb9823ea.png" width="600">

従って、__`play_common`__ の __仮引数 `seed`__ と __`size`__ は __必要がない__ ことが __わかります__。__実際__ に、__VSCode__ で __Ctrl + f__ を押して表示される __検索機能__ を使って、__`seed`__ と __`size`__ が __`play_common`__ の __ブロックの中__ で一度も __記述されていない__ ことを __確認__ して下さい。

__下記__ のプログラムは、__仮引数 `seed`__ と __`size`__ を __削除__ した __`play_common`__ の __定義__ です。

```python
def play_common(self, ai, params=[{}, {}], verbose=True, gui=False):
元と同じなので省略
    
Marubatsu.play_common = play_common
```

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

```python
def play_common(self, ai, params=[{}, {}], verbose=True, gui=False):
    # 〇×ゲームを再起動する
    self.restart()

    # ゲームの決着がついていない間繰り返す
    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)
                # 手番を人間が担当する場合は、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)
        else:
            print(self)
            
    return self.status
    
Marubatsu.play_common = play_common
```
</details>

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

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

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

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

次に、__下記__ のプログラムのように、__`play`__ メソッドと __`on_button_clicked`__ から、__共通する処理__ を __削除__ し、__代わり__ に __`play_common`__ を __呼び出す__ ように __修正__ します。


* __6 行目__：__`on_button_clicked`__ に、__`play_common`__ メソッドを __呼び出す処理のみ__ を __記述__ する
* __10 行目__：__ゲーム__ の __決着がついていない間__ 繰り返す __処理を削除__ し、__return 文__ で、__`play_common`__ メソッドの __返り値__ を __返す処理__ を __記述する__

__`play`__ メソッドの場合は、__勝敗結果__ を __返す__ 必要がある[^1]ので、__return 文__ で __`play_common`__ の __返り値__ を __返す必要がある__ 点に __注意__ して下さい。__イベントハンドラ__ は __返り値__ を __返す必要がない__ ので、__`on_button_clicked`__ では、__return 文__ を __記述する必要__ は __ありません__。

```python
 1  def play(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
元と同じなので省略
 2      # リセットボタンのイベントハンドラを定義する
 3      def on_button_clicked(b):
 4          # この下に記述されていた処理を削除する
 5   
 6          self.play_common(ai=ai, params=params, verbose=verbose, gui=gui)           
元と同じなので省略         
 7
 8    # この下に記述されていた、ゲームの決着がついていない間繰り返す処理以降を削除する
 9  
10      return self.play_common(ai=ai, params=params, verbose=verbose, gui=gui)
11    
12  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)
    
    # リセットボタンを配置する
    button = widgets.Button(description="リセット")
    display(button)
    
    # リセットボタンのイベントハンドラを定義する
    def on_button_clicked(b):
        self.play_common(ai=ai, 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)

                # 現在の手番を表す ai のインデックスを計算する
                index = 0 if self.turn == Marubatsu.CIRCLE else 1
                # ゲームの決着がついていない場合に、ai が着手を行うかどうかを判定する
                if self.status == Marubatsu.PLAYING and ai[index] is not None:                
                    x, y = ai[index](self, **params[index])
                    self.move(x, y) 
                    self.draw_board(ax)
                   
        # fig の画像にマウスを押した際のイベントハンドラを結び付ける
        fig.canvas.mpl_connect("button_press_event", on_mouse_down)     

    return self.play_common(ai=ai, params=params, verbose=verbose, gui=gui)
    
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_button_clicked(b):
-       self.restart()       
-       self.draw_board(ax)  
        
        # 現在の手番を表す ai のインデックスを計算する
-       index = 0 if self.turn == Marubatsu.CIRCLE else 1
        # ゲームの決着がついていない場合に、ai が着手を行うかどうかを判定する
-       if self.status == Marubatsu.PLAYING and ai[index] is not None:                
-           x, y = ai[index](self, **params[index])
-           self.move(x, y) 
-           self.draw_board(ax)
+       self.play_common(ai=ai, params=params, verbose=verbose, gui=gui)           
元と同じなので省略         

    # ゲームの決着がついていない間繰り返す
-   while self.status == Marubatsu.PLAYING:
-   非常に長いのでこの間の部分は省略  
-   return self.status   
+   return self.play_common(ai=ai, params=params, verbose=verbose, gui=gui)
    
Marubatsu.play = play
```
</details>

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

```python
%matplotlib widget
from ai import ai2
mb = Marubatsu()

mb.play(ai=[ai2, ai2], gui=True);
```

実行結果（下記のエラーメッセージの下に表示される画像は省略します）

```
略
Cell In[3], line 45
     42     # fig の画像にマウスを押した際のイベントハンドラを結び付ける
     43     fig.canvas.mpl_connect("button_press_event", on_mouse_down)     
---> 45 return self.play_common(ai=ai, params=params, verbose=verbose, gui=gui)

Cell In[2], line 49
     47 if verbose:
     48     if gui:
---> 49         self.draw_board(ax)
     50     else:
     51         print(self)

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

### エラーの検証と修正

__エラーメッセージ__ から、__`ax`__  が __定義されていない__（not defined）ことが __わかります__。また、__エラーが発生__ している __`self.draw_board(ax)`__ の __行__ は、__`---> 45 return self.play_common(ai=ai, params=params, verbose=verbose, gui=gui)`__ という __メッセージ__ から、__`play_common` の中__ で __実行__ されていることが __わかります__。

従って、__このエラー__ は、__`play_common` の中__ で、__`ax`__ が __定義されていない__ ことが __原因__ です。

:::note
__上記__ の __名前が定義されていない__ という __エラー__ は、[以前の記事](https://qiita.com/ysgeso/items/c91d5373753253d98f23#draw_board-%E3%81%B8%E3%81%AE%E4%BB%AE%E5%BC%95%E6%95%B0-ax-%E3%81%AE%E8%BF%BD%E5%8A%A0)で説明したように、__下図__ のように、__VSCode__ の __`play_common`__ の __ブロック内__ のプログラムの __`ax`__ の部分に __オレンジ色__ の __波線__ が __表示__ されることから __気づく__ ことも __できます__。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/45b052b9-6104-57ae-a645-83d84fb5e8ac.png" width="200">
:::

そこで、__下記__ のプログラムのように、__`play_common`__ に __仮引数 `ax`__ を __追加__ します。なお、__通常の仮引数__ は、__デフォルト引数より前__ に __記述__ する __必要がある__ 点に __注意__ して下さい。

```python
def play_common(self, ai, ax, params=[{}, {}], verbose=True, gui=False):
元と同じなので省略
    
Marubatsu.play_common = play_common
```

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

```python
def play_common(self, ai, ax, params=[{}, {}], verbose=True, gui=False):
    # 〇×ゲームを再起動する
    self.restart()

    # ゲームの決着がついていない間繰り返す
    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)
                # 手番を人間が担当する場合は、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)
        else:
            print(self)
            
    return self.status
    
Marubatsu.play_common = play_common
```
</details>

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

```diff_python
-def play_common(self, ai, params=[{}, {}], verbose=True, gui=False):
+def play_common(self, ai, ax, params=[{}, {}], verbose=True, gui=False):
元と同じなので省略
    
Marubatsu.play_common = play_common
```
</details>

次に、__下記__ のプログラムの __4、7 行目__ のように、__`play`__ メソッドと __`on_button_clicked`__ 内で __`play_common`__ を __呼び出す際__ に、__実引数__ に __`ax=ax`__ を __記述__ するように __修正__ します。

```python
1  def play(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
元と同じなので省略
2      # リセットボタンのイベントハンドラを定義する
3      def on_button_clicked(b):
4          self.play_common(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)           
5         
元と同じなので省略 
6
7      return self.play_common(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, params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
    # seed が None でない場合は、seed を乱数の種として設定する
    if seed is not None:
        random.seed(seed)
    
    # リセットボタンを配置する
    button = widgets.Button(description="リセット")
    display(button)
    
    # リセットボタンのイベントハンドラを定義する
    def on_button_clicked(b):
        self.play_common(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)

                # 現在の手番を表す ai のインデックスを計算する
                index = 0 if self.turn == Marubatsu.CIRCLE else 1
                # ゲームの決着がついていない場合に、ai が着手を行うかどうかを判定する
                if self.status == Marubatsu.PLAYING and ai[index] is not None:                
                    x, y = ai[index](self, **params[index])
                    self.move(x, y) 
                    self.draw_board(ax)
                   
        # fig の画像にマウスを押した際のイベントハンドラを結び付ける
        fig.canvas.mpl_connect("button_press_event", on_mouse_down)     

    return self.play_common(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, params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
元と同じなので省略
    # リセットボタンのイベントハンドラを定義する
    def on_button_clicked(b):
-       self.play_common(ai=ai, params=params, verbose=verbose, gui=gui)           
+       self.play_common(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)           
         
元と同じなので省略 

-   return self.play_common(ai=ai, params=params, verbose=verbose, gui=gui)
+   return self.play_common(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
    
Marubatsu.play = play
```
</details>

__上記__ の __修正後__ に、__下記__ のプログラムで __`play`__ メソッドを __実行__ し、__正しく__ プログラムが __動作する__ ことを __確認__ して下さい。また、__人間__ VS __人間__、__AI__ VS __人間__、__人間__ VS __AI__ でも __正しく__ プログラムが __動作する__ ことを __確認__ して下さい。

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

# `play` メソッドと `on_mouse_down` の処理の統合

次に、__`play`__ メソッドと __`on_mouse_down`__ の __処理__ を __統合__ することにします。

## `play` メソッドと `on_mouse_down` の処理の共通点

__`on_mouse_down`__ が __行う処理__ を __箇条書き__ にすると __以下__ のようになります。

1. __マウス__ が __押されたマス__ に __着手__ を行う
2. 着手後の __ゲーム盤__ の __画像を描画__ する
3. __ゲーム中__ で __手番__ が __AI の場合__ は、__AI の着手__ を行い、__画像を描画__ する

そのように思えないかもしれませんが、__上記__ の __手順 2、3__ の __処理__ は、__下記__ の __`play`__ メソッドの __手順 5、6__ で __置き換える__ ことが __できます__。その理由について少し考えてみて下さい。

1. __乱数の種__ に __関する処理__ を行う
2. __ゲーム__ を __リセット__ する
3. __Figure__ に __関する処理__ を行う
4. __リセットボタン__ に __関する処理__ を行う
5. __繰り返し処理__ によって __決着がつくまで__ の間 __以下の処理__ を行う
    1. __ゲーム盤の画像__ を __描画__ する 
    2. __現在の手番__ が __AI の場合__ は __AI の着手__ を __選択__ する
    3. __現在の手番__ が __人間 の場合__ は、__関数の処理__ を __終了__ して __イベントループ__ を __再開__ する
6. __繰り返しの処理__ の __終了後__ に __ゲーム盤の画像__ を __描画__ する

__下図左__ は __`on_mouse_down`__ の、__下図右__ は __`on_mouse_down`__ の __手順 2、3__ を __`play`__ メソッドの __手順 5、6__ で __置き換え__ た __フローチャート__ です。__緑__ の部分が __`on_mouse_down`__ の、__水色__ の部分が __`play`__ メソッドの __手順__ であることを表します。__このフローチャート__ を使って、__両者__ が __同じ処理を行う__ ことを __示す__ ことにします。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/b21b3463-0de1-6a12-ff3a-7687776ef5a1.png" width="600">

__`on_mouse_down`__ が __実行された__ ということは、__〇__ か __×__ の __いずれか__ を __人間が担当__ しているということなので、__AI__ VS __AI 以外__ の __組み合わせ__ の対戦で __行われる処理__ を __検証__ します。

### __人間__ VS __人間__ の場合

__`on_mouse_down`__ の __手順 1__ の __後__ は、__以下__ の __2 種類の状況__ が __考えられる__ ので、__それぞれ__ の __処理の流れ__ が __同じ__ であることを __示します__。

* __手順 1__ で行われた __着手__ によって __決着__ が __つく__
* __手順 1__ で行われた __着手__ によって __決着__ が __つかない__

__下図__ は、__`on_mouse_down`__ の __手順 1__ によって、__ゲーム__ の __決着__ が __ついた場合__ の __処理__ を表す __フローチャート__ です。図から、__いずれの場合__ も __手順 1__ によって行われた __人間__ の __着手後__ の __ゲーム盤の描画__ を行い、__処理が終了__ します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/40314845-742f-986f-165d-c4a482e994c6.png" width="600">

__下図__ は、__`on_mouse_down`__ の __手順 1__ によって、__ゲーム__ の __決着__ が __ついていない場合__ の __処理__ を表す __フローチャート__ です。__下図左__ で行われる __処理__ は __上記__ と __同じ__ です。__下図右__ で行われる __処理__ は、__処理の流れ__ は __変わっています__ が、__人間__ の __着手後__ の __ゲーム盤の描画__ を行い、__処理が終了する__ という点では両者は __同じ処理__ を __行います__。 

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/e6bfe73f-3d27-20e0-7c93-744468be64ae.png" width="600">

__上記__ から、__人間__ VS __人間__ の場合は、両者は __同じ処理を行う__ ことが __確認__ できました。

### __人間__ VS __AI__ または、__AI__ VS __人間__ の場合

__人間__ VS __AI__ と __AI__ VS __人間__ の __いずれの場合__ も、__`on_mouse_down`__ の __手順 1__ は __人間の着手の処理__ である点に __変わりはありません__。そのため、__両者__ の __処理の手順__ を __まとめて説明します__。

__`on_mouse_down`__ の __手順 1__ の __後__ は、__以下__ の __3 種類の状況__ が __考えられる__ ので、__それぞれ__ の __処理の流れ__ が __同じ__ であることを __示します__。

* __手順 1__ で行われた __着手__ によって __決着__ が __つく__
* __手順 1__ で行われた __着手__ によって __決着__ が __つかない__
    * __次__ の __AI の着手__ によって __決着__ が __つく__
    * __次__ の __AI の着手__ によって __決着__ が __つかない__

__手順 1__ で行われた __着手__ によって __決着__ が __つく__ 場合に __行われる処理__ は、__人間__ VS __人間__ の場合と __全く同じ__ なので説明は __省略__ します。

__下図__ は、__`on_mouse_down`__ の __手順 1__ によって、__ゲーム__ の __決着__ が __つかず__、その __次__ の __AI の着手__ によって __決着__ が __ついた場合__ の __処理__ を表す __フローチャート__ です。__線の色__ は、__赤__、__水色__ の __順__ で __処理が行われる__ ことを __表します__。

図から、__いずれの場合__ も __下記__ の処理が __行われる__ ことが __わかります__。

* __人間__ の __着手後__ の __ゲーム盤の描画__ が __行われる__
* __AI__ が __着手__ を行い、__ゲーム盤の描画__ が __行われる__

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/a1148847-b26c-51c8-6ea5-88bf4f803cee.png" width="600">

__下図__ は、__`on_mouse_down`__ の __手順 1__ によって、__ゲーム__ の __決着__ が __つかず__、その __次__ の __AI の着手__ によって __決着__ が __つかなかった場合__ の __処理__ を表す __フローチャート__ です。__下図左__ で行われる __処理__ は __上記__ と __同じ__ です。__下図右__ で行われる __処理__ は、__処理の流れ__ は __変わっています__ が、__下記の処理__ が __行われる__ という点では、両者は __同じ処理__ を __行います__。なお、__下図右__ の __黄色__ の __ゲーム盤を描画する__ 処理は、__人間__ の __着手後__ と __AI__ の __着手後__ で __2 度実行__ されます。

* __人間__ の __着手後__ の __ゲーム盤の描画__ が __行われる__
* __AI__ が __着手__ を行い、__ゲーム盤の描画__ が __行われる__

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/cce66aca-9967-b153-b380-5901e1a59c06.png" width="600">

__上記__ から、__人間__ VS __AI__ と __AI__ VS __人間__ の場合も、__同じ処理を行う__ ことが __確認__ できました。

## `play_loop` の定義

上記から、__`on_mouse_down`__ の __手順 2、3__ と __`play`__ メソッドの __手順 5、6__ は __同じ処理__ を __行う__ ことが __確認__ できました。そこで、__`play_common`__ の場合と __同様__ に、__共通する処理__ である __`play`__ メソッドの __手順 5、6__ の処理を  __抜き出したメソッド__ を __定義__ する事にします。

__`play`__ メソッドの __手順 5__ の __処理__ は、__繰り返しの処理__ なので、__メソッド__ の __名前__ を __`play_loop`__ と __名付ける__ ことにし、__下記__ のプログラムのように __定義__ します。

```python
def play_loop(self, ai, ax, params=[{}, {}], verbose=True, gui=False):
    # ゲームの決着がついていない間繰り返す
    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)
                # 手番を人間が担当する場合は、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)
        else:
            print(self)
            
    return self.status   
    
Marubatsu.play_loop = play_loop
```

<details><summary><strong><code>play_common</code>との違い</strong></summary>

```diff_python
-def play_common(self, ai, ax, params=[{}, {}], verbose=True, gui=False):
+def play_loop(self, ai, ax, params=[{}, {}], verbose=True, gui=False):
    # 〇×ゲームを再起動する
-   self.restart()
元と同じなので省略
    
Marubatsu.play_loop = play_loop
```
</details>

## `on_mouse_down` の修正

次に、__`on_mouse_down`__ の、__マウス__ を __押したマス__ に __着手を行った後__ の __処理__ を、__下記__ のプログラムのように、__`play_loop`__ を __呼び出す__ プログラムに __修正__ します。

* __12 行目__：__AI__ が __着手を行う処理__ を __削除__ し、__`play_loop`__ を __呼び出す__ 処理を __記述__ する

```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                  # 次の手番の処理を行うメソッドを呼び出す
12                  self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
13                   
元と同じなので省略
14
15  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)
    
    # リセットボタンを配置する
    button = widgets.Button(description="リセット")
    display(button)
    
    # リセットボタンのイベントハンドラを定義する
    def on_button_clicked(b):
        self.play_common(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)     

    return self.play_common(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, params=[{}, {}], 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)

                # 現在の手番を表す ai のインデックスを計算する
-               index = 0 if self.turn == Marubatsu.CIRCLE else 1
                # ゲームの決着がついていない場合に、ai が着手を行うかどうかを判定する
-               if self.status == Marubatsu.PLAYING and ai[index] is not None:                
-                   x, y = ai[index](self, **params[index])
-                   self.move(x, y) 
-                   self.draw_board(ax)                   
                # 次の手番の処理を行うメソッドを呼び出す
+               self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
                   
元と同じなので省略

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

__上記__ の __修正後__ に、__下記__ のプログラムで __`play`__ メソッドを __実行__ し、__正しく__ プログラムが __動作する__ ことを __確認__ して下さい。また、__人間__ VS __人間__、__AI__ VS __人間__、__人間__ VS __AI__ でも __正しく__ プログラムが __動作する__ ことを __確認__ して下さい。

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

# `play_common` と `play_loop` の統合 

ここまでで、__`play`__、__`on_button_clicked`__、__`on_mouse_down`__ の __処理__ の __統合__ を __行うため__ に、__`play_loop`__ と __`play_common`__ の __2 種類__ の __メソッド__ を __定義__ しましたが、この __2 つのメソッド__ で行われる __処理の違い__ は下記の点だけなので、__統合__ したほうが良いでしょう。

* __最初__ に __`self.restart()`__ を __実行__ して __ゲーム__ を __リセットするか__ どうか

従って、__`play_common`__ の中で、__`play`__ メソッドの __手順 5、6__ の処理を __下記__ のプログラムの __6 行目__ のように __`play_loop`__ を __呼び出す__ ように __修正__ することが __できます__。

```python
 1  def play_common(self, ai, ax, params=[{}, {}], verbose=True, gui=False):
 2      # 〇×ゲームを再起動する
 3      self.restart()
 4
 5      # 決着がつくまでの繰り返し処理を行うメソッドを呼び出す
 6      return self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
 7
 8  Marubatsu.play_common = play_common
```

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

```python
def play_common(self, ai, ax, params=[{}, {}], verbose=True, gui=False):
    # 〇×ゲームを再起動する
    self.restart()

    # 決着がつくまでの繰り返し処理を行うメソッドを呼び出す
    return self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)

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

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

```diff_python
def play_common(self, ai, ax, params=[{}, {}], verbose=True, gui=False):
    # 〇×ゲームを再起動する
    self.restart()

    # 決着がつくまでの繰り返し処理を行うメソッドを呼び出す
+   self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
-   # ゲームの決着がついていない間繰り返す
-   while self.status == Marubatsu.PLAYING:
-   以下非常に長いので省略します

    # 決着がついたので、ゲーム盤を表示する
    if verbose:
        if gui:
            self.draw_board(ax)
        else:
            print(self)
            
    return self.status
    
Marubatsu.play_common = play_common
```
</details>

__上記__ の __修正後__ に、__下記__ のプログラムで __`play`__ メソッドを __実行__ し、__正しく__ プログラムが __動作する__ ことを __確認__ して下さい。また、__人間__ VS __人間__、__AI__ VS __人間__、__人間__ VS __AI__ でも __正しく__ プログラムが __動作する__ ことを __確認__ して下さい。

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

## `play_common` の削除

__`play_common`__ が __行う処理__ は、__`self.restart`__ と __`play_loop`__ を __呼び出す__ という __処理だけ__ なので、わざわざ __メソッドとして定義__ する __必要はない__ と __思う人__ が __いるかもしれません__。

そのように感じた人は、__下記__ のプログラムの __5 ~ 7、9 ~ 11 行目__ のように、__`play`__ メソッドと __`on_button_clicked`__ 内で __`play_common`__ を __呼び出す処理__ を、__`self.restart`__ と __`play_loop`__ を呼び出す処理に __修正__ すると良いでしょう。

```python
 1  def play(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
元と同じなので省略
 2      # リセットボタンのイベントハンドラを定義する
 3      def on_button_clicked(b):
 4          # ゲームをリセットする
 5          self.restart()
 6          # 決着がつくまで繰り返す処理を呼び出す
 7          self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)           
元と同じなので省略
 8      # ゲームをリセットする
 9      self.restart()
10      # 決着がつくまで繰り返す処理を呼び出す
11      return self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
12    
13  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)
    
    # リセットボタンを配置する
    button = widgets.Button(description="リセット")
    display(button)
    
    # リセットボタンのイベントハンドラを定義する
    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)     

    # ゲームをリセットする
    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, params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
元と同じなので省略
    # リセットボタンのイベントハンドラを定義する
    def on_button_clicked(b):
        # ゲームをリセットする
+       self.restart()
        # 決着がつくまで繰り返す処理を呼び出す
-       self.play_common(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)           
+       self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)           
元と同じなので省略
    # ゲームをリセットする
+   self.restart()
    # 決着がつくまで繰り返す処理を呼び出す
-   return self.play_common(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
+   return self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
    
Marubatsu.play = play
```
</details>

__本記事__ では __上記__ のプログラムを __採用__ しますが、__修正前__ と __修正後__ のプログラムで、__どちらが良いか__ については __一概には言えません__。__わかりやすい__ と __思ったほう__ を __採用して下さい__。なお、__`play_common`__ はもう __利用しない__ ので、__marubatsu.py__ には __記述しません__。

__上記__ の __修正後__ に、__下記__ のプログラムで __`play`__ メソッドを __実行__ し、__正しく__ プログラムが __動作する__ ことを __確認__ して下さい。また、__人間__ VS __人間__、__AI__ VS __人間__、__人間__ VS __AI__ でも __正しく__ プログラムが __動作する__ ことを __確認__ して下さい。

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

# 今回の記事のまとめ

今回の記事では、__`play`__、__`on_button_clicked`__、__`on_mouse_down`__ の __処理__ の __統合__ を行いました。このような __処理の統合__ を __行わなくても__、__リセットボタン__ の __処理を記述__ することが __できます__ が、__統合したほう__ が、__プログラム__ を __簡潔に記述できる__ ようになります。__統合前__ と __統合後__ のプログラムを __見比べて__、そのことを __確認__ してみて下さい。

また、__同じ処理__ をプログラムの __複数の個所__ に __記述する__ ことは、__避けたほうが良い__ ので、このような __統合ができる場合__ は、__しておいたほうが良い__ でしょう。

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

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

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

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

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

# 次回の記事

https://qiita.com/ysgeso/items/51842efc6193ed456ebc

[^1]:__`play`__ メソッドの __返り値__ は、__`ai_match`__ を使って __通算成績__ を __計算__ する際などで __必要__ になります