Pythonで〇×ゲームのAIを一から作成する　その67　画像によるマークの描画

# 目次と前回の記事

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

https://qiita.com/ysgeso/items/442db53b5b7e22c1000c

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

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

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

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

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

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

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

# 図形によるマークの描画

前回の記事では、__文字列__ による __マークの描画__ の方法と、__欠点__ について __説明__ しました。

今回の記事では、__図形__ を使って __〇__ や __×__ を __描画__ するという __方法__ を紹介します。この方法は、文字列を描画する方法と比べて、__下記__ のような __利点__ が __あります__。

* __マス__ の __真ん中__ に __正確に描画__ することが __簡単__ に __できる__
* __枠の太さ__ を __自由に調整__ することが __できる__
* __画像の大きさ__ を __変更__ しても、__図形__ の __大きさ__ や __位置__ を __調整__ する __必要__ が __ない__

なお、__図形の大きさ__ を __試行錯誤__ で __調整__ する __必要__ があるという __点__ は、__文字列__ でも __図形__ でも __変わりません__。

## 図形による 〇 の描画

まず、__(0, 0)__ のマスに __円__ の __図形__ を __描画__ するプログラムを __記述__ することにします。[前回の記事](https://qiita.com/ysgeso/items/ab2770693c947c108dd0#%E5%86%86%E3%81%AE%E4%BD%9C%E6%88%90%E3%81%A8%E7%99%BB%E9%8C%B2)で説明したように、__円__ の __図形の描画__ は、__円__ の __図形__ 表す __Artist__ を __patches__ モジュールの __`Circle`__ メソッドで __作成__ し、__Axes__ の __`add_artist`__ メソッドで __登録__ することで行います。その際に、__円__ の __中心の座標__ と、__半径__ を __指定__ する __必要__ が __あります__。それらをどのように設定すればよいかについて少し考えてみて下さい。

下記は、__(0, 0)__ のマスに __〇__ を __配置__ した __ゲーム盤__ の __画像__ の __一例__ です。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/5440b3e7-3afb-6572-a107-965ce4a005e6.png" width="150">

__図__ から、__(0, 0)__ の __マス__ の __中央__ に __〇 を描画__ するためには、__円__ の __中心の座標__ を __(0.5, 0.5)<sub>A</sub>__[^1] に __すれば良い__ ことが __わかります__。また、__そのように設定__ することで、__円__ を __必ず (0, 0)__ のマスの __中央__ に __描画__ できることが __保証__ されます。この点は、__試行錯誤__ で __〇__ を __描画する位置__ を __探す必要__ がある、__文字列__ による __〇__ の __描画__ に対する __大きな利点__ です。

また、__図__ から、描画する __円の半径__ を __0.5 以下__ にする __必要__ がある事も __わかります__。__円の半径__ については __試行錯誤__ で __決める必要__ があるので、__とりあえず__、下記のプログラムのように、__0.3__ を __設定__ して __描画してみる__ ことにします。

```python
from marubatsu import Marubatsu
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import japanize_matplotlib

mb = Marubatsu()
ax = mb.draw_board()
circle = patches.Circle([0.5, 0.5], 0.3)
ax.add_artist(circle)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/95bddf73-7d06-fe07-74b2-451f0b6f9964.png" width="150">

__実行結果__ から、__円__ が __塗りつぶされて__ おり、__枠__ も __描画されていない__ ことが __わかります__。また、__円の大きさ__ が __若干小さい__ 気がするので、__下記__ のように __修正__ することにします。

* __塗りつぶさない__ ようにするために、__`fill=False`__ を __記述__ する
* __円__ の __半径__ を __0.35__ に __増やす__

なお、__枠__ と __塗りつぶし__ の __設定方法__ について忘れた方は、[以前の記事](https://qiita.com/ysgeso/items/ab2770693c947c108dd0#%E6%9E%A0%E3%81%A8%E5%A1%97%E3%82%8A%E3%81%A4%E3%81%B6%E3%81%97%E3%81%AE%E8%A8%AD%E5%AE%9A%E6%96%B9%E6%B3%95)を参照して下さい。

```python
ax = mb.draw_board()
circle = patches.Circle([0.5, 0.5], 0.35, fill=False)
ax.add_artist(circle)
```

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

```diff_python
ax = mb.draw_board()
-circle = patches.Circle([0.5, 0.5], 0.3)
+circle = patches.Circle([0.5, 0.5], 0.35, fill=False)
ax.add_artist(circle)
```
</details>


実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/18050611-c094-76fa-6e73-ff8d556f20d4.png" width="150">

__実行結果__ から、__枠線__ が __細い__ 気がするので、__`lw=2`__ を __記述__ して __太く__ してみます。

```python
ax = mb.draw_board()
circle = patches.Circle([0.5, 0.5], 0.35, fill=False, lw=2)
ax.add_artist(circle)
```

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

```diff_python
ax = mb.draw_board()
-circle = patches.Circle([0.5, 0.5], 0.35, fill=False)
+circle = patches.Circle([0.5, 0.5], 0.35, fill=False, lw=2)
ax.add_artist(circle)
```
</details>

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/ae3bddb8-6319-b9fb-b8f6-821c994db4c5.png" width="150">

__上記__ の __実行結果__ で __特に問題はない__ と思いますので、__本記事__ では、__上記の設定__ で __〇__ を __描画__ することにします。この設定が気に入らない人は、自由に変更して下さい。

また、__〇__ を __図形で描画__ した場合は、__[文字列で描画](https://qiita.com/ysgeso/items/442db53b5b7e22c1000c#%E6%96%87%E5%AD%97%E5%88%97%E3%81%AB%E3%82%88%E3%82%8B-%E3%80%87-%E3%81%AE%E6%8F%8F%E7%94%BB%E3%81%AE%E5%95%8F%E9%A1%8C%E7%82%B9)__ した場合と __異なり__、__下記__ のプログラムのように、__画像の大きさ__ を __変えて__ も __〇__ が __マスの真ん中__ に __描画される__ という、__マスの中__ の __配置__ が __変わらない__ という __利点__ が __得られます__。

```python
ax = mb.draw_board(size=5)
circle = patches.Circle([0.5, 0.5], 0.35, fill=False, lw=2)
ax.add_artist(circle)
```

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

```diff_python
-ax = mb.draw_board()
+ax = mb.draw_board(size=5)
circle = patches.Circle([0.5, 0.5], 0.35, fill=False, lw=2)
ax.add_artist(circle)
```
</details>

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/fd7abde4-dfe5-0096-fc2b-d8a89a174423.png" width="250">

### 任意のマスに 〇 を描画する方法

__上記__ では、__(0, 0)__ のマスに __〇 を描画__ しましたが、__実際__ には、__任意のマス__ に __〇 を描画__ する __必要__ が __あります__。__いずれのマス__ に __〇 を描画__ する場合でも、__そのマス__ の __中心の座標__ がわかれば、マスの __真ん中__ に __〇 を描画__ することが __できます__。

__(x, y)__ の __マス__ の __中心の座標__ は、上図を見れば __(x + 0.5, y + 0.5)<sub>A</sub>__ であることが簡単にわかるので、例えば __(2, 1)__ のマスに __〇 を描画__ するプログラムは __下記__ のようになります。

```python
x = 2
y = 1

ax = mb.draw_board()
circle = patches.Circle([x + 0.5, y + 0.5], 0.35, fill=False, lw=2)
ax.add_artist(circle)
```

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

```diff_python
+x = 2
+y = 1

ax = mb.draw_board()
-circle = patches.Circle([0.5, 0.5], 0.35, fill=False, lw=2)
+circle = patches.Circle([x + 0.5, y + 0.5], 0.35, fill=False, lw=2)
ax.add_artist(circle)
```
</details>


実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/9c9b802c-828a-78b0-3a6e-31362dd0c3a5.png" width="150">

__実行結果__ から、__(2, 1)__ のマスの __真ん中__ に __〇__ を __描画できている__ ことが __確認__ できます。

### `draw_circle` の定義

__(x, y)__ のマスに __〇 を描画__ する __たび__ に、__上記__ のようなプログラムを __毎回記述__ するのは __面倒__ なので、__任意のマス__ に __〇 を描画__ する、__下記__ のような __メソッドを定義__ する事にします。

* __名前__：__〇__（circle）を __描画__（draw）するので、__`draw_circle`__ という __名前__ にする
* __処理__：__（x, y）__ のマスに __〇 を描画__ する
* __入力__：__`x`__ と __`y`__ という __仮引数__ に、__〇 を描画__ する __マスの座標__ を __代入__ する。また、__描画__ を __行うため__ には __Axes__ が __必要になる__ ので、__`ax`__ という __仮引数__ に __Axes__ を __代入__ する
* __出力__：なし

下記は、__`draw_circle`__ の __定義__ を行うプログラムです。

```python
def draw_circle(self, ax, x, y):
    circle = patches.Circle([x + 0.5, y + 0.5], 0.35, fill=False, lw=2)
    ax.add_artist(circle)

Marubatsu.draw_circle = draw_circle
```

下記は、__`draw_circle`__ を使って、__すべてのマス__ に __〇 を描画__ するプログラムです。__実行結果__ から __正しく動作__ することが __確認__ できます。

* __1 行目__：__ゲーム盤の枠__ を __描画__ する
* __2 ~ 4 行目__：__2 重__ の __for 文__ を使って、__ゲーム盤__ の __すべてのマス__ に対して、__4 行目__ で __〇 を描画__ する

```python
ax = mb.draw_board()
for x in range(3):
    for y in range(3):
        mb.draw_circle(ax, x, y)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/99cb4f08-b7fd-84ed-6013-45ea51192cb6.png" width="150">

## 図形による × の描画

次に、__(0, 0)__ のマスに __×__ を __図形で描画__ するプログラムを __記述__ することにします。__×__ の __図形__ は __2 本__ の __線__ によって __構成__ されるので、__`plot`__ メソッドで __描画__ することが __できます__。__2 本__ の __線__ の __両端の座標__ をどのように設定すればよいかについて少し考えてみて下さい。

下記は、__(0, 0)__ のマスに __× を配置__ した __ゲーム盤__ の __画像__ の __一例__ です。先程 __〇__ を __半径__ が __0.35__ の __円で描画__ したので、__×__ を __同じ大きさ__ で __描画__ するために、それぞれの __線__ の __両端__ の __x 座標__ と __y 座標__ を、__マスの中央__ の __(0.5, 0.5)<sub>A</sub>__ から __0.35__ だけ __増減した値__ に __設定__ しました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/412d870f-e022-f0ea-25bc-83d83765b2e0.png" width="200">

__下記__ は 、__上図を元__ に、__(0, 0)__ のマスに __× を描画__ するプログラムです。__`c = "k"`__ によって __線の色__ を __黒__ に、__`lw=2`__ によって、__線の太さ__ を __〇 の図形__ と __同じ 2__ に __設定__ しました。

```python
ax = mb.draw_board()
ax.plot([0.15, 0.85], [0.15, 0.85], c="k", lw="2")
ax.plot([0.15, 0.85], [0.85, 0.15], c="k", lw="2")
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/dfb4c8c2-ca77-b01a-9750-42455a71422c.png" width="150">

__実行結果__ から、__うまく描画__ できていることが __確認__ できます。

### `draw_cross` の定義

__`draw_circle`__ と __同様__ の __考え方__ で、__任意のマス__ に __× を描画__ する __`draw_cross`__ メソッドを、__下記__ のプログラムのように __定義__ することが __できます__。

```python
def draw_cross(self, ax, x, y):
    ax.plot([x + 0.15, x + 0.85], [y + 0.15, y + 0.85], c="k", lw="2")
    ax.plot([x + 0.15, x + 0.85], [y + 0.85, y + 0.15], c="k", lw="2")

Marubatsu.draw_cross = draw_cross
```

__下記__ は、__`draw_cross`__ を使って、__すべてのマス__ に __× を描画__ するプログラムです。__実行結果__ から __正しく動作__ することが __確認__ できます。

```python
ax = mb.draw_board()
for x in range(3):
    for y in range(3):
        mb.draw_cross(ax, x, y)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/ddc5d96e-b6b3-4457-4293-ed9ded1aec0c.png" width="150">

## ゲーム盤に配置された 〇 と × の描画

__任意のマス__ に __〇__ と __×__ を __描画できる__ ようになったので、__`draw_board`__ を __ゲーム盤__ に __配置__ された __マークの描画__ を __行うよう__ に __修正__ します。具体的には、__下記__ のプログラムのように、__2 重の for 文__ を使って __すべてのマス__ に対して、__〇 が配置__ されていれば __`draw_circle`__ を、__× が配置__ されていれば __`draw_cross`__ を呼び出して __マークを描画__ するプログラムを __追加__ します。 

* __3 ~ 8 行目__：__2 重__ の __for 文__ を使って、__ゲーム盤__ の __すべてのマス__ に対して処理を行う
* __5、6 行目__：__(x, y)__ に __〇 が配置__ されていれば __`draw_circle`__ で __〇 を描画__ する
* __7、8 行目__：__(x, y)__ に __× が配置__ されていれば __`draw_cross`__ で __× を描画__ する

```python
 1  def draw_board(self, size=3):
元と同じなので省略
 2      # ゲーム盤のマークを描画する
 3      for y in range(self.BOARD_SIZE):
 4          for x in range(self.BOARD_SIZE):
 5              if self.board[x][y] == Marubatsu.CIRCLE:
 6                  self.draw_circle(ax, x, y)
 7              elif self.board[x][y] == Marubatsu.CROSS:
 8                  self.draw_cross(ax, x, y)
 9
10      # ax を返り値として返す
12      return ax
13
14  Marubatsu.draw_board = draw_board
```

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

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

    # y 軸を反転させる
    ax.invert_yaxis()

    # ゲーム盤の枠を描画する
    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):
            if self.board[x][y] == Marubatsu.CIRCLE:
                self.draw_circle(ax, x, y)
            elif self.board[x][y] == Marubatsu.CROSS:
                self.draw_cross(ax, x, y)

    # ax を返り値として返す
    return ax

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

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

```diff_python
def draw_board(self, size=3):
元と同じなので省略
    # ゲーム盤のマークを描画する
+   for y in range(self.BOARD_SIZE):
+       for x in range(self.BOARD_SIZE):
+           if self.board[x][y] == Marubatsu.CIRCLE:
+               self.draw_circle(ax, x, y)
+           elif self.board[x][y] == Marubatsu.CROSS:
+               self.draw_cross(ax, x, y)

    # ax を返り値として返す
    return ax

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


__下記__ は、__いくつか__ の __マス__ に __着手__ を __行った後__ で __`draw_board`__ で __ゲーム盤を描画__ するプログラムです。__実行結果__ から、__正しく動作__ していることが __確認__ できます。

```python
mb.move(0, 0)
mb.move(1, 1)
mb.move(2, 1)
mb.draw_board()
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/f842ed5b-1775-ed6b-7bf7-6d71972f1a0c.png" width="150">

### 直前に着手したマークの表示色の変更

__ゲーム盤__ を __文字列で描画__ する場合は、__下記__ の __実行結果__ のように、__マーク__ を __大文字で表示__ することで、__直前に着手__ した __マーク__ を __示して__ いました。

```python
print(mb)
```

実行結果

```
Turn x
o..
.xO
...
```

__画像__ で __ゲーム盤__ を __描画__ する場合は、__大文字__ と __小文字__ で __区別__ することは __できない__ ので、__別の方法__ で __直前に着手__ した __マーク__ を __示す必要__ があります。どのような方法で示せばよいかについて、少し考えてみて下さい。

ぱっと思いつく方法として、__下記__ のような __方法__ があるでしょう。このうち、__マークの色__ を __変える__ 方法が __最も簡単__ なので、__本記事__ では __その方法を採用__ することにします。__マス__ の __塗りつぶしの色__ を __変える__ 方法や、他に __もっと良い方法__ を思いついた人は、時間と興味があれば実際に実装してみて下さい。

* __マークの色__ を __変える__
* __マス__ の __塗りつぶしの色__ を __変える__

__マークの色__ を __変える__ ためには、__マーク__ を __描画する処理__ を行う __`draw_circle`__ メソッドと、__`draw_cross`__ メソッドに、__マークの色__ を __代入__ する __仮引数を追加__ する __必要__ が __あります__。本記事では __その仮引数__ を __下記__ のように __設定__ することにします。

* __仮引数__ の __名前__ を __`color`__ とする
* __デフォルト値__ に __`"black"`__ を __設定__ した、__デフォルト引数__ とする

下記は、__`color`__ を __追加__ した __`draw_circle`__ と __`draw_cross`__ の __定義__ を行うプログラムです。

* __1、7 行目__：__デフォルト値__ を __`"black"`__ とする、__デフォルト引数 `color`__ を __追加__ する
* __2 行目__：__`ec=color`__ を記述して、__円の枠__ を __`color`__ で __指定した色__ で __描画__ する
* __8、9 行目__：__`c=color`__ を記述して、__線__ を __`color`__ で __指定した色__ で __描画__ する

```python
 1  def draw_circle(self, ax, x, y, color="black"):
 2      circle = patches.Circle([x + 0.5, y + 0.5], 0.35, ec=color, fill=False, lw=2)
 3      ax.add_artist(circle)
 4
 5  Marubatsu.draw_circle = draw_circle
 6
 7  def draw_cross(self, ax, x, y, color="black"):
 8      ax.plot([x + 0.15, x + 0.85], [y + 0.15, y + 0.85], c=color, lw="2")
 9      ax.plot([x + 0.15, x + 0.85], [y + 0.85, y + 0.15], c=color, lw="2")
10
11  Marubatsu.draw_cross = draw_cross
```

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

```python
def draw_circle(self, ax, x, y, color="black"):
    circle = patches.Circle([x + 0.5, y + 0.5], 0.35, ec=color, fill=False, lw=2)
    ax.add_artist(circle)

Marubatsu.draw_circle = draw_circle

def draw_cross(self, ax, x, y, color="black"):
    ax.plot([x + 0.15, x + 0.85], [y + 0.15, y + 0.85], c=color, lw="2")
    ax.plot([x + 0.15, x + 0.85], [y + 0.85, y + 0.15], c=color, lw="2")

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

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

```diff_python
-def draw_circle(self, ax, x, y):
+def draw_circle(self, ax, x, y, color="black"):
-   circle = patches.Circle([x + 0.5, y + 0.5], 0.35, fill=False, lw=2)
+   circle = patches.Circle([x + 0.5, y + 0.5], 0.35, ec=color, fill=False, lw=2)
    ax.add_artist(circle)

Marubatsu.draw_circle = draw_circle

-def draw_cross(self, ax, x, y):
+def draw_cross(self, ax, x, y, color="black"):
-   ax.plot([x + 0.15, x + 0.85], [y + 0.15, y + 0.85], lw="2")
+   ax.plot([x + 0.15, x + 0.85], [y + 0.15, y + 0.85], c=color, lw="2")
-   ax.plot([x + 0.15, x + 0.85], [y + 0.85, y + 0.15], lw="2")
+   ax.plot([x + 0.15, x + 0.85], [y + 0.85, y + 0.15], c=color, lw="2")

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

:::note
__`plot`__ メソッドで __線の色__ を __指定__ する際の __キーワード引数__ は、__`c`__ と __`color`__ の __どちら__ を使っても __かまいません__ が、__`Circle`__ メソッド の __キーワード引数__ に __`c`__ を __使う__ ことはで __きません__（__エラーが発生__ します）。おそらく、__枠線の色__ を表す __`ec`__ と __塗りつぶしの色__ を表す __`fc`__ との __区別がつきづらい__ からではないかと思います。
:::

次に、__`draw_board`__ を __下記__ のように __修正__ します。なお、__下記__ のプログラムでは、__直前に着手__ した __マークの色__ を __赤色__ に設定しました。他の色が良い人は自由に変更して下さい。

* __5 行目__：変数 __`color`__ に、__(x, y)__ が __直前に着手__ を行った __マスと同じ__ 場合は、__`"red"`__ を、__そうでなければ `"black"`__ を __代入__ する
* __7、9 行目__：__キーワード引数 `color=color`__ を __記述__ することで、__`color` に代入__ された __色__ で __マークを描画__ する

```python
 1  def draw_board(self, size=3):
元と同じなので省略
 2      # ゲーム盤のマークを描画する
 3      for y in range(self.BOARD_SIZE):
 4          for x in range(self.BOARD_SIZE):
 5              color = "red" if (x, y) == self.last_move else "black"
 6              if self.board[x][y] == Marubatsu.CIRCLE:
 7                  self.draw_circle(ax, x, y, color=color)
 8              elif self.board[x][y] == Marubatsu.CROSS:
 9                  self.draw_cross(ax, x, y, color=color)
10
11      # ax を返り値として返す
12      return ax
13
14  Marubatsu.draw_board = draw_board

```

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

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

    # y 軸を反転させる
    ax.invert_yaxis()

    # ゲーム盤の枠を描画する
    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"
            if self.board[x][y] == Marubatsu.CIRCLE:
                self.draw_circle(ax, x, y, color=color)
            elif self.board[x][y] == Marubatsu.CROSS:
                self.draw_cross(ax, x, y, color=color)

    # ax を返り値として返す
    return ax

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

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

```diff_python
def draw_board(self, size=3):
元と同じなので省略
    # ゲーム盤のマークを描画する
    for y in range(self.BOARD_SIZE):
        for x in range(self.BOARD_SIZE):
+           color = "red" if (x, y) == self.last_move else "black"
            if self.board[x][y] == Marubatsu.CIRCLE:
-               self.draw_circle(ax, x, y)
+               self.draw_circle(ax, x, y, color=color)
            elif self.board[x][y] == Marubatsu.CROSS:
-               self.draw_cross(ax, x, y)
+               self.draw_cross(ax, x, y, color=color)

    # ax を返り値として返す
    return ax

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

__下記__ のプログラムのように、__`draw_board`__ を実行すると、__直前に着手__ した __(2, 1)__ のマスの __〇__ が __赤く表示__ されることが __確認__ できます。

```python
mb.draw_board()
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/053ba080-2592-7a41-cf08-5e4f161f17b3.png" width="150">

### `draw_circle` と `draw_cross` の統合

__上記__ のプログラムでも __問題なくゲーム盤__ を __描画できます__ が、__同じような処理__ を行う、__`draw_circle`__ と __`draw_cross`__ を、__1 つ__ のメソッドに __まとめる__ ことで、__プログラム__ を __少し簡潔__ にすることが __できます__。

__具体的__ には、この __2 つの関数__ を __`draw_mark`__ という __メソッド__ に __まとめます__。その際に、__`draw_mark`__ に、__どのマーク__ を __描画するか__ を __代入__ する __仮引数__ を __追加__ する __必要__ が __あります__。そこで、下記のプログラムのように __仮引数 `mark`__ を __追加__ することにします。

* __1 行目__：__メソッド__ の __名前__ を __`draw_mark`__ に __修正__ し、__仮引数 `mark`__ を __追加__ する
* __2 ~ 4 行目__：__`mark`__ が __`Marubatsu.CIRCLE`__ の場合は、__〇 を描画__ する処理を __記述__ する
* __5 ~ 7 行目__：__`mark`__ が __`Marubatsu.CROSS`__ の場合は、__× を描画__ する処理を __記述__ する
```python
1  def draw_mark(self, ax, x, y, mark, color="black"):
2      if mark == Marubatsu.CIRCLE:
3          circle = patches.Circle([x + 0.5, y + 0.5], 0.35, ec=color, fill=False, lw=2)
4          ax.add_artist(circle)
5      elif mark == Marubatsu.CROSS:
6          ax.plot([x + 0.15, x + 0.85], [y + 0.15, y + 0.85], c=color, lw="2")
7          ax.plot([x + 0.15, x + 0.85], [y + 0.85, y + 0.15], c=color, lw="2")
8
9  Marubatsu.draw_mark = draw_mark
```

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

```python
def draw_mark(self, ax, x, y, mark, color="black"):
    if mark == Marubatsu.CIRCLE:
        circle = patches.Circle([x + 0.5, y + 0.5], 0.35, ec=color, fill=False, lw=2)
        ax.add_artist(circle)
    elif mark == Marubatsu.CROSS:
        ax.plot([x + 0.15, x + 0.85], [y + 0.15, y + 0.85], c=color, lw="2")
        ax.plot([x + 0.15, x + 0.85], [y + 0.85, y + 0.15], c=color, lw="2")

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

__`draw_mark`__ を __定義__ する事で、__`draw_board`__ を、__下記__ のように __少し簡潔__ に __記述できます__。

__6 行目__：__`draw_mark`__ の __実引数__ に、__(x, y)__ の __マスのマーク__ を表す __`self.board[x][y]`__ を __記述__ することで、__(x, y)__ の __マークを描画__ する

```python
 1  def draw_board(self, size=3):
元と同じなので省略
 2      # ゲーム盤のマークを描画する
 3      for y in range(self.BOARD_SIZE):
 4          for x in range(self.BOARD_SIZE):
 5              color = "red" if (x, y) == self.last_move else "black"
 6              self.draw_mark(ax, x, y, self.board[x][y], color)
 7
 8      # ax を返り値として返す
 9      return ax
10
11  Marubatsu.draw_board = draw_board
```

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

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

    # y 軸を反転させる
    ax.invert_yaxis()

    # ゲーム盤の枠を描画する
    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)


    # ax を返り値として返す
    return ax

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

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

```diff_python
def draw_board(self, size=3):
元と同じなので省略
    # ゲーム盤のマークを描画する
    for y in range(self.BOARD_SIZE):
        for x in range(self.BOARD_SIZE):
            color = "red" if (x, y) == self.last_move else "black"
-           if self.board[x][y] == Marubatsu.CIRCLE:
-               self.draw_circle(ax, x, y, color=color)
-           elif self.board[x][y] == Marubatsu.CROSS:
-               self.draw_cross(ax, x, y, color=color)
+           self.draw_mark(ax, x, y, self.board[x][y], color)

    # ax を返り値として返す
    return ax

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

なお、__(x, y)__ の __マス__ が __空の場合__ に、__`draw_mark`__ を __呼び出す__ のは __おかしい__ と __思うかもしれません__ が、__その場合__ は __以下の処理__ が __行われる__ ので __問題はありません__。

* __`draw_mark`__ の __仮引数 `mark`__ に __`Marubatsu.EMPTY`__ が __代入__ される
* __`draw_mark`__ の __if 文__ の __2 つの条件式__ は、__いずれも `False`__ になる
* 従って、__`draw_mark`__ は何の __処理__ も __行わない__

__下記__ は、__修正後__ の __`draw_board`__ を使って、__ゲーム盤を描画__ するプログラムです。__実行結果__ から __正しく動作__ することが __確認__ できます。

```python
mb.draw_board()
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/053ba080-2592-7a41-cf08-5e4f161f17b3.png" width="150">

なお、__以後__ は __`draw_circle`__ と __`draw_cross`__ はもう __使わない__ ので、__`marubatsu.py`__ には __それらのメソッド__ は __記述しません__。

### 静的メソッドとしての `draw_mark` の定義

__この部分__ の __内容__ は、__初心者__ には __わかりづらい__ と思いますので、__意味が分からない場合__ は __現時点__ では __読み飛ばしても構いません__。また、本記事では、__`draw_mark`__ メソッドの __定義__ の __直前の行__ に __`@classmethod`__ を __記述__ することにしますが、__`draw_mark`__ は __通常のメソッド__ と __同じ方法__ で __呼び出して利用__ することが __できる__ ので、当面は、__`@classmethod`__ が記述されていても、その存在を __気にする必要__ は __ありません__。

__下記__ の __`draw_mark`__ の __定義__ の __ブロックの中__ では、__`self`__ は __一度__ も __使われていません__。

```python
def draw_mark(self, ax, x, y, mark, color="black"):
    if mark == Marubatsu.CIRCLE:
        circle = patches.Circle([x + 0.5, y + 0.5], 0.35, ec=color, fill=False, lw=2)
        ax.add_artist(circle)
    elif mark == Marubatsu.CROSS:
        ax.plot([x + 0.15, x + 0.85], [y + 0.15, y + 0.85], c=color, lw="2")
        ax.plot([x + 0.15, x + 0.85], [y + 0.85, y + 0.15], c=color, lw="2")
        
Marubatsu.draw_mark = draw_mark
```

__このようなメソッド__ は、__仮引数__ から __`self` を削除__ することで、下記のプログラムのように、__通常の関数__ として __定義__ する事が __できます__。

```python
def draw_mark(ax, x, y, mark, color="black"):
    if mark == Marubatsu.CIRCLE:
        circle = patches.Circle([x + 0.5, y + 0.5], 0.35, ec=color, fill=False, lw=2)
        ax.add_artist(circle)
    elif mark == Marubatsu.CROSS:
        ax.plot([x + 0.15, x + 0.85], [y + 0.15, y + 0.85], c=color, lw="2")
        ax.plot([x + 0.15, x + 0.85], [y + 0.85, y + 0.15], c=color, lw="2")
```

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

```diff_python
-def draw_mark(self, ax, x, y, mark, color="black"):
+def draw_mark(ax, x, y, mark, color="black"):
    if mark == Marubatsu.CIRCLE:
        circle = patches.Circle([x + 0.5, y + 0.5], 0.35, ec=color, fill=False, lw=2)
        ax.add_artist(circle)
    elif mark == Marubatsu.CROSS:
        ax.plot([x + 0.15, x + 0.85], [y + 0.15, y + 0.85], c=color, lw="2")
        ax.plot([x + 0.15, x + 0.85], [y + 0.85, y + 0.15], c=color, lw="2")
        
-Marubatsu.draw_mark = draw_mark
```
</details>

しかし、__`draw_mark`__ は、__`Marubatsu`__ クラス __の中__ で __利用__ することを __想定__ して __定義__ したものなので、__通常の関数__ として __定義__ すると、__そのこと__ が __わかりづらく__ なってしまいます。

このような場合は、[以前の記事](https://qiita.com/ysgeso/items/ad5e7ddb071653fd2ac5#%E9%9D%99%E7%9A%84%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89)で __簡単に説明__ した、__静的メソッド__ として __`draw_mark`__ を __定義__ するという __方法__ があります。__静的メソッド__ は、__下記__ のプログラムのように、__メソッドの定義__ の __直前の行__ に、__`@staticmethod`__ という __デコレータ__ を記述し、__仮引数__ から __`self` を削除__ することで __定義__ します。

```python
@staticmethod
def draw_mark(ax, x, y, mark, color="black"):
    if mark == Marubatsu.CIRCLE:
        circle = patches.Circle([x + 0.5, y + 0.5], 0.35, ec=color, fill=False, lw=2)
        ax.add_artist(circle)
    elif mark == Marubatsu.CROSS:
        ax.plot([x + 0.15, x + 0.85], [y + 0.15, y + 0.85], c=color, lw="2")
        ax.plot([x + 0.15, x + 0.85], [y + 0.85, y + 0.15], c=color, lw="2")

Marubatsu.draw_mark = draw_mark
```

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

```diff_python
+@staticmethod
-def draw_mark(self, ax, x, y, mark, color="black"):
+def draw_mark(ax, x, y, mark, color="black"):
    if mark == Marubatsu.CIRCLE:
        circle = patches.Circle([x + 0.5, y + 0.5], 0.35, ec=color, fill=False, lw=2)
        ax.add_artist(circle)
    elif mark == Marubatsu.CROSS:
        ax.plot([x + 0.15, x + 0.85], [y + 0.15, y + 0.85], c=color, lw="2")
        ax.plot([x + 0.15, x + 0.85], [y + 0.85, y + 0.15], c=color, lw="2")

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

__静的メソッド__ として __`draw_mark`__ を __定義__ しても、__`Marubatsu`__ クラスの __インスタンスから__、__`draw_mark`__ を __呼び出す__ ことが __できるの__ で、__`draw_mark`__ を __利用__ する __`draw_board`__ の __プログラム__ を __修正__ する __必要__ は __ありません__。実際に、__`draw_board`__ を修正しなくても、__下記__ のプログラムのように、__`draw_board`__ を使って __ゲーム盤__ を __描画__ することが __できます__。

```python
mb.draw_board()
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/053ba080-2592-7a41-cf08-5e4f161f17b3.png" width="150">

上記の性質から、わざわざ __`draw_mark`__ を __静的メソッド__ で __定義__ する __意味がない__ と思う人がいるかもしれませんので、__静的メソッド__ の __メリット__ をいくつか紹介します。

* その __クラス__ に __密接__ に __関係がある関数__ であることが __明確__ になる
* __インスタンス__ に __関する処理__ が __記述されていない__ ことが __明確__ になる
* __インスタンス__ を __作成しなくても__、__クラス__ から __直接呼び出し__ て __利用できる__

__最後の利点__ の __具体例__ を紹介します。__下記__ は、__〇×ゲーム__ を __リセットした後__ で、__2 行目__ で __ゲーム盤を描画__ し、__その後__ で __`draw_mark`__ メソッドで __(0, 0)__ と __(1, 1)__ に __〇__ と __×__ を __描画__ するプログラムです。__3 行目__ では __`Marubatsu`__ クラスの __インスタンス__ から __`draw_mark`__ を __呼び出して__ いますが、__4 行目__ では __`Marubatsu`__ クラスから __直接 `draw_mark`__ を __呼び出して__ います。

```python
mb.restart()
ax = mb.draw_board()
mb.draw_mark(ax, 0, 0, Marubatsu.CIRCLE)
Marubatsu.draw_mark(ax, 1, 1, Marubatsu.CROSS)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/695f9c4b-0508-ebc7-f8a4-26049b63642b.png" width="150">

__`draw_mark`__ を __静的メソッド__ として __定義していない__ 場合は、__`Marubatsu` クラス__ から __直接 `draw_mark`__ を __呼び出す__ と __エラーが発生__ します。例えば、__今回の記事__ で __定義__ した __`draw_circle`__ メソッドは __静的メソッドではない__ ので、下記のプログラムのように、__`Marubatsu`__ クラスから __直接呼び出そうとする__ と、__エラーが発生__ します。

```python
Marubatsu.draw_circle(ax, 2, 2)
```

実行結果

```
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[28], line 1
----> 1 Marubatsu.draw_circle(ax, 2, 2)

TypeError: draw_circle() missing 1 required positional argument: 'y'
```

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

* __TypeError__
データ型（Type）に関するエラー
* __draw_circle() missing 1 required positional argument: 'y'__
`draw_circle` を呼び出す際に必要とされる（required）、位置引数 （positional argument）`y` に対応する実引数（argument）が 1 つ存在しない（missing）
:::

__上記のエラー__ は、[以前の記事](https://qiita.com/ysgeso/items/44884dcf168aab949508#%E3%82%AF%E3%83%A9%E3%82%B9%E3%81%AE%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89)で説明したように、__クラスのメソッド__ を __クラスから呼び出した__ 場合 は、__通常の関数__ と __同じ処理__ が行われるため、__`draw_circle`__ メソッドの __仮引数 `self`__ に、__対応__ する __実引数__ が __記述されていない__ ことが __原因__ で __発生__ します。

__初心者__ には上記の __利点__ が __わかりづらい__ ので、__静的メソッド__ は、__無理に使う必要__ は __ありません__ が、__利用__ すると __便利な場合がある__ ので __紹介しました__。他にも __本記事__ では __紹介していない使い道__ があるので、興味がある方は調べてみると良いでしょう。

# 手番と結果の描画

__ゲーム盤__ を __文字列で表示__ する場合は、__下記__ の実行結果のように、__ゲーム盤__ の __上__ に、__手番__ や __勝敗の結果__ を __表示__ していました。そこで、 __ゲーム盤__ を __画像で描画__ する場合も、__ゲーム盤__ の __上__ に __その情報__ を __描画__ することにします。なお、先程 __`restart`__ メソッドで、__ゲーム盤をリセット__ したので、__下記__ のプログラムでは、__いくつかのマス__ に __着手__ を行っています。

```python
mb.move(0, 0)
mb.move(1, 1)
mb.move(2, 1)
print(mb)
```

実行結果

```
Turn x
o..
.xO
...
```

__手番__ や __勝敗の結果__ の __情報__ は、__文字列の情報__ なので、__Axes__ の __`text`__ メソッドを __利用__ する __必要__ が __あります__。__下記__ は、__文字列__ で __ゲーム盤__ を __表示__ する __プログラムの中__ で、__上部__ に __表示__ する __メッセージを作成__ する __部分__ で、__この部分__ のプログラムを __参考__ することにします。

```python
    def __str__(self):
        # ゲームの決着がついていない場合は、手番を表示する
        if self.status == Marubatsu.PLAYING:
            text = "Turn " + self.turn + "\n"
        # 決着がついていれば勝者を表示する
        else:
            text = "winner " + self.status + "\n"
    以下略
```

__ゲーム盤__ の __左上の座標__ は __(0, 0)<sub>A</sub>__ なので、__とりあえずその位置__ に __文字列を描画__ することにします。__下記__ は、__`draw_board`__ に __上記__ のプログラムと __同じメッセージ__ を __描画__ する __処理を追加__ したプログラムです。なお、__元のプログラム__ では、この __メッセージの後__ に、__ゲーム盤__ を表す __文字列__ を __表示__ するので、__`text`__ の __最後__ に __改行__ を表す __`"\n"`__ を __結合__ していましたが、__画像__ で __文字列を描画__ する場合は、この __メッセージの後__ に __文字列__ は __存在しない__ ので __削除__ しました。

* __4 ~ 8 行目__：__画面上部__ に __描画__ する __文字列__ を __作成__ し、__`text`__ に __代入__ する
* __9 行目__：__`text`__ を __(0, 0)<sub>A</sub>__ の __位置__ に __描画__ する

```python
 1  def draw_board(self, size=3):
元と同じなので省略
 2      # 上部のメッセージを描画する
 3      # ゲームの決着がついていない場合は、手番を表示する
 4      if self.status == Marubatsu.PLAYING:
 5          text = "Turn " + self.turn
 6      # 決着がついていれば勝者を表示する
 7      else:
 8          text = "winner " + self.status
 9      ax.text(0, 0, text)
10
11    # ゲーム盤の枠を描画する
元と同じなので省略
12
13  Marubatsu.draw_board = draw_board
```

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

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

    # y 軸を反転させる
    ax.invert_yaxis()

    # 上部のメッセージを描画する
    # ゲームの決着がついていない場合は、手番を表示する
    if self.status == Marubatsu.PLAYING:
        text = "Turn " + self.turn
    # 決着がついていれば勝者を表示する
    else:
        text = "winner " + self.status
    ax.text(0, 0, text)

    # ゲーム盤の枠を描画する
    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)           

    # ax を返り値として返す
    return ax

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

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

```diff_python
def draw_board(self, size=3):
元と同じなので省略
    # 上部のメッセージを描画する
    # ゲームの決着がついていない場合は、手番を表示する
+   if self.status == Marubatsu.PLAYING:
+       text = "Turn " + self.turn
+   # 決着がついていれば勝者を表示する
+   else:
+       text = "winner " + self.status
+   ax.text(0, 0, text)

    # ゲーム盤の枠を描画する
元と同じなので省略

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

__下記__ は、__修正後__ に __`draw_board`__ を実行して __ゲーム盤を表示__ するプログラムです。

```python
mb.draw_board()
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/48f8799e-0d81-4856-5184-32bac8024b5f.png" width="150">

__実行結果__ から、__以下の問題__ がある事が __わかります__。

* __文字列__ が __小さすぎる__
* __文字列__ の __位置__ が、__ゲーム盤__ と __くっついている__ ように見える
* __枠__ と __目盛り__ が __邪魔__

そこで、__下記__ のプログラムのように、__文字列__ を __大きく__ し、__位置__ を __少し上に調整__ し、__枠__ と __目盛り__ の __表示を消す__ ように、__`draw_board` を修正__ します。

* __3 行目__：__枠__ と __目盛り__ を __表示しない__ ようにする
* __12 行目__：__文字列__ の __描画位置__ を __0.2__ だけ __上にずらし__、__`fontsize`__ を __`20`__ に __設定__ して __文字列__ を __大きくする__

```python
 1  def draw_board(self, size=3):
元と同じなので省略
 2      # 枠と目盛りを表示しないようにする
 3      ax.axis("off")
 4
 5      # 上部のメッセージを描画する
 6      # ゲームの決着がついていない場合は、手番を表示する
 7      if self.status == Marubatsu.PLAYING:
 8          text = "Turn " + self.turn
 9      # 決着がついていれば勝者を表示する
10      else:
11          text = "winner " + self.status
12      ax.text(0, -0.2, text, fontsize=20)
元と同じなので省略
13
14  Marubatsu.draw_board = draw_board
```

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

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

    # 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)
            
    # ax を返り値として返す
    return ax

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

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

```diff_python
def draw_board(self, size=3):
元と同じなので省略
    # 枠と目盛りを表示しないようにする
+   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)
元と同じなので省略

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

__下記__ は、__修正後__ に __`draw_board`__ を __実行__ して __ゲーム盤を描画__ するプログラムです。__実行結果__ から、__問題はなさそう__ であることが __確認__ できました。文字列の表示内容、大きさ、表示位置をさらに調整したい人は、自由に変更して下さい。

```python
mb.draw_board()
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/7756f140-55a7-724a-be34-92a472586a3d.png" width="150">

__下記__ は、__ゲーム__ の __決着がついた場合__ に __`draw_board`__ を __実行__ して __ゲーム盤を描画__ するプログラムです。__実行結果__ から、こちらも __問題はなさそう__ であることが __確認__ できました。

```python
mb.restart()
mb.move(0, 0)
mb.move(0, 1)
mb.move(1, 0)
mb.move(1, 1)
mb.move(2, 0)
mb.draw_board()
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/ef504432-1fda-f535-1605-2c1997c1ff2e.png" width="150">

# `play` メソッドの修正

__ゲーム盤__ を __画像で描画できる__ ようになったので、最後に __`play`__ メソッドで、__ゲーム盤__ を __画像で表示できる__ ように、__`play`__ メソッドを __下記__ のように __修正__ することにします。

* __ゲーム盤__ を __文字列__ と __画像__ の __どちらで表示__ するかを __代入__ する __仮引数 `gui`__ を __追加__ する
* __`gui`__ に __`False`__ が __代入__ されている場合は __文字列__ で、__`True`__ が __代入__ されている場合は、__画像__ で __ゲーム盤を表示__ する
* __これまで__ の __`play`__ メソッドとの __[互換性を保つ](https://qiita.com/ysgeso/items/af8b5dda0ddffd5c6d13#%E9%96%A2%E6%95%B0%E3%81%AE%E3%83%87%E3%83%95%E3%82%A9%E3%83%AB%E3%83%88%E5%BC%95%E6%95%B0%E3%82%92%E5%88%A9%E7%94%A8%E3%81%97%E3%81%A6%E4%BA%92%E6%8F%9B%E6%80%A7%E3%82%92%E6%8C%81%E3%81%A4)__ ために、__`gui`__ を __デフォルト値__ が __`False`__ の __デフォルト引数__ とする

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

* __1 行目__：__`False`__ を __デフォルト値__ とする __デフォルト引数 `gui`__ を __追加__ する
* __4 ~ 8、10 ~ 14 行目__：__ゲーム盤を表示__ する際に、__`gui`__ が __`True`__ の場合は、__`draw_board`__ メソッドで __描画__ するように __修正__ する

```python
 1  def play(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False):
元と同じなので省略
 2      while self.status == Marubatsu.PLAYING:
 3          # ゲーム盤の表示
 4          if verbose:
 5              if gui:
 6                  self.draw_board()
 7              else:
 8                  print(self)
元と同じなので省略
 9      # 決着がついたので、ゲーム盤を表示する
10      if verbose:
11          if gui:
12              self.draw_board()
13          else:
14              print(self)
15
16      return self.status
17
18  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()
    # ゲームの決着がついていない間繰り返す
    while self.status == Marubatsu.PLAYING:
        # ゲーム盤の表示
        if verbose:
            if gui:
                self.draw_board()
            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):
+def play(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False):
元と同じなので省略
    while self.status == Marubatsu.PLAYING:
        # ゲーム盤の表示
        if verbose:
-           print(self)
+           if gui:
+               self.draw_board()
+           else:
+               print(self)
元と同じなので省略
    # 決着がついたので、ゲーム盤を表示する
    if verbose:
        print(self)
+       if gui:
+           self.draw_board()
+       else:
+           print(self)

    return self.status

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

__下記__ は、__`gui=True`__ を __実引数に記述__ して __`play`__ メソッドで __`ai2` どうし__ の __対戦__ を行うプログラムです。__実際__ の __実行結果__ は、__画像__ が __縦に並びます__ が、__下記__ では __横に並べます__。

```python
from ai import ai2
mb = Marubatsu()

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

実行結果（実行結果はランダムなので下記とは異なる場合があります）

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/59c1b79c-b960-32c2-a44f-afc887dbf11e.png" width="100">
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/0d434b02-1dcf-ef9f-fd43-4a580c2bfbbe.png" width="100">
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/d9cb3b4e-7765-0a50-ad16-c0603bf3acc9.png" width="100">
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/44b83173-7f2f-b909-a4bd-593b53b924eb.png" width="100">
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/12c19ab3-ec21-5c17-883f-e4b8e1d3cb1d.png" width="100">
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/0b3c338e-7659-702b-1ec4-32d613563a1e.png" width="100">

__念のため__ に、__下記__ のプログラムで __`gui`__ を __記述せず__ に __`play`__ メソッドを __実行__ してみます。__実行結果__ から、__これまでどおり__、__文字列__ で __ゲーム盤が表示__ されることが __確認__ できました。

```python
from ai import ai2
mb = Marubatsu()

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

実行結果（実行結果はランダムなので下記とは異なる場合があります）

```
Turn o
...
...
...

Turn x
...
...
.O.

Turn o
..X
...
.o.

Turn x
O.x
...
.o.

Turn o
oXx
...
.o.

Turn x
oxx
O..
.o.

Turn o
oxx
o..
.oX

winner o
oxx
o..
Oox
```

## 人間との対戦

次に、__下記__ のプログラムを実行して、__`ai2` と対戦__ してみることにします。

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

__実行__ すると __わかる__ と思いますが、__ゲーム盤の画像__ が __描画されません__。また、テキストボックスに __exit__ を __入力__ して __ゲームを中断__ すると、__その時点__ で __ゲーム盤の画像__ が下記のように __描画__ されます。このようなことが起きる理由について少し考えてみて下さい。

__exit__ を __入力後__ の実行結果 

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/b3f97115-5160-f785-8f2f-d9f4b1acb366.png" width="150">

上記のようなことがおきる __理由__ は、[以前の記事](https://qiita.com/ysgeso/items/ab2770693c947c108dd0#figure-%E3%81%AE%E6%8F%8F%E7%94%BB)で説明したように、__画像の描画__ が __`plt.show()`__ が __実行__ された __時点__ で __行われる__ からです。__`play`__ メソッドが __行う処理の中__ では、__`plt.show()`__ は __実行されない__ ため、__`play`__ メソッドが __終了するまで__ の間で、__ゲーム盤の画像__ は __描画されません__。

なお、__exit__ を __入力した後__ で、__ゲーム盤の画像__ が __描画される__ のは、__JupyterLab__ の __セルの実行__ が __終了__ して __自動的__ に __`plt.show()`__ が __呼び出される__ ためです。

:::note
__細かい話__ ですが、__AI どうし__ の __対戦後__ に、__複数__ の __ゲーム盤の画像__ が __まとめて描画__ されるのは、__下記__ のような __処理__ が行われているからです。

* __`draw_board`__ の中で、__`subplots`__ メソッドが __実行されるたび__ に、__新しい Figure__ が __作成__ されて __pyplot__ に登録される
* __`draw_board`__ は、__新しく作成__ した __Figure__ に __ゲーム盤の画像__ の __描画を行う__
* 従って、__AI どうし__ の __対戦__ が行われた場合は、__`draw_board`__ が __呼ばれた数__ だけ、__Figure が作成__ され、__pyplot__ に __登録__ される
* __`plt.show()`__ が __実行__ された際に、__複数__ の __Figure が登録__ されていた場合は、__登録__ されている __すべての Figure__ の __画像__ が __順番に描画__ される
:::

__`play`__ メソッドで __人間が対戦__ する場合は、__ゲーム__ が __終了するまで__ に、__`plt.show()`__ が __実行されない__ ため、__ゲーム盤__ が __表示されません__。__この問題__ は、__下記__ のプログラムの __3 行目__ のように、__`draw_board`__ の中で、__すべて__ の __描画の処理__ を __行った後__ で、__`plt.show()`__ を実行するように __修正__ することで __解決__ することができます。なお、__`plt.show()`__ を __実行__ すると、__pyplot__ に __登録__ された __Figure__ は __自動的に削除される__ ので、__`ax`__ に代入された __Axes__ も __利用できなくなります__。そのため、__`ax`__ を __return 文__ で __返す処理__ は __削除__ しました。

```python
1  def draw_board(self, size=3):
元と同じなので省略
2      # ゲーム盤を描画する
3      plt.show()
4
5  Marubatsu.draw_board = draw_board
```

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

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

    # 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
def draw_board(self, size=3):
元と同じなので省略
    # ゲーム盤を描画する
+   plt.show()
            
    # ax を返り値として返す
    return ax

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

実行結果は示しませんが、__下記__ のプログラムを実行して __`ai2` と対戦__ すると、今度は __ゲーム盤の画面__ が __表示__ されるようになることが __確認__ できます。

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

# 今回の記事のまとめ

__今回の記事__ では、__〇×ゲーム__ の __ゲーム盤__ を __画像で描画__ し、__実際__ に __`play` メソッド__ で __ゲーム盤の画像__ を __表示__ して __遊ぶことができるよう__ に しました。これで __GUI__ の __出力__ を行う部分は __完成__ しましたが、__座標の入力__ は、相変わらず __文字で入力__ を行う __必要__ が __あります__。

次回の記事では、__GUI__ の __入力部分__ の __処理__ を __記述する方法__ を __紹介__ します。

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

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

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

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

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

# 次回の記事

https://qiita.com/ysgeso/items/1920c68f5a1e8a998796

[^1]:[前回の記事](https://qiita.com/ysgeso/items/442db53b5b7e22c1000c#2-%E7%A8%AE%E9%A1%9E%E3%81%AE%E5%BA%A7%E6%A8%99%E3%81%AE%E8%A1%A8%E8%A8%98%E3%81%AE%E5%8C%BA%E5%88%A5)で説明したように、本記事では、__matplotlib__ の __Axes__ の __座標__ を __ゲーム盤のマスの座標__ と __区別__ するために、__右下__ に __小さな A__ を __表記__ します