Pythonで〇×ゲームのAIを一から作成する　その66　枠線と文字列によるマークの描画

# 目次と前回の記事

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

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

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

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

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

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

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

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

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

# Artist を登録するメソッドに関する今後の説明の方法

最初に、__Artist を登録__ する __メソッド__ に関する __今後__ の __説明の方法__ を説明します。

:::note warn
[前回の記事](https://qiita.com/ysgeso/items/ab2770693c947c108dd0#figure-%E3%81%AE%E6%8F%8F%E7%94%BB)で、__matplotlib__ は __`plt.show()`__ を __実行するまで__ は、__画像__ を __描画しない__ と説明しました。例えば __`plot`__ メソッドは、__実際__ に __Axes__ に __線__ や __折れ線__ を表す __図形__ の __Artist__ を __登録するだけ__ で、__画像の描画__ は __行いません__。

ただし、__`plot`__ などの __説明__ をする際に、「__Axes__ に __Artist を登録__ する」や、「__実際の描画__ は __`plt.show()`__ で行う」のような __説明__ を行うと、__まわりくどい__、__意味が分かづらい__ 説明になってしまいます。そのため、__以後__ は __`plot`__ メソッド __など__、__Artist__ を __登録__ する __処理の説明__ を、「__〇〇を描画する__」 のように __説明する__ ことにします。
:::

# 2 種類の座標の表記の区別

__ゲーム盤__ の __マスの座標__ と、__matplotlib__ の __Axes__ が管理する __座標__ は、__異なる座標__ です。この __2 つの座標__ を __混同__ すると、__間違った処理__ が __行われる点__ に __注意__ して下さい。

:::note warn
__前回の記事__ では、__どちらの座標__ も __(0, 0)__ のように __記述__ してきましたが、__どちらの座標を表すか__ が __区別しづらい__ ので、以後は __ゲーム盤のマスの座標__ は、これまでどおり __(0, 0)__ のように __表記__ し、__Axes の座標__ は __(0, 0)<sub>A</sub>__ のように、__右下__ に __小さな A__ を __表記__ することにします。

ただし、__プログラム__ の __コメントの中__ での __表記__ は __区別しません__。
:::

# ゲーム盤の描画

今回の記事では、前回の記事で説明した __matplotlib__ の機能を使って、__〇×ゲーム__ の __ゲーム盤__ を __画像__ で __描画__ します。まず、__`Marubatsu`__ クラスに、__ゲーム盤__ を __描画__ するための __下記__ のような __メソッド__ を __定義__ する事にします。

* __名前__：__ゲーム盤__（board）を __描画__（draw）するので、__`draw_board`__ という __名前__ にする
* __処理__：__ゲーム盤__ の __画像__ を __描画__ する
* __入力__：なし
* __出力__：なし

下記は、__`draw_board`__ メソッドの __定義__ です。なお、メソッドの処理はこれから記述します。

```python
from marubatsu import Marubatsu

def draw_board(self):
    pass

Marubatsu.draw_board = draw_board
```

## ゲーム盤の描画で行う処理の考察

__画像__ で __ゲーム盤__ を __表示__ する __処理__ を __記述__ する際に、何をすれば良いかがわからない場合は、これまでの __文字列__ での __ゲーム盤の表示__ を行う __処理__ を __参考__ にすると良いでしょう。

下記は、__文字列__ で __ゲーム盤の表示__ を行う __プログラム__ です。

```python
mb = Marubatsu()
mb.move(0, 0)
mb.move(1, 1)
print(mb)
```

実行結果

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

上記の __表示内容__ から、__ゲーム盤を描画__ するためには、__下記__ の内容を __描画__ する __必要がある__ ことが __わかります__。

* __手番__ や __ゲームの結果__ の __情報__
* __ゲーム盤__ に __配置__ された __マーク__

また、__文字列__ での __ゲーム盤__ には __表示していません__ が、__画像__ で __ゲーム盤を描画__ する際には、ゲーム盤の __マスを区別__ するための、__枠線の描画__ が __必要__ になります。

そこで、__それら__ を __順番__ に __実装する__ ことにします。

## ゲーム盤の枠線の描画

__ゲーム開始時__ の __〇×ゲーム__ の __ゲーム盤__ を __画像で描画__ する場合、おそらくほとんどの方は、__下記__ のような __画像__ を __思い浮かべる__ のではないかと思います。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/8e604d57-3608-71e5-c3c7-edd80713e0e8.png" width="150">

この画像は、__4 本の線__[^1]で __構成__ されているので、__Axes__ の __`plot`__ メソッドを __利用__ して __ゲーム盤__ の __枠線__ を __描画__ することが __できます__。

[前回の記事](https://qiita.com/ysgeso/items/ab2770693c947c108dd0#%E3%82%B0%E3%83%A9%E3%83%95%E3%81%AE%E6%8F%8F%E7%94%BB%E6%96%B9%E6%B3%95)では、__`plot`__ メソッドで __折れ線グラフ__ を __描画__ する方法を __紹介__ しましたが、__`plot`__ メソッドの __実引数__ に __記述__ する、__x 座標__ と __y 座標__ の __それぞれ__ の __座標の数__ を __2 つ__ にすることで、__線__ を __描画__ することが __できます__。

そこで、__4 つの線__ の __両端__ の __座標__ を __どのように設定__ するかについて __少し考えてみて__ 下さい。__ピンとこない人__ は、__ノート__ や __グラフ用紙__ などに __上記の図__ を __実際__ に __書いてみて__、それぞれの線の両端の __点の座標__ が __どうなるか__ を __考えてみる__ と __良い__ でしょう。

### 枠線の両端の座標の設定例

__下記__ は、__線__ の __両端の座標__ の __設定例__ です。__ゲーム盤__ の __左上の座標__ を __原点（0, 0)<sub>A</sub>__ とし、__縦方向__ の __枠線__ は、__原点__ から __x 軸方向__ に __1__ ずつ __離れた位置__ に __描画__ します。__横方向__ の __枠線__ も __同様__ です。なお、__ゲーム盤__ の __枠__ が __上図__ のように __配置__ されるのであれば、__下図以外__ の __設定__ でも __構いません__。興味と時間がある方は、他の設定でゲーム盤を描画してみて下さい。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/f24cc46a-209e-6232-926c-b91379dc6bc0.png" width="150">

:::note warn
このような __図__ をノートなどに __書く__ ことで、__枠線__ の __両端の座標__ が __理解しやすく__ なります。プログラムで __図形__ などを __組み合わせ__ て __画像を描画__ する際に、__わからなくなった場合__ は、__図__ を __書いてみる__ ことを __強くお勧めします__。
:::

:::note
__上図__ は、__matplotlib__ で __描画__ したものです。詳細は長くなるので省略しますが、__矢印__ による __注釈__ は、__Axes__ の __`annotate`__ というメソッドを使って __描画__ することができます。例えば、__(0, 0)<sub>A</sub>__ の __矢印__ と __注釈__ は、__下記__ のプログラムのように __記述__ します。

```python
ax.annotate('(0, 0)', xy=(0, 0), xytext=(0.35, 0.6),
            arrowprops=dict(facecolor='black', shrink=0.05))
```

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

https://matplotlib.org/stable/users/explain/quick_start.html#annotations

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

### 枠線の描画を行うプログラム

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/f24cc46a-209e-6232-926c-b91379dc6bc0.png" width="150">

__下記__ は、__上図__ を __参考__ に、それぞれの __枠線__ の __両端の座標__ を __列挙__ したものです。

* __(0, 1)<sub>A</sub>__ と __(3, 1)<sub>A</sub>__
* __(0, 2)<sub>A</sub>__ と __(3, 2)<sub>A</sub>__
* __(1, 0)<sub>A</sub>__ と __(1, 3)<sub>A</sub>__
* __(2, 0)<sub>A</sub>__ と __(2, 3)<sub>A</sub>__

__下記__ のプログラムは、__`plot`__ メソッドに __上記の座標__ を __記述__ して、__4 つの線__ を __描画__ するプログラムです。__`plot`__ メソッドは __一度__ で __1 つの線__（または折れ線）しか __描画__ できないので、下記のように、__それぞれ__ の __枠線ごと__ に、__`plot`__ メソッドを __呼び出す必要__ が __あります__。

* __4 行目__：__[`subplots`](https://qiita.com/ysgeso/items/ab2770693c947c108dd0#figure-%E3%81%A8-axes-%E3%81%AE%E4%B8%80%E6%8B%AC%E4%BD%9C%E6%88%90)__ メソッドで __Figure__ と __Axes__ を __作成__ し、それぞれを __変数に代入__ する
* __5 ~ 8 行目__：__Axes__ の __`plot`__ メソッドを呼び出して __4 つの線__ を __描画__ する

```python
1  import matplotlib.pyplot as plt
2  import japanize_matplotlib
3
4  fig, ax = plt.subplots(figsize=[3, 3])
5  ax.plot([0, 3], [1, 1]) # (0, 1) と (3, 1) を結ぶ線
6  ax.plot([0, 3], [2, 2]) # (0, 2) と (3, 2) を結ぶ線
7  ax.plot([1, 1], [0, 3]) # (1, 0) と (1, 3) を結ぶ線
8  ax.plot([2, 2], [0, 3]) # (2, 0) と (2, 3) を結ぶ線
```

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

```python
import matplotlib.pyplot as plt
import japanize_matplotlib

fig, ax = plt.subplots(figsize=[3, 3])
ax.plot([0, 3], [1, 1]) # (0, 1) と (3, 1) を結ぶ線
ax.plot([0, 3], [2, 2]) # (0, 2) と (3, 2) を結ぶ線
ax.plot([1, 1], [0, 3]) # (1, 0) と (1, 3) を結ぶ線
ax.plot([2, 2], [0, 3]) # (2, 0) と (2, 3) を結ぶ線
```
</details>


実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/c532333b-59d0-4215-12cf-8866bd4356c0.png" width="150">

:::note alert
__`plot`__ メソッド は、__線__ や __折れ線__ の __頂点__ の __それぞれの座標__ を __[0, 1]__ のように __記述__ するの __ではなく__、__それぞれの座標__ を __x 座標__ と __y 座標__ に __分けて記述__ する点に __注意__ して下さい。__よくある間違い__ ですが、__下記__ のように __記述__ すると、__(0, 1)<sub>A</sub>__ と __(3, 1)<sub>A</sub>__ を結ぶ線 __ではなく__、__(0, 3)<sub>A</sub>__ と __(1, 1)<sub>A</sub>__ を __結ぶ線__ が __描画__ されてしまいます。

```python
fig, ax = plt.subplots(figsize=[3, 3])
ax.plot([0, 1], [3, 1])
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/9a7d8160-7d71-568a-dadb-d7cf7e421115.png" width="150">
:::

:::note
__上記__ の __実行結果__ には __示しませんでした__ が、__上記__ のプログラムを __実行__ すると、__最初__ に __下記__ のような __表示__ が __行われます__。

__JupyterLab__ では、__セルの最後__ に __実行__ された __処理の結果__ が __表示される__ ようになっており、これは、__最後の行__ の __`plot`__ メソッドが __返す Artist__ です。

本記事では、__以降__ も、__このような表示__ は __実行結果の表記__ から __省略__ します。

```
[<matplotlib.lines.Line2D at 0x29ffa9bc790>]
```
:::

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/c532333b-59d0-4215-12cf-8866bd4356c0.png" width="150">

__上記__ の __実行結果__ のように、__`plot`__ で __線を描画__ すると、__勝手__ に __色__ が __ついて__ しまいます。これは、__`plot`__ が __折れ線グラフ__ を __描画するため__ のメソッドなので、__複数__ の __折れ線__ を __描画__ した際に、__区別がつく__ ように __自動的__ に __異なる色__ で __表示__ するという __機能を持つ__ からです。

__線__ を __黒色__ で __描画__ したい場合は、__下記__ のプログラムのように、__`c`__ という __キーワード引数__ に、[__黒色__ を表す __`"k"`__](https://qiita.com/ysgeso/items/ab2770693c947c108dd0#%E8%89%B2%E3%81%AE%E8%A8%AD%E5%AE%9A%E6%96%B9%E6%B3%95) を記述します。わかりづらいと思った人は、__`color="black"`__ を __記述__ しても __構いません__。__下記__ のプログラムでは、__5 行目__ で __そのように記述__ してみました。

```python
fig, ax = plt.subplots(figsize=[3, 3])
ax.plot([0, 3], [1, 1], c="k") # (0, 1) と (3, 1) を結ぶ線
ax.plot([0, 3], [2, 2], c="k") # (0, 2) と (3, 2) を結ぶ線
ax.plot([1, 1], [0, 3], c="k") # (1, 0) と (1, 3) を結ぶ線
ax.plot([2, 2], [0, 3], color="black") # (2, 0) と (2, 3) を結ぶ線
```

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

```diff_python
fig, ax = plt.subplots(figsize=[3, 3])
-ax.plot([0, 3], [1, 1]) # (0, 1) と (3, 1) を結ぶ線
+ax.plot([0, 3], [1, 1], c="k") # (0, 1) と (3, 1) を結ぶ線
-ax.plot([0, 3], [2, 2]) # (0, 2) と (3, 2) を結ぶ線
+ax.plot([0, 3], [2, 2], c="k") # (0, 2) と (3, 2) を結ぶ線
-ax.plot([1, 1], [0, 3], c="k") # (1, 0) と (1, 3) を結ぶ線
+ax.plot([1, 1], [0, 3]) # (1, 0) と (1, 3) を結ぶ線
-ax.plot([2, 2], [0, 3], color="black") # (2, 0) と (2, 3) を結ぶ線
+ax.plot([2, 2], [0, 3]) # (2, 0) と (2, 3) を結ぶ線
```
</details>


実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/5fd8f1b6-d23d-8c03-deb9-6056efb4851a.png" width="150">

__実行結果__ から、__すべての枠線__ が __黒色__ で __描画__ されることが __確認__ できます。

### Axes の 表示範囲に関する補足

__`plot`__ メソッドなど、__Axes__ の __グラフを描画__ するメソッドの __多く__ は、__実行__ することで、__Axes__ の __表示範囲__ を、__描画__ する __すべての図形__ がぴったり __収まる__ ような __範囲より__ も __少しだけ広い範囲__ になるように __自動的__ に __設定__ してくれます。__上図__ の場合は __x 座標__ と __y 座標__ の __範囲__ には __`-0.15 ~ 3.15`__ が __設定__ されます。__自動的__ に __設定__ された __表示範囲__ に __問題がない__ 場合は、[__`set_xlim`__](https://qiita.com/ysgeso/items/ab2770693c947c108dd0#plot-%E3%81%AB%E6%8F%8F%E7%94%BB%E3%81%99%E3%82%8B%E8%A1%A8%E7%A4%BA%E7%AF%84%E5%9B%B2%E3%81%AE%E7%AE%A1%E7%90%86) 等のメソッドを __記述__ する __必要__ は __ありません__。

__〇×ゲーム__ の __ゲーム盤__ を __描画__ する際に、__表示範囲__ を __ゲーム盤__ の __大きさ__ に __ぴったり合わせる__ ことも __できます__ が、__上記の画像__ で特に __違和感はない__ ので __そのままにする__ ことにします。ぴったり合わせたい方は `set_xlim` 等のメソッドを記述して下さい。

__Axes__ の __x 軸__ と __y 軸__ の __表示範囲__ は、下記のプログラムのように、__`get_xlim`__ と __`get_ylim`__ というメソッドで知ることができます。__描画__ される __画像__ は __同じ__ なので __省略__ しました。

```python
fig, ax = plt.subplots(figsize=[3, 3])
ax.plot([0, 3], [1, 1], c="k") # (0, 1) と (3, 1) を結ぶ線
ax.plot([0, 3], [2, 2], c="k") # (0, 2) と (3, 2) を結ぶ線
ax.plot([1, 1], [0, 3], c="k") # (1, 0) と (1, 3) を結ぶ線
ax.plot([2, 2], [0, 3], color="black") # (2, 0) と (2, 3) を結ぶ線
print(ax.get_xlim())
print(ax.get_ylim())
```

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

```diff_python
fig, ax = plt.subplots(figsize=[3, 3])
ax.plot([0, 3], [1, 1], c="k") # (0, 1) と (3, 1) を結ぶ線
ax.plot([0, 3], [2, 2], c="k") # (0, 2) と (3, 2) を結ぶ線
ax.plot([1, 1], [0, 3], c="k") # (1, 0) と (1, 3) を結ぶ線
ax.plot([2, 2], [0, 3], color="black") # (2, 0) と (2, 3) を結ぶ線
+print(ax.get_xlim())
+print(ax.get_ylim())
```
</details>

実行結果

```
(-0.15000000000000002, 3.15)
(-0.15000000000000002, 3.15)
```

:::note
上記の __実行結果__ で、__-0.15000000000000002__ のように __表示__ されるのは、__2 進数__ で、__10 進数__ の __-0.15__ を __表現__ すると、__1.00110011・・・__ のように、__小数点以下__ に __0011__ が __無限に繰り返される__ ような __数値__ になるためです。__Python__ の __float__[^2] は __2 進数__ で __数値を表現__ するので、__-0.15__ を __正確__ に __表現__ することは __できない__ ため、上記のような __微小な誤差__ が __発生__ します。

__上記__ では __3.15__ は __正確__ に __表現できている__ ように __見えるかもしれません__ が、実は __3.15__ も __同様__ に、__2 進数__ では __正確__ に __表現できません__。上記で __3.15__ のように __表示__ されるのは、__四捨五入__ した __結果__ が、__3.15 になった__ からです。

なお、__Axes__ の __表示範囲__ の場合は、このような __微小な誤差__ は、__画像の描画__ に __影響を与えない__ ので、__気にする必要__ は __ありません__。

[筆者のホームページ](http://www.edu.i.hosei.ac.jp/~sigesada/kyouzai/)に、__2 進数__ と __10 進数__ の __変換を行うツール__ を作りましたので、興味がある方は[リンク先](http://www.edu.i.hosei.ac.jp/~sigesada/kyouzai/number.html)を見て下さい。他にも、[色](http://www.edu.i.hosei.ac.jp/~sigesada/kyouzai/color.html)や[画像](http://www.edu.i.hosei.ac.jp/~sigesada/kyouzai/image_bitmap.html)のデジタル表現の解説や[文字コードの変換ツール](http://www.edu.i.hosei.ac.jp/~sigesada/kyouzai/mojicodehenkan.html)などもあります。
:::

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

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

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

### 軸の反転

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/5fd8f1b6-d23d-8c03-deb9-6056efb4851a.png" width="150">

上図は、先程の実行結果です。__上図__ で __うまくいっている__ ように __見えるかもしれません__ が、実は上図には __大きな問題__ が __あります__。それが何かを少し考えてみて下さい。

__matplotlib__ は、__グラフを描画__ するために __利用__ されることが __多い__ ので、__y 座標__ は __数学と同様__ に、__上方向__ が __正の値__ になります。一方、__コンピュータ__ が扱う __画像の座標__ は、__一般的__ に __y 座標__ は __下方向__ が __正の値__ になります。実際に、__上図__ をよく見ると、__y 座標__ が __上方向__ が __正の値__ になっていることがわかります。

:::note
__コンピュータ__ の __画像__ で、__y 座標__ が __下方向__ が __正の値__ になっているのは、コンピューターでは __一般的__ に __上から下__ に __文字など__ を __描画__ するからだと __思います__。
:::

__matplotlib__ では、__下記__ のプログラムのように、__Axes__ の __`invert_yaxis`__ メソッドを実行することで、__y 軸__（y axis）の __方向__ を __逆にする__（invert）ことが __できます__。__〇×ゲーム__ では __利用しません__ が、__x 軸__ の __方向__ を __逆__ にする __`invert_xaxis`__ もあります。

```python
fig, ax = plt.subplots(figsize=[3, 3])
ax.plot([0, 3], [1, 1], c="k") # (0, 1) と (3, 1) を結ぶ線
ax.plot([0, 3], [2, 2], c="k") # (0, 2) と (3, 2) を結ぶ線
ax.plot([1, 1], [0, 3], c="k") # (1, 0) と (1, 3) を結ぶ線
ax.plot([2, 2], [0, 3], color="black") # (2, 0) と (2, 3) を結ぶ線
ax.invert_yaxis()
```

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

```diff_python
fig, ax = plt.subplots(figsize=[3, 3])
ax.plot([0, 3], [1, 1], c="k") # (0, 1) と (3, 1) を結ぶ線
ax.plot([0, 3], [2, 2], c="k") # (0, 2) と (3, 2) を結ぶ線
ax.plot([1, 1], [0, 3], c="k") # (1, 0) と (1, 3) を結ぶ線
ax.plot([2, 2], [0, 3], color="black") # (2, 0) と (2, 3) を結ぶ線
+ax.invert_yaxis()
```
</details>


実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/60307b3a-dae6-d314-463a-95153c481465.png" width="150">

__実行結果__ から __y 座標__ の __上下が反転__ していることが __確認__ できます。

### `draw_board` メソッドへの組み込み

__上記__ のプログラムを __`draw_board`__ メソッドに __組み込む__ と __下記__ のようになります。__y 軸を反転__ させる __処理__ は、__`plt.show()`__ で __画像__ を __実際に描画__ する __前__ であれば、__どこで行っても構わない__ ので、下記のプロラムでは、__ゲーム盤の枠__ を __描画__ するよりも __前で行う__ ことにしてみました。__`ax.invert_yaxis()`__ を __最後に記述したい__ 人は、 __順番__ を __入れ替えて__ 下さい。

:::note
__matplotlib__ が、__`plt.show()`__ を __実行するまで__ 画像を __描画しない理由__ の __一つ__ は、__Artist__ を __登録するたび__ に __画像__ を __描画__ すると、__軸の方向__ や、__Axes__ の __表示範囲__ を __変更__ した際に、それまでに描画した __画像__ を __描画し直す必要__ が __生じる__ からです。
:::

なお、__〇×ゲーム__ の __ゲーム盤を描画__ する際に、__Axes__ の __枠や目盛り__ の __描画__ は __消したほうが良い__ のですが、__この後__ で __ゲーム盤__ に __〇 など__ を __描画__ する際に、__目盛り__ が __表示__ されていた方が __わかりやすい__ ので、__しばらく__ は __目盛りを表示したまま__ にすることにします。

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

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

    # ゲーム盤の枠を描画する
    ax.plot([0, 3], [1, 1], c="k") # (0, 1) と (3, 1) を結ぶ線
    ax.plot([0, 3], [2, 2], c="k") # (0, 2) と (3, 2) を結ぶ線
    ax.plot([1, 1], [0, 3], c="k") # (1, 0) と (1, 3) を結ぶ線
    ax.plot([2, 2], [0, 3], color="black") # (2, 0) と (2, 3) を結ぶ線

Marubatsu.draw_board = draw_board
```

__下記__ のプログラムで、__`Marubatsu`__ クラスの __インスタンスを作成__ し、__`draw_board`__ を __実行__ してみます。__実行結果__ から __ゲーム盤__ が __描画される__ ことが __確認__ できました。

```python
mb = Marubatsu()
mb.draw_board()
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/60307b3a-dae6-d314-463a-95153c481465.png" width="150">

### draw_board メソッドの修正 その 1

__上記__ の __`draw_board`__ メソッドでは、__4 本__ の __枠線__ を __描画__ するために、__`plot`__ メソッドを __4 回__ 呼び出していますが、__その処理__ を __繰り返し処理__ を使って __記述__ することが __できます__。また、__そのように修正__ することで、例えば __5 x 5 など__ の、__3 x 3 以外__ の __大きさ__ の __〇×ゲーム__ の __ゲーム盤__ も __表示できる__ ようになるという __利点__ が __得られます__。

まず、__下記__ の __横線を描画__ するプログラムを、__繰り返し__ で __表示する方法__ を考えて下さい。

```python
    ax.plot([0, 3], [1, 1], c="k") # (0, 1) と (3, 1) を結ぶ線
    ax.plot([0, 3], [2, 2], c="k") # (0, 2) と (3, 2) を結ぶ線
```

__上記__ のプログラムを __観察__ すると __下記__ の事が __わかります__。

* __x 座標__ を表す __`[0, 3]`__ は __共通__ する
* __x 座標__ のデータのうちの __`3`__ は、__ゲーム盤__ の __大きさ__ を __表す 3__ である
* __y 座標__ を表すデータは、__`[1, 1]`__、__`[2, 2]`__ のように __規則正しく増えて__ いる
* __y 座標__ を表すデータは、__1__ から __ゲーム盤__ の __大きさ__ を表す __3 未満__ まで __増えて__ いる

__ゲーム盤のサイズ__ は、__`BOARD_SIZE`__ 属性に __代入__ されているので、__上記__ のプログラムは、__下記__ のように __for 文__ を使って __記述__ することが __できます__。

```python
    for i in range(1, self.BOARD_SIZE):
        ax.plot([0, self.BOARD_SIZE], [i, i], c="k") # 横方向の枠線
```

また、__縦線__ を __描画__ するプログラムも、__同様__ に、__下記__ のプログラムで __記述__ できます。

```python
    for i in range(1, self.BOARD_SIZE):
        ax.plot([i, i], [0, self.BOARD_SIZE], c="k") # 縦方向の枠線
```

__上記__ の 2 つのプログラムは、__1 つにまとめる__ ことが __できるの__ で、__`draw_board`__ は __下記__ のように __定義__ することが __できます__。

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

    # 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") # 縦方向の枠線        

Marubatsu.draw_board = draw_board
```

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

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

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

    # ゲーム盤の枠を描画する
    # ゲーム盤の枠を描画する
-   ax.plot([0, 3], [1, 1], c="k") # (0, 1) と (3, 1) を結ぶ線
-   ax.plot([0, 3], [2, 2], c="k") # (0, 2) と (3, 2) を結ぶ線
-   ax.plot([1, 1], [0, 3], c="k") # (1, 0) と (1, 3) を結ぶ線
-   ax.plot([2, 2], [0, 3], color="black") # (2, 0) と (2, 3) を結ぶ線
+   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") # 縦方向の枠線        

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/60307b3a-dae6-d314-463a-95153c481465.png" width="150">

また、__下記__ のプログラムで、__ゲーム盤__ の __サイズ__ を __5 x 5__ にした場合でも __`draw_board`__ で __ゲーム盤__ を __正しく描画__ されることが __確認__ できます。

```python
mb = Marubatsu(board_size=5)
mb.draw_board()
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/9cacf51b-adc5-54e5-b55e-ae20a1e28c2c.png" width="150">

### draw_board メソッドの修正 その 2

上記の __`draw_board`__ は、__`figsize`__ が __`[3, 3]`__ の __サイズ__ の __画像を描画__ しますが、__ゲーム盤__ の __画像の大きさ__ を __自由__ に __調整できる__ ほうが __便利__ です。そこで、__`draw_board`__ の __仮引数__ に、__ゲーム盤__ の __画像の大きさ__ を __代入__ する __`size`__ という __仮引数__ を __追加__ します。また、__`size`__ に対して、__以下__ のような __工夫を行う__ ことにします。

* __〇×ゲーム__ の __ゲーム盤__ は __正方形__ なので、__`size`__ に __`[3, 3]`__ のような __list__ を __記述__ するの __ではなく__、__一つ__ の __`3`__ のような __数値を代入__ するようにする
* __`draw_board`__ を __呼び出す__ 際に、毎回 __`size`__ に __対応__ する __実引数__ を __記述__ するのは __面倒__ なので、__`3`__ を __デフォルト値__ とする __デフォルト引数__ にする

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

* __1 行目__：__デフォルト値__ に __3__ を __設定__ した __デフォルト引数 `size`__ を __追加__ する
* __2 行目__：__`figsize`__ に __`[size, size]`__ を __設定__ して __`subplots`__ を __呼び出す__ ように __修正__ することで、__描画__ する __画像の大きさ__ を __`size`__ で __設定__ できる __ようにする__ 

```python
def draw_board(self, size=3):
    fig, ax = plt.subplots(figsize=[size, size])
以下同じなので略
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") # 縦方向の枠線        

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

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

```diff_python
-def draw_board(self):
+def draw_board(self, size=3):
-   fig, ax = plt.subplots(figsize=[3, 3])
+   fig, ax = plt.subplots(figsize=[size, size])
以下同じなので略
Marubatsu.draw_board = draw_board
```
</details>

__下記__ は、__`draw_board`__ の __実引数__ の記述で __`size` を省略__ した場合と、__`size`__ に __`5` を設定__ した場合のプログラムです。[前回の記事](https://qiita.com/ysgeso/items/ab2770693c947c108dd0#figure-%E3%81%AE%E6%8F%8F%E7%94%BB)で説明したように、__1 つ__ の __JupyterLab のセル__ で、__複数__ の __画像を描画__ する場合は、__`plt.show()`__ を __実行__ する __必要__ が __あります__。__実行結果__ から、__ゲーム盤__ の __画像の大きさ__ を __変える__ ことが __できるようになった__ とが __確認__ できます。

なお、__先程 `mb`__ に __5 x 5__ の __ゲーム盤を代入__ したので、__下記__ では __`mb`__ を __作り直し__ ています。

```python
mb = Marubatsu()
mb.draw_board()
plt.show()
mb.draw_board(size=5)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/60307b3a-dae6-d314-463a-95153c481465.png" width="150"><br>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/c51916d9-3aaa-5e47-6a16-ed8cfafcd7eb.png" width="250">

## 文字列によるマークの描画

次は、__〇__ と __×__ の __マーク__ を __ゲーム盤に描画__ する __処理__ を __記述__ します。

__これまでと同様__ に、__〇__ と __×__ の __文字列__ を __画像に描画__ するという __方法__ も考えられますが、__文字列__ で __〇__ や __×__ などを __描画__ する __方法__ には、__下記__ のような __問題__ が __あります__。

* 表示する __文字列__ の __位置__ を __うまく調整__ しないと、__マスの真ん中__ に __表示できない__
* __文字列__ の __大きさ__ を __変える__ と、__位置__ の __調整__ を __やり直す必要__ がある
* __〇__ や __×__ の __線の太さ__ を __変更__ することが __難しい__

わかりづらいと思いますので、__具体例__ を挙げながら __説明__ します。

まず、__(0, 0)__ のマスに __〇__ を __文字列で描画__ するプログラムを __記述__ することにします。[前回の記事](https://qiita.com/ysgeso/items/ab2770693c947c108dd0#%E6%96%87%E5%AD%97%E5%88%97%E3%81%AE%E4%BD%9C%E6%88%90%E3%81%A8%E7%99%BB%E9%8C%B2)で説明したように、__文字列__ は __Axes__ の __`text`__ メソッドで __描画__ することが __できます__ が、その際に、描画する __文字列__ の __左下の座標__ を __指定__ する __必要__ が __あります__。その座標をどのように設定すればよいかについて少し考えてみて下さい。

座標をどのように設定すればよいかが良くわからない場合は、先程説明したように、__ノート__ や __グラフ用紙__ などに、__想定する画像__ を __書いてみる__ ことを __強くお勧めします__。__下図__ は、__(0, 0)__ のマスに __〇 を配置__ した __ゲーム盤__ の __画像__ の __一例__ です。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/1e2a862b-268d-274d-ce5b-6735e435bd75.png" width="150">

__上図__ から、__この画像__ を __描画__ するためには、__`text`__ メソッドで __`"〇"`__ を __(0, 1)<sub>A</sub>__ の __座標付近__ に __描画__ すればよいことが __わかります__。__下記__ は __その処理__ を行うプログラムです。

* __3 行目__：__左下の座標__ が __(0, 1)<sub>A</sub>__ の __位置__ に __`"〇"`__ を __描画__ する

```python
1  def draw_board(self, size=3):
元と同じなので省略
2      # (0, 0) のマスに 〇 を描画する
3      ax.text(0, 1, "〇")
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()

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

    # (0, 0) のマスに 〇 を描画する
    ax.text(0, 1, "〇")

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

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

```diff_python
def draw_board(self, size=3):
元と同じなので省略
    # (0, 0) のマスに 〇 を描画する
+   ax.text(0, 1, "〇")

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/001e2d7b-4a82-5e15-8005-d7c98200dcc2.png" width="150">

__実行結果__ からわかるように、__〇__ は確かに __(0, 0)__ の __マスの中__ に __描画__ されますが、__文字__ の __大きさ__ が __小さすぎ__ ます。__文字__ の __大きさ__ は、__`text`__ メソッドの __`fontsize`__ という __キーワード引数__ で __設定__ できますが、__ちょうどいい大きさ__ の __〇 を描画__ するためには、__さまざまな大きさ__ で __〇 を描画__ し、__自分の目__ で __確かめる__ という、__試行錯誤__ を __行う必要__ が __あります__。

### 〇 のフォントサイズの調整

__〇__ の __フォントサイズ__ を __変更__ する際に、__毎回 `draw_board`__ メソッドを __定義し直す__ のは __大変__ です。そこで、__下記__ のプログラムの __5 行目__ のように、__`draw_board`__ メソッドが、その内部の処理で __作成__ した __Axes__ が __代入__ されている __`ax`__ を __返り値__ として __返す__ ように __修正__ することで、__`draw_board`__ メソッドを __実行した後__ で、__〇__ を __描画できる__ ようにします。

* __2 行目__ の __下__ に __記述__ されていた __(0, 0)__ に __〇 を描画__ する __処理__ を __削除__ する
* __5 行目__：__`ax`__ を __返り値__ として __返す__ ように __修正__ する

```python
1  def draw_board(self, size=3):
元と同じなので省略
2      # ここにあった、(0, 0) のマスに 〇 を描画するプログラムを削除する
3
4      # ax を返り値として返す
5      return ax
6
7  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") # 縦方向の枠線   

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

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

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

```diff_python
def draw_board(self, size=3):
元と同じなので省略
    # (0, 0) のマスに 〇 を描画する
-   ax.text(0, 1, "〇")
    
    # ax を返り値として返す
+   return ax

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

__上記__ のように __`draw_board`__ を __修正__ することで、__下記__ の __プログラム__ のように、__`draw_board`__ の __返り値__ である __Axes__ を使って、__ゲーム盤の枠線__ を __描画した後__ で、__引き続き 〇 を描画__ するプログラムを __記述できる__ ようになります。実行結果は先程と同じなので省略します。

```python
ax = mb.draw_board()
ax.text(0, 1, "〇")
```

__下記__ は、__for 文__ を使って、__`fontsize`__ に __20__、__30__、__40__、__50__、__60__ を __設定__ して __〇 を描画__ するプログラムです。なお、__実際__ の __実行結果__ は、__画像__ が __縦に並んで描画__ されますが、__わかりやすさ__ を __重視__ して、下記の __実行結果__ では、__画像__ を __横に並べて表記__ します。

```python
for size in range(20, 70, 10):
    ax = mb.draw_board()
    ax.text(0, 1, "〇", fontsize=size)
    plt.show()
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/4d2e8e81-5341-1f5b-f7ee-07d88ab6ec9f.png" width="600">

__実行結果__ から、__`fontsize`__ を __`50`__ にすると __ちょうど良さそう__ であることが __わかります__。なお、__どれくらい__ の大きさが __ちょうど良い__ かは、__人によって__ 感じ方が __異なる__ ので、__`50` 以外__ の大きさが __良い__ と __思った人__ は、__自由に変更__ しても __かまいません__。

### 〇 の描画位置の調整

__上記__ の __`fontsize`__ を __`50`__  に __設定__ した場合の __図__ から、__〇__ の __位置__ をもう __少し上に移動__ したほうが __よさそう__ であることが __わかります__。__大きさの調整__ と __同様__ に、__ちょうどよい位置__ は、__試行錯誤__ で __調べる必要__ が __あります__。

__下記__ は、__for 文__ を使って、__〇__ の __表示位置__ の __y 座標__ を __0.75__、__0.8__、__0.85__、__0.9__、__0.95__ に __設定__ して __〇 を描画__ するプログラムです。なお、__`range`__ は __整数しか扱えない__[^3] ので、__list__ を使って __繰り返し処理__ を __行っています__。

```python
for y in [0.75, 0.8, 0.85, 0.9, 0.95]:
    ax = mb.draw_board()
    ax.text(0, y, "〇", fontsize=50)
    plt.show()
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/c9afa633-bad5-d716-81f3-a139a80c9979.png" width="600">

__実行結果__ から、__y 座標__ を __`0.85`__ にすると __ちょうど良さそう__ であることが __わかります__。__この位置__ が __気に入らない__ 方は、__x 座標__ を __含めて自由に調整__ しても __かまいません__。

### 文字列による 〇 の描画の問題点

__上記の手順__ を見て、__かなり面倒__ だと __思った人__ が __多いのではないか__ と思いますが、__文字列__ による __〇 の描画__ には __他にも問題__ があります。その一つは __文字列__ の __描画__ では __〇__ の __枠の太さ__ を __柔軟に変更__ することが __できない__ という問題です。__`text`__ メソッドは、__`fontweight`__ という __キーワード引数__ を使って、__文字列の太さ__ を __変更できる__ のですが、__フォントの種類__ によっては、__設定__ を __行っても__ 文字列の __太さが変わらない__ 場合が __あるようです__。__実際__ に、__筆者のパソコン__ では、__下記__ のプログラムの __実行結果__ のように、__`fontweight`__ に __細字__ を表す __`"light"`__ と __太字__ を表す __`"bold"`__ を __設定__ しても __文字列の太さ__ は __変わりませんでした__。

```python
plt.text(0, 0, "abc", fontsize=50, fontweight="light")
plt.text(0.3, 0, "abc", fontsize=50, fontweight="bold")
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/5afb51bd-9640-8e07-d761-bfb55cdfe96d.png" width="200">

__他の問題点__ として、__画像の大きさ__ を __変更__ した場合に、__Axes__ の __表示範囲__ は __変化しない__ ので、__図形__ の __位置と大きさ__ はそれにあわせて __変化__ しますが、表示される __文字列の大きさ__ は __変わらない__ というものが __あります__（__文字列__ の __位置__ は __変化します__）。

下記は、__`draw_board`__ の実引数に __`size=5`__ を記述することで　__大きなゲーム盤__ を __描画した後__ で、__先程__ と __同じ位置__ と __大きさ__ で __〇 を描画__ するプログラムです。

```python
mb.draw_board(size=5)
plt.text(0, 0.85, "〇", fontsize=50)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/27b0d02b-2325-8951-a7e5-53062865595a.png" width="250">

__実行結果__ からわかるように、__画像のサイズ__ が __変化__ したことで、__枠線__ も __それに合わせた位置__ と __大きさ__ で __描画__ されています。一方、__〇__ の __文字列__ の __描画__ は __以下__ のように __行われます__。

* __〇__ の __左下__ の __座標__ の __位置__ は、__画像のサイズ__ の __変化__ に __合わせた位置__ で __表示__ される
* __〇__ の __文字列__ の __描画__ の __大きさ__ は、__画像のサイズ__ が __変化__ しても __変わらない__

従って、__マスの中__ での __〇__ の __描画位置__ が、__真ん中__ から __左下__ に __ずれて__ しまいます。この __問題を解決__ するためには、__再び__ 文字の大きさや位置を __試行錯誤で調整__ する __必要__ があるため __大変__ です。なお、__この問題__ は、__×__ を __文字列__ で __描画__ する際にも __発生__ します。興味がある方は、実際に × を同じ方法で描画してみて下さい。

__〇__ や __×__ のような __図形を描画__ する際に、__文字列__ を使って __描画__ を行うと、__いくつかの問題__ が __発生する__ ことが、__実感__ できたのではないでしょうか。

# 今回の記事のまとめ

__今回の記事__ では、__〇×ゲーム__ の __ゲーム盤__ の __枠線__ と、__文字列__ による __マーク__ の __描画__ の方法について説明しました。また、__文字列__ で __マーク__ を描画する方法の __欠点__ を説明しました。

__次回の記事__ では、__図形__ を使って __マーク__ を __描画__ する方法を __説明__ し、__ゲーム盤__ を __描画__ するプログラムを __完成__ させます。

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

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

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

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

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

# 次回の記事

https://qiita.com/ysgeso/items/8ac83f1b50b2706b8319

[^1]:__数学__ では、このような __両端がある線__ のことを、__線分__ と呼びますが、__一般的__ には __線分__ のことも __線と呼ぶ__ ことが __多い__ ので、__本記事__ では __線分__ のことも __線と表記__ します
[^2]:本記事では取り扱いませんが、Python には、__10 進数__ の __数値__ を __正確に表現__ するための、__[decimal](https://docs.python.org/ja/3/library/decimal.html)__ という __データ型__ が __あります__
[^3]:__整数以外__ の値を __扱う__ ことが __できる__、__`range`__ と __同様の機能__ を持つ __関数__ に、__numpy__ というモジュールの __`arange`__ という __関数__ が __あります__ が、__説明__ すると __長くなる__ ので、__今回の記事__ では __list を利用__ しました。numpy や `arange` については今後の記事で説明する予定です