Pythonで〇×ゲームのAIを一から作成する　その69　画像の描画のタイミングと丸めによる座標の変換

# 目次と前回の記事

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

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

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

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

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

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

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

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

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

# マウスによる着手の入力の実装

前回の記事で、ゲーム盤の __画像の上__ で __マウスを押した際__ に __処理を行う方法__ を紹介したので、その方法を使って __ゲーム盤上__ で __マウスを押す__ ことで __着手を行う処理__ を __実装__ します。

下記は、[前回の記事](https://qiita.com/ysgeso/items/1920c68f5a1e8a998796#%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E9%A7%86%E5%8B%95%E5%9E%8B%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E3%81%AB%E3%82%88%E3%82%8B-gui-%E3%82%A2%E3%83%97%E3%83%AA%E3%81%AE%E8%A8%98%E8%BF%B0%E6%96%B9%E6%B3%95)で説明した、__イベント駆動型プログラミング__ の __手法__ を __利用__ した __GUI__ の __アプリケーション__ の記述の __手順__ です。この手順に従って __`play`__ メソッドを __修正__ することで __実装__ を行います。

1. __画面内__ の適切な位置に __ウィジェットを配置__ する __処理を記述__ する
2. __それぞれ__ の __ウィジェット__ に対して、__下記の処理__ を __記述__ する 
    1. __ウィジェット__ を __操作__ した際に __行う処理__ を __記述__ する、__イベントハンドラ__ というプログラムを __記述__ する
    2. __ウィジェット__ に __イベントハンドラ__ を __結び付ける__

__最初__ に __人間どうし__ が __対戦__ する場合の __処理を実装__ し、__その後__ で __AI との対戦__ を __実装__ することにします。

## matplotlib の Figure の画像の描画が行われるタイミング

[前回の記事](https://qiita.com/ysgeso/items/1920c68f5a1e8a998796#%E3%83%9C%E3%82%BF%E3%83%B3%E3%81%AE%E3%82%A6%E3%82%A3%E3%82%B8%E3%82%A7%E3%83%83%E3%83%88%E3%82%92%E4%BD%9C%E6%88%90%E3%81%97%E3%81%A6%E9%85%8D%E7%BD%AE%E3%81%99%E3%82%8B)では、__ipywigets__ の __ボタンを配置__ する __例を紹介__ しましたが、__〇×ゲーム__ の場合は、__ゲーム盤の画像__ に対して __マウスで操作__ を行うので、__ゲーム盤の画像__ そのものが __GUI__ の __操作の対象__ となる __[ウィジェット](https://qiita.com/ysgeso/items/1920c68f5a1e8a998796#ipywidgets-%E3%81%AB%E3%82%88%E3%82%8B-gui)__ になります。

__`play`__ メソッドを __実行__ すると、__ゲームをリセットした後__ で、__ゲーム盤を描画__ するので、__`play`__ メソッドを __修正しなくても__、__ゲーム盤の画像__ が __描画されるはず__ です。

下記は、__`%matplotlib widget`__ を __実行__ し、__`play`__ メソッドに __`ai=[None, None]`__ と __`gui=True`__ を __記述__ して __人間どうしの対戦__ を、__GUI で行う__ プログラムです。

```python
%matplotlib widget
from marubatsu import Marubatsu

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

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

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/72c30760-66e5-5a93-e7f6-92579f223457.png" width="200">

### ゲーム盤が描画されない理由

__実行結果__ には、__matplotlib が作成__ した __Figure が描画__ されますが、__画像の中__ に __ゲーム盤__ が __描画されない__ という __問題が発生__ します。__その理由__ は、__以下__ の通りです。

* [前回の記事](https://qiita.com/ysgeso/items/1920c68f5a1e8a998796#%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%A9%E3%82%AF%E3%83%86%E3%82%A3%E3%83%96%E3%83%A2%E3%83%BC%E3%83%89)では、__`%matplotlib widget`__ を __実行__ すると、__matplotlib__ が __インタラクティブモード__ になるため、__Figure__ の __内容が更新__ されると、__自動的__ に __画像が更新__ されると __説明したが__、__正確__ には __画像__ の __描画の更新__ は、__[イベントループ](https://qiita.com/ysgeso/items/1920c68f5a1e8a998796#%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E9%A7%86%E5%8B%95%E5%9E%8B%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E3%81%AE%E4%BB%95%E7%B5%84%E3%81%BF) の中__ で __行われる__
* __イベントループ__ は、イベントループから呼び出された __イベントハンドラの処理__ が __行われている間__ は __中断__ され、__その処理__ が __すべて終了した後__ で __再開__ される


下記は、[前回の記事](https://qiita.com/ysgeso/items/1920c68f5a1e8a998796#%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E9%A7%86%E5%8B%95%E5%9E%8B%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E3%81%AE%E4%BB%95%E7%B5%84%E3%81%BF)で説明した __イベントループ__ が __行う処理__ の __手順 3__ に __上記の処理__ を __加えた__ ものです。

1. __ipywidgets__ に __登録__ された __イベントハンドラ__ に __対応するイベント__ が __発生しているか__ どうかを __調べる__
2. __イベント__ が __発生していれば__、__対応__ する __イベントハンドラ__ を __呼び出す__。__発生していなけれ__ ば __何もしない__
3. __Figure の描画__ を __更新__ する
4. __手順 1__ へ __戻る__

また、前回の記事では説明していませんでしたが、__JupyterLab のセル__ に __プログラムを記述__ して __実行した場合__ にも __イベントが発生__ し、__ipywidgets__ には __あらかじめ__、__そのイベント__ に __対応__ する __下記__ のような __イベントハンドラ__ が __登録__ されています。

* __JupyterLab__ の __セルに記述__ された __プログラムを実行__ する

従って、__JupyterLab のセル__ に __プログラムを記述__ して __実行した場合__ は、__その処理__ が __終了するまで__ の間は、__イベントループの処理__ が __中断される__ ため、__実行したプログラム__ が __すべて終了するまで__ の間は、__Figure__ の __描画の更新__ は __行われません__。

__先程実行__ した __`play` メソッド__ は、__ゲーム盤の描画__ を行う、__`draw_board`__ メソッドを __呼び出し__ ており、__`draw_board`__ メソッドの __最後__ で __`plt.show()`__ が __呼び出されています__。__`plt.show()`__ は、__イベントハンドラ__ の __処理の中で実行__ された場合でも __画像の描画__ を行うので、上記の __実行結果__ のように、__真っ白__ な __Figure が描画__ されますが、その __Figure に対して__ 行われた __`plot`__ メソッドなどによる __描画__ は、__`play`__ メソッドの __処理が終了__ して __JupyterLab__ の __セルの処理__ が __終了するまで__ は __更新されません__。__`play`__ メソッドは、__ゲームの決着がつく__ か、__exit__ を __入力するまで__ の間は __終了しない__ ため、__真っ白の画像__ が __描画される__ ことになります。

また、__`play`__ メソッドで表示される __テキストボックス__ に __exit を入力__ して __`play`__ メソッドを __終了__ すると、__JupyterLab__ の __セルの処理__ が __終了__ し、__イベントループ__ の処理が __再開__ されるようになるため、__下図__ のように __ゲーム盤の画像__ が __描画される__ ようになります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/fb0eba51-0d57-e82a-835f-133d8362b833.png" width="200">

__以下__ は、__上記__ を __まとめた__ ものです。

:::note warn
__ipywidgets__ と __ipympl__ を __利用__ した __[イベント駆動型プログラミング](https://qiita.com/ysgeso/items/1920c68f5a1e8a998796#%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E9%A7%86%E5%8B%95%E5%9E%8B%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0)__ では、__matplotlib__ の __Figure__ の __描画の更新__ は、__イベントループ内__ の __処理__ で __行われる__。

__イベントループの処理__ は、__イベントハンドラの処理__ が __行われている間__ は __中断__ され、__イベントハンドラの処理__ が __終了した後__ で __再開__ される。

__JupyterLab__ の __セルに記述__ された __プログラムの実行__ も、__イベントループ__ から __呼び出される__、__イベントハンドラの処理__ であるため、__セルのプログラム__ の __処理が終了するまで__ は、__Figure__ の __描画の更新__ は __行われない__。
:::

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

__上記__ のことから、__`gui=True`__ を記述して __GUI__ で __〇×ゲームを遊ぶ__ 場合は、__`play`__ メソッドで __ゲーム盤__ を __描画する処理__ を __行った後__ で、__`play`__ メソッドの __処理を終了__ する __必要__ が __あります__。__下記__ は __そのよう__ に __`play`__ メソッドを __修正__ したプログラムです。

* __10 行目__：__`gui` が `True`__ の場合に、__`draw_board`__ メソッドで __ゲーム盤__ を __描画した後__ に __return 文__ を記述して、__`play`__ メソッドの __処理を終了__ する
```python
 1  import matplotlib.pyplot as plt
 2
 3  def play(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False):
元と同じなので省略
 4          # ゲームの決着がついていない間繰り返す
 5          while self.status == Marubatsu.PLAYING:
 6              # ゲーム盤の表示
 7              if verbose:
 8                  if gui:
 9                      self.draw_board()
10                      return
11                  else:
12                      print(self)
元と同じなので省略                    
13    
14  Marubatsu.play = play
```

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

```python
import matplotlib.pyplot as plt

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()
                    return
                else:
                    print(self)
                    
            # 現在の手番を表す ai のインデックスを計算する
            index = 0 if self.turn == Marubatsu.CIRCLE else 1
            # ai が着手を行うかどうかを判定する
            if ai[index] is not None:
                x, y = ai[index](self, **params[index])
            else:
                # キーボードからの座標の入力
                coord = input("x,y の形式で座標を入力して下さい。exit を入力すると終了します")
                # "exit" が入力されていればメッセージを表示して関数を終了する
                if coord == "exit":
                    print("ゲームを終了します")
                    return       
                # x 座標と y 座標を要素として持つ list を計算する
                xylist = coord.split(",")
                # xylist の要素の数が 2 ではない場合
                if len(xylist) != 2:
                    # エラーメッセージを表示する
                    print("x, y の形式ではありません")
                    # 残りの while 文のブロックを実行せずに、次の繰り返し処理を行う
                    continue
                x, y = xylist
            # (x, y) に着手を行う
            try:
                self.move(int(x), int(y))
            except:
                print("整数の座標を入力して下さい")

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

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

```diff_python
import matplotlib.pyplot as plt

def play(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False):
元と同じなので省略
        # ゲームの決着がついていない間繰り返す
        while self.status == Marubatsu.PLAYING:
            # ゲーム盤の表示
            if verbose:
                if gui:
                    self.draw_board()
+                   return
                else:
                    print(self)
元と同じなので省略                    
    
Marubatsu.play = play
```
</details>

__修正後__ に __下記__ のプログラムを実行すると、今度は __`play`__ メソッドの __実行が終了__ し、__ゲーム盤の画像__ が __すぐに描画__ されるようになることが __確認__ できます。

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

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

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/a504828e-9f75-656a-807e-2e23cff950ca.png" width="200">

:::note
__上記__ のように、__`gui=True`__ の場合は、__ゲーム__ の __決着がつく前__ に __return 文__ で __`play`__ メソッドが __終了__ して __`play`__ メソッドの __返り値__ が __`None`__ になるため、__`gui=False`__（または、キーワード引数 __`gui` を省略__）の場合のように、__`play`__ メソッドの __返り値__ が、__ゲームの結果__ では __なくなります__。従って、__AI どうし__ の対戦の __通線成績__ を __計算する場合__ などで、__`play`__ メソッドの __対戦結果__ を __返り値__ として __知りたい場合__ は、__`gui=True`__ を __記述してはいけない__ 点に __注意__ して下さい。
:::

## イベントハンドラの定義とゲーム盤の画像への結び付け

次に、[前回の記事](https://qiita.com/ysgeso/items/1920c68f5a1e8a998796#matplotlib-%E3%81%AE-figure-%E3%81%AB%E5%AF%BE%E3%81%99%E3%82%8B%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E3%83%8F%E3%83%B3%E3%83%89%E3%83%A9%E3%81%AE%E7%99%BB%E9%8C%B2)で紹介した __方法__ を使って、__ゲーム盤の画像__ を __マウスで押した際__ の __処理__ を行う __イベントハンドラを定義__ します。__このイベントハンドラ__ は、__最終的__ には __マウスを押した__ ゲーム盤の __マスに着手__ を行う __処理__ を行いますが、__いきなり__ その処理を __記述__ するのは __難しい__ ので、__とりあえず__、__下記__ のプログラムのように、[前回の記事](https://qiita.com/ysgeso/items/1920c68f5a1e8a998796#%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E3%83%8F%E3%83%B3%E3%83%89%E3%83%A9%E3%81%AE%E5%AE%9A%E7%BE%A9-1)で記述した、__マウスを押した Axes の座標__ を __表示__ するプログラムとして __定義__ する事にします。

```python
def on_mouse_down(event):
    print(event.xdata, event.ydata)
```

次に、__ゲーム盤の画像__ と、この __イベントハンドラ__ を __結び付ける必要__ が __あります__。__ゲーム盤の画像__ は __`draw_board`__ メソッドで __作成__ しているので、__その処理の直後__ に __下記__ のプログラムのように、[前回の記事](https://qiita.com/ysgeso/items/1920c68f5a1e8a998796#mpl_connect-%E3%81%AE%E8%A8%98%E8%BF%B0%E6%96%B9%E6%B3%95)で紹介した __方法__ で、__ゲーム盤の画像__ の上で __マウスを押した時__ の __イベントハンドラ__ を __結び付ける処理__ を __記述__ します。

なお、__ゲーム盤の画像__ の「__タイトル__」、「__ツールバー__」、「__変形の操作のマーク__」の __表示__ は __〇×ゲーム__ の __ゲーム盤の画像__ には __必要がない__ ので、[前回の記事](https://qiita.com/ysgeso/items/1920c68f5a1e8a998796#%E3%82%BF%E3%82%A4%E3%83%88%E3%83%AB%E3%82%84%E3%83%84%E3%83%BC%E3%83%AB%E3%83%90%E3%83%BC%E3%81%AA%E3%81%A9%E3%81%AB%E9%96%A2%E3%81%99%E3%82%8B%E8%A8%AD%E5%AE%9A)で説明した __方法__ で、__表示しない__ ようにしました。ただし、__マウスの座標の表示__ は __残しておいたほうが__ この後のプログラムを記述する際に __便利__ なので、__しばらくの間__ は __表示を残しておく__ ことにします。

* __3 行目__：__`mpl_connect`__ を呼び出して、__ゲーム盤__ の __画像の上__ で __マウスを押した際__ に、__`on_mouse_down`__ の __イベントハンドラ__ を __呼び出す__ ようにする
* __4 ~ 6 行目__：__ゲーム盤の画像__ の「__タイトル__」、「__ツールバー__」、「__変形の操作のマーク__」の __表示__ を __行わないよう__ にする

```python
1  def draw_board(self, size=3):   
2      fig, ax = plt.subplots(figsize=[size, size])
3      fig.canvas.mpl_connect("button_press_event", on_mouse_down)
4      fig.canvas.toolbar_visible = False
5      fig.canvas.header_visible = False
6      fig.canvas.resizable = False
# 以下同じなので略
7
8  Marubatsu.draw_board = draw_board
```

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

```python
def draw_board(self, size=3):   
    fig, ax = plt.subplots(figsize=[size, size])
    fig.canvas.mpl_connect("button_press_event", on_mouse_down)
    fig.canvas.toolbar_visible = False
    fig.canvas.header_visible = False
    fig.canvas.resizable = False
        
    # 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):   
    fig, ax = plt.subplots(figsize=[size, size])
+   fig.canvas.mpl_connect("button_press_event", on_mouse_down)
+   fig.canvas.toolbar_visible = False
+   fig.canvas.header_visible = False
+   fig.canvas.resizable = False
# 以下同じなので略

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

上記の __修正後__ に __下記__ のプログラムを実行し、__描画__ された __画像の上__ で __マウスを押す__ と、__実行結果__ のように、__画像の下__ に __マウスを押した Axes__ の __座標が表示__ されるようになります。

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

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

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/92a2a82b-f74a-1529-b567-711aba7d2560.png" width="200">

## マウスを押したマスに着手を行う処理の記述

__上記__ では、__`on_mouse_down`__ の中で、__マウスを押した Axes__ の __座標を表示__ しましたが、マウスを押した __ゲーム盤のマス__ に __着手を行う__ ためには、__この座標__ を、__ゲーム盤__ の __マスの座標__ に __変換__ する __必要__ が __あります__。その __変換の方法__ について少し考えてみて下さい。

__変換の方法__ が良く __わからない__ 人は、__座標が表示__ された __ゲーム盤の図__ を __書いてみる__ と良いでしょう。__下図__ は、[以前の記事](https://qiita.com/ysgeso/items/442db53b5b7e22c1000c#draw_board-%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%AE%E4%BF%AE%E6%AD%A3-%E3%81%9D%E3%81%AE-2) で __描画__ した、__ゲーム盤__ の __枠__ に加えて __Axes の枠__ と __目盛り__ を __描画__ した際の __図__ です。

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

:::note
__画像を書く__ のが __面倒な場合__ は、__JupterLab__ の __画像の上__ で __マウスを移動__ した際に、__画像の下__ に __表示__ される __Axes の座標__ を __観察する__ という __方法__ もあります。
:::

__上図__ から、例えば、__ゲーム盤__ の __(0, 0)__ のマスの __座標の範囲__ は、__x 座標__ と __y 座標__ が __共に 0 ~ 1 の範囲__ であることが __わかります__。同様に、__(x, y)__ のマスの __座標の範囲__ は、__x 座標__ は __x ~ x + 1__、__y 座標__ は __y ~ y + 1__ になることが __わかります__。

__上記__ から、__マウス__ の __Axes 上__ の __座標__ の __小数点以下の数字__ を __切り捨て__ て  __整数__ に __変換する__ ことで、__ゲーム盤__ の __マスの座標__ に __変換できる__ ことが __わかりました__。

## 数値を整数に変換する方法とその注意点

__Python__ には、__数値__ を __整数__ に __変換する方法__ が __いくつかあります__ が、それぞれ __異なる処理__ を __行う__ ため、正しく __使い分ける必要__ が __あります__。__間違った方法__ を __利用__ してしまうと、思わぬ __バグの原因__ になる __場合がある__ ので __注意が必要__ です。

なお、__小数点以下__ の値が存在する __数値__ を __整数に変換する__ など、__数値の中__ で、__特定の桁数以下__ の __部分__ を __無くす処理__ の事を、__丸める__（round）と __呼びます__。

:::note
__以下__ の説明では、__整数__ に __変換__ する __場合のみ__ を __説明__ しますが、例えば「__100 の位__ で __四捨五入__」する、「__小数点以下 2 桁__ で __切り捨てる__」など、__特定の桁数__ の値に __変換する場合__ も __あります__。いずれも __桁数が異なるだけ__ で、__考え方__ は __変わりません__。
:::

### 切り捨て、切り下げ、床関数

「__切り捨て__」という __用語__ は、その __名前から__、__小数点以下__ の __数値__ を __無くす__ という意味だと __思っている人__ が __いるかもしれません__ が、__数学__ での __切り捨て__ はそうではなく、具体的には __下記__ のような __計算を行います__。

* __その値以下__ の、__最も大きな整数__ を __計算__ する

当然ですが、__元の数値__ が __整数の場合__ は、__その値__ そのものが __計算結果__ になります。

__整数でない__ 場合は、__数値__ が __0 以上であるか__ によって、__下記__ のような __計算__ が __行われます__。

* __0 以上__ の数値の場合は、__小数点以下__ の __数値を無くす__ 計算を行う
* __負の数値__ の場合は、__小数点以下__ の __数値を無くした後__ で __1 を引いた値__ を計算する

言葉の説明だけでは分かりづらいと思いますので、__切り捨て__ が行う __計算__ を __図で示します__。下図は、__数値__ を __縦方向の直線__ で __図示__ した場合に、__0 ~ 1 未満__ と __-1 ~ 0 未満__ の __範囲の数値__ に対する __切り捨て__ の __計算__ を表す図です。


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/97c5158b-0544-7a0f-e4d1-4038c385364a.png" width="250">

図を見れば、__切り捨て__ が行う __計算__ が、__複雑__ なもの __ではない__ ことがわかるのではないかと思います。また、__負の数値__ の場合は、__小数点以下__ の __数値を無くした後__ で __1 を引いた値__ になることも __理解できる__ のではないでしょうか。

下記は、__いくつかの数値__ に対して __切り捨て__ を行った場合の表です。

| 数値 | 切り捨て後の数値 |
|--:|--:|
| __1__  | 1  |
| __0.5__  | 0   |
| __0__ | 0 |
| __-0.5__  | -1  |
| __-1__ | -1 |

__切り捨て__ はという __表現__ は __紛らわしい__ ので、他にも「__切り下げ__」、「__負の無限大への丸め__」、「__床関数__」と呼ばれる場合があります。

__負の無限大への丸め__ は、__小数点以下__ の値が __存在する場合__ は、__上図__ のように __負の無限大__ に __近いほうの整数__ に __丸める計算__ が __行われる__ からです。

__床関数__ の __由来__ は、__先程の図__ で、__数値__ を __縦方向の直線__ で __表現__ した際に、__0 ~ 1 未満__ の数値を __0 にする計算__ が、__数値__ を __床におろして__ 0 にする __処理を行う__ ように __見える__ からです。

__Python__ では __切り捨て__ は、__数学__ に関する __様々な処理__ を行うことができる __math__ という __モジュール__ の、__`floor`__ という __関数__ で行えます。__`floor`__ は __床__ を表す __英語__ です。__下記__ のプログラムは、__`floor`__ の __使用例__ です。__上記の表__ と __同じ値__ が __計算__ されることが __確認__ できます。

```python 
import math

print(math.floor(1))
print(math.floor(0.5))
print(math.floor(0))
print(math.floor(-0.5)) # 0 ではなく、-1 が返される
print(math.floor(-1))
```

実行結果

```
1
0
0
-1
-1
```

__`floor`__ に関する詳細は、下記のリンク先を参照して下さい。

https://docs.python.org/3/library/math.html#math.floor

### 切り上げ、天井関数

__数学__ の「__切り上げ__」は、__下記__ のような __計算__ を行います。

* __その値以上__ の __最も小さな整数__ を __計算__ する

当然ですが、__元の数値__ が __整数の場合__ は、__その値__ そのものが __計算結果__ になります。

__整数でない__ 場合は、__数値__ が __0 以上であるか__ によって、__下記__ のような __計算__ が __行われます__。

* __0 以上__ の __数値__ の場合は、__小数点以下__ の __数値を無くした後__ で __1 を足した値__ を計算する
* __負の数値__ の場合は、__小数点以下__ の __数値を無くす__ 計算を行う

先程と同様に、__切り上げ__ が __行う計算__ を __図__ で示します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/1f79c56a-f253-5671-4386-f00f42a383a5.png" width="250">

下記は、__いくつかの数値__ に対して __切り上げ__ を行った場合の __表__ です。

| 数値 | 切り上げ後の数値 |
|--:|--:|
| __1__  | 1  |
| __0.5__  | 1   |
| __0__ | 0 |
| __-0.5__  | 0  |
| __-1__ | -1 |

__切り上げ__ は、他にも「__正の無限大への丸め__」、「__天井関数__」と __呼ばれる__ 場合があります。

__正の無限大への丸め__ は、__小数点以下__ の値が __存在する場合__ は、上図のように、__正の無限大__ に __近いほうの整数__ に __丸める計算__ が __行われる__ からです。

__天井関数__ の __由来__ は、__先程の図__ で、__数値__ を __縦方向の直線__ で __表現__ した際に、__0 より大きく 1 以下の数値__ を __1 にする計算__ が、__数値__ を __天井に上げて__ 1 にするという __処理を行う__ ように __見える__ からです。

__切り上げ__ は、__math__ モジュールの、__`ceil`__ という __関数__ で __行えます__。__`ceil`__ は、__天井__ を表す __ceiling__ という __英単語が由来__ です。__下記__ のプログラムは、__`ceil`__ の __使用例__ です。__上記の表__ と __同じ値__ が __計算される__ ことが __確認__ できます。

```python 
print(math.ceil(1))
print(math.ceil(0.5))
print(math.ceil(0))
print(math.ceil(-0.5)) # -1 ではなく、0 が返される
print(math.ceil(-1))
```

実行結果

```
1
1
0
0
-1
```

__`ceil`__ に関する詳細は、下記のリンク先を参照して下さい。

https://docs.python.org/3/library/math.html#math.ceil

### 偶数丸めによる四捨五入

〇×ゲームの座標の変換の際には利用しませんが、__四捨五入__ は __良く使われる丸めの計算__ で、__Python__ の __四捨五入__ を行う __関数__ は __少し特殊な計算__ を行うので __説明__ します。

__四捨五入__ は、__`round`__ という __組み込み関数__ で __計算__ できます（__math__ モジュールの __関数__ では __ありません__）。__`round`__ は、__丸め__ や __四捨五入__ を表す __round__ という __英単語が由来__ です。なお、__round__ という __用語__ に関しては __この後で補足__ します。

:::note alert
__四捨五入__ は、__一般的__ には、__下記の計算__ を行います。しかし、__Python__ の __`round`__ は、__下記__ のような __計算__ は __行わない__ 点に __注意__ して下さい。

* __小数点以下__ の __数値__ が __0.5 以下__ の場合は __切り捨てる__
* __小数点以下__ の __数値__ が __0.5 より大きい__ 場合は、__切り上げる__
:::

:::note warn
__Python__ の __`round`__ は、__下記__ の __計算__ を行います。

* __最も近い整数__ を __計算__ する
* __小数点以下__ の __数値__ が __0.5 の場合__ は、__最も近い整数__ のうち、__偶数を計算__ する

__このような四捨五入__ の __計算方法__ を __偶数丸め__[^1]と __呼びます__。
:::

このように、__Python__ の __`round`__ を __利用する際__ は、__一般的__ な __四捨五入__ とは __少しだけ異なる計算__ が __行われる点__ に __注意__ して下さい。

__下記__ は、__いくつかの数値__ に対して __偶数丸め__ による __四捨五入__ を __行った場合の表__ です。表のように、__小数点以下__ の __数値__ が __0.5 の場合__ は、__偶数の整数__ が __計算__ されます。

| 数値 | 切り上げ後の数値 |
|--:|--:|
| __2__  | 2  |
| __1.5__  | 2   |
| __1__  | 1  |
| __0.5__  | 0   |
| __0__ | 0 |
| __-0.5__  | 0  |
| __-1__ | -1 |
| __-1.5__  | -2  |
| __-2__ | -2 |

__下記__ のプログラムは、__`round`__ の __使用例__ です。__上記の表__ と __同じ値__ が __計算__ されます。

```python 
print(round(2))
print(round(1.5))
print(round(1))
print(round(0.5))
print(round(0))
print(round(-0.5))
print(round(-1))
print(round(-1.5))
print(round(-2))
```

実行結果

```
2
2
1
0
0
0
-1
-2
-2
```

__`round`__ に関する詳細は、下記のリンク先を参照して下さい。

https://docs.python.org/3/library/functions.html#round

### 0 への丸め

__小数点以下__ の __数値を無くす__ 計算のことを __0 への丸め__ と呼びます。__由来__ は、__小数点以下__ の __数値が存在__ した場合は、__下図__ のように、__0 に近いほうの整数__ に __丸めが行われる__ からです。__数値__ が __正の場合__ と __負の場合__ で、下図の __矢印の方向__ が __異なる点__ に __注目__ して下さい。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/c6cd582c-1b0f-7c9f-6ca8-50dce632d1fe.png" width="250">

__Python__ では、__math__ モジュールの __`trunc`__[^2] という __関数__ で __0 への丸め__ を __計算__ できます。下記は、__`trunc`__ の使用例です。__`floor`__ と __`trunc`__ を __混同しない__ ように __注意__ して下さい。

```python 
print(math.trunc(1))
print(math.trunc(0.5))
print(math.trunc(0))
print(math.trunc(-0.5))
print(math.trunc(-1))
```

実行結果

```
1
0
0
0
-1
```

__`trunc`__ に関する詳細は、下記のリンク先を参照して下さい。

https://docs.python.org/3/library/math.html#math.ceil

__0 への丸め__ は、__整数型__ の __クラス__ を表す __`int`__ を使って __行う__ こともできます。__下記__ は、__`int`__ の __使用例__ です。__`math.trunc`__ と __同じ計算__ が __行われます__。

```python 
print(int(1))
print(int(0.5))
print(int(0))
print(int(-0.5))
print(int(-1))
```

実行結果

```
1
0
0
0
-1
```

__`int`__ と __`math.trunc`__ の __違い__ は、__`int`__ が __実引数__ に __文字列__ を __記述できる__ 点です。実際に、__下記__ のプログラムのように、__`int`__ は __文字列の `"1"`__ を __整数の `1`__ に __変換__ できます。

```python
print(int("1"))
```

実行結果

```
1
```

一方、__`math.trunc`__ の __実引数__ に __文字列の `"1"`__ を __記述__ すると、__下記__ のプログラムのように __エラーが発生__ します。

```python
print(math.trunc("1"))
```

実行結果

```
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[13], line 1
----> 1 print(math.trunc("1"))

TypeError: type str doesn't define __trunc__ method
```

__`int`__ に関する詳細は、下記のリンク先を参照して下さい。

https://docs.python.org/3/library/functions.html#int

### 丸めに関する英語の表記の補足

__英語__ の __`round`__ は、__四捨五入__ という __意味__ で使われることが __実際にあります__ が、__切り捨てなど__ を __含めた__「__丸め__」という __意味__ で __使われること__ も __あります__。__round__ という __用語__ が表す __丸めの種類__ を __明確__ にしたい場合は、__下記__ の表のように、__round__ に __丸めの種類__ を表す __単語__ を __組み合わせる__ ことで __区別__ します[^3]。なお、__紛らわしい__ ですが、__場合によって__ は __round off__ を __四捨五入__ の __意味で使う__ こともあるようです。

| 日本語 | 英語 |
|:--|:--|
| __切り捨て__、__負の無限大への丸め__  | round up、round towards negative infinity  |
| __切り上げ__、__正の無限大への丸め__  | round down、round towards positive infinity|
| __一般的な四捨五入__  | round off、round half up |
| __偶数丸めによる四捨五入__  | round half to even  |
| __0 への丸め__  | round towards 0   |

__さまざま__ な __丸め処理__ を __扱った記事__ がありましたのでリンクを __紹介__ します。

https://qiita.com/tobira-code/items/d70093c4ad6c3e3e214f

## 〇×ゲームの座標の変換で利用する丸めの種類

__数値__ を __整数に変換__ する __丸めの方法__ を __紹介__ しましたが、__どの方法__ で、__Axes 上__ の __マウスの座標__ を __ゲーム盤__ の __マスの座標__ に __変換すればよいか__ について少し考えてみて下さい。

__切り上げ__ と __四捨五入__ は __明らか__ に __ふさわしくない__ ので、__候補__ としては __切り捨て__ と __0 への丸め__ の __2 つ__ が挙げられます。__どちらが良いかわからない__ 場合は、__実際__ に __それぞれの方法__ で __処理を行う__ プログラムを __記述__ して __確認する__ と良いでしょう。

### 0 への丸めによる座標の変換

__下記__ は、__0 への丸め__ を行う __`int`__ を使って、__Axes 上__ の __マウスを押した座標__ の __小数点以下の数値__ を __削除__ した __値を表示__ するように、__`on_mouse_down`__ を __修正__ したプログラムです。

```python
def on_mouse_down(event):
    print(int(event.xdata), int(event.ydata))
```

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

```diff_python
def on_mouse_down(event):
-   print(event.xdata, event.ydata)
+   print(int(event.xdata), int(event.ydata))
```
</details>

__上記__ の __修正後__ に、__下記__ のプログラムを実行し、__ゲーム盤の画像__ の上の __さまざまな場所__ で __マウス__ を __押してみて__ 下さい。

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

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/66ea3bee-23b8-0ab3-2f85-d21dce49d578.png" width="200">

__上図__ は、__ゲーム盤__ の __(1, 1)__、__(2, 1)__、__(0, 1)__ の __マスの上__ で __マウスを押した__ 場合の図です。__図の下__ にそれらの __マスの座標__ が __正しく表示__ されているので、__一見する__ と __問題がない__ ように __思えるかもしれません__。しかし、__ゲーム盤の外__ で __マウスを押す__ と、__下記__ のような __エラーが発生__ してしまいます。このエラーの原因について少し考えてみて下さい。

なお、__どの部分__ で __マウスを押す__ と __エラーが発生するか__ が良く __わからない__ 人がいるのではないかと思いますので、__この後__ で __エラーが発生する場所__ が __分かるよう__ にします。

```
略
Cell In[14], line 2
      1 def on_mouse_down(event):
----> 2     print(int(event.xdata), int(event.ydata))

TypeError: int() argument must be a string, a bytes-like object or a real number, not 'NoneType'
```

:::note
__これまで__ のプログラムは、__エラーが発生__ すると、__その時点__ で __プログラムの処理__ が __完全に終了__ しましたが、__イベント駆動型プログラミング__ では、__イベントハンドラ__ で __エラーが発生__ しすると、__イベントループ__ の __中断が再開__ されるため、__イベントループ__ の __処理__ は __引き続き実行__ されます。そのため、__上記__ の __エラーが発生した後__ に __ゲーム盤の上__ で __マウスを押す__ と、新しく __イベントハンドラ__ が __実行__ されて、__エラーメッセージの後__ に __マウスの座標が表示__ されます。
:::

### エラーの原因

__エラーメッセージ__ から、__`int`__ の __実引数__ に __記述__ した __`event.xdeata`__ の値が、__`NoneType`__、すなわち __`None` になっている__ ことが __原因__ であることが __推測__ されます。

__上記__ のプログラムで __表示__ される __ゲーム盤の画像__ ではこの __エラーの原因__ が __わかりにくい__ ので、__`draw_board`__ メソッドの __最初の行__ を、__下記__ のプログラムのように __変更__ して __Figure__ の __背景色__ を __水色__ にし、__Axes__ の __枠と目盛り__ の __描画を削除__ する __処理__ を __コメント__ にして __実行しない__ ようにすることで、__原因__ が __わかりやすく__ なります。

* __2 行目__：__`subplots`__ の __実引数__ に、__`facecolor="lightblue"`__ を __記述__ することで、__作成__ する __Figure__ の __背景色__ を __水色__ にする
* __12 行目__：__`ax.axis("off")`__ を __コメントにする__ ことによって、__Axes__ の __枠と目盛り__ が __表示される__ ようにする

```python
 1  def draw_board(self, size=3):
 2      fig, ax = plt.subplots(figsize=[size, size], facecolor="lightblue")
 3      fig.canvas.mpl_connect("button_press_event", on_mouse_down)
 4      fig.canvas.toolbar_visible = False
 5      fig.canvas.header_visible = False
 6      fig.canvas.resizable = False
 7       
 8      # y 軸を反転させる
 9      ax.invert_yaxis()
10
11      # 枠と目盛りを表示しないようにする
12      #ax.axis("off")
以下同じなので略
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], facecolor="lightblue")
    fig.canvas.mpl_connect("button_press_event", on_mouse_down)
    fig.canvas.toolbar_visible = False
    fig.canvas.header_visible = False
    fig.canvas.resizable = False
        
    # 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):
-   fig, ax = plt.subplots(figsize=[size, size])
+   fig, ax = plt.subplots(figsize=[size, size], facecolor="lightblue")
    fig.canvas.mpl_connect("button_press_event", on_mouse_down)
    fig.canvas.toolbar_visible = False
    fig.canvas.header_visible = False
    fig.canvas.resizable = False
        
    # y 軸を反転させる
    ax.invert_yaxis()

    # 枠と目盛りを表示しないようにする
-   ax.axis("off")
+   #ax.axis("off")
以下同じなので略

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

__上記__ の __修正後__ に、__下記__ のプログラムを実行すると、__実行結果__ のような __ゲーム盤の画像__ が __表示__ されるようになります。

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

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

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/a97452c6-4386-6278-783d-8ab314dbc70d.png" width="200">

__画像__ の __白い部分__ が、__Figure__ に __登録__ された __Axes__ の __表示の範囲__ です。__この範囲の外__ の、__水色の部分__ は __Axes__ の __表示__ の __範囲の外__ なので、__水色の部分__ の上で __マウスを押す__ と、[前回の記事](https://qiita.com/ysgeso/items/1920c68f5a1e8a998796#%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E3%83%8F%E3%83%B3%E3%83%89%E3%83%A9%E3%81%AE%E5%AE%9A%E7%BE%A9-1)で説明したように、__イベントハンドラ__ の __仮引数__ の __`xdata`__ と __`ydata` 属性__ の __値__ は __`None`__ になります。そのことは、__JupyterLab__ に __描画__ された上図の __水色の部分__ に __マウスを移動__ した際に、__図の下__ に __マウスの座標__ が __描画されない__ ことからも __確認__ できます。

__上図__ の __白い部分__ で __マウスを押す__ と __図の下__ に __マスの座標__ が __表示__ され、__水色の部分__ で __マウスを押す__ と __エラーが発生する__ ことを __確認__ してみて下さい。

### エラーの修正

[前回の記事](https://qiita.com/ysgeso/items/1920c68f5a1e8a998796#%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E3%83%8F%E3%83%B3%E3%83%89%E3%83%A9%E3%81%AE%E5%AE%9A%E7%BE%A9-1)で説明したように、__マウス__ を __押した場所__ が、__Axes__ の __上であるか__ どうかは、__イベントハンドラ__ の __仮引数__ の __`inaxes`__[^4] という __属性__ で __調べる__ ことが __できます__。従って、__`on_mouse_down`__ を、__下記__ のプログラムの __3 行目__ のように __修正__ することで、__Axes の中__ で __マウス__ を __押した場合だけ__、__マスの座標__ を __表示する__ ようになります。

```python
def on_mouse_down(event):
    # Axes の上でマウスを押していた場合のみ処理を行う
    if event.inaxes:
        print(int(event.xdata), int(event.ydata))
```

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

```diff_python
def on_mouse_down(event):
-   print(int(event.xdata), int(event.ydata))
    # Axes の上でマウスを押していた場合のみ処理を行う
+   if event.inaxes:
+       print(int(event.xdata), int(event.ydata))
```
</details>

__上記の修正後__ に、__下記__ のプログラムを実行し、__ゲーム盤__ の __画像の上__ の __さまざまな場所__ で __マウスを押して__ みて下さい。表示される画像は先ほどと同じなので実行結果は省略しますが、今度は __白色__ の __Axes の中__ で __マウス__ を __押した時だけ__ 下部に __マスの座標__ が __表示__ され、__水色__ の __Axes の外__ で __マウスを押して__ も __エラー__ が __発生しなくなります__。

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

### 0 への丸めの問題点

初心者の方には気が付きづらいと思いますが、__0 への丸め__ による __座標の変換__ には __1 つ問題__ が __あります__。それは、__Axes の中__ の、__ゲーム盤__ の __外の部分__ で __マウスを押す__ と、__間違ったマスの座標__ に __変換される__ というものです。

[以前の記事](https://qiita.com/ysgeso/items/442db53b5b7e22c1000c#axes-%E3%81%AE-%E8%A1%A8%E7%A4%BA%E7%AF%84%E5%9B%B2%E3%81%AB%E9%96%A2%E3%81%99%E3%82%8B%E8%A3%9C%E8%B6%B3)で説明したように、__matplotlib__ で __Axes__ に __`plot`__ メソッドなどで __描画__ を __行った場合__ は、__Axes__ の __描画範囲__ は、__Axes__ に __登録__ した __すべての図形__ が __ぴったり収まる__ ような __範囲より__ も __少しだけ広い範囲__ になるように __自動的__ に __設定__ されます。〇×ゲームの __ゲーム盤の画像__ の場合は、__Axes__ の __x 座標__ と __y 座標__ の __範囲__ は、[以前の記事](https://qiita.com/ysgeso/items/442db53b5b7e22c1000c#axes-%E3%81%AE-%E8%A1%A8%E7%A4%BA%E7%AF%84%E5%9B%B2%E3%81%AB%E9%96%A2%E3%81%99%E3%82%8B%E8%A3%9C%E8%B6%B3)で __確認__ したように、 __-0.15 ~ 3.15__ が __設定__  されます。

__下図__[^5]の __黄色__ と __緑__ の __部分__ が、__Axes の中__ で、__ゲーム盤__ の __外の部分__ を表します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/75b2d154-b56c-64c0-600d-2727f91a7a2e.png" width="200">

__0 への丸め__ で __座標の変換__ を行った場合は、この __図__ の __黄色い部分__ で __マウスを押した__ 際に、__間違った座標__ が __表示__ されます。__黄色い部分__ は、__Axes__ の __x 座標__ または __y 座標__ が __-0.15 ~ 0 の範囲__ の __負の座標__ になりますが、__その範囲の数値__ は __0 への丸め__ で __整数に変換__ を行うと、__0__ になります。そのため、__黄色い部分__ で __マウスを押す__ と、__ゲーム盤の外__ であるにも __関わらず__、__(0, 0)__ のような __ゲーム盤の中__ の __座標__ が __表示__ されてしまいます。

__実際__ に、__JupyterLab__ に __表示__ される __ゲーム盤の画像__ の中で、__上図__ の __黄色い部分__ に __相当__ する場所で __マウスを押して__、そのことを __確認__ してみて下さい。

__上記__ の事から、__0 への丸め__ で __座標の変換__ を行ってしまうと、__ゲーム盤の外__ で __マウスを押した際__ に __着手が行われてしまう__ という、__間違った処理__ が __行われてしまう__ ことになります。

:::note
__上記__ の問題が __発生する理由__ は、__0 への丸め__ では、__0 ~ 1__ と __-1 ~ 0__ の __範囲の数値__ が、__どちらも 0__ に __なってしまう__ という __性質がある__ からです。__切り捨て__ と __切り上げ__ では、__異なる整数の間__ の範囲の __数値__ が __同じ値になる__ ことは __ありません__。
:::

__このような問題__ は、__些細なこと__ なので __どうでも良い__ のではないかと __思った人__ が __いるかもしれません__ が、__些細に思える問題__ を __放置__ することで、後で __思わぬバグの原因になる__ ことが __実際にある__ ので、__この問題__ は __修正__ しておいたほうが __良い__ でしょう。

### 切り捨て（床関数）による座標の変換

__上記の問題__ は、__下記__ のプログラムのように、__`math.floor`__ による __切り捨て__ によって __座標を変換__ することで __解決__ できます。

```python
def on_mouse_down(event):
    # Axes の上でマウスを押していた場合のみ処理を行う
    if event.inaxes:
        print(math.floor(event.xdata), math.floor(event.ydata))
```

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

```diff_python
def on_mouse_down(event):
    # Axes の上でマウスを押していた場合のみ処理を行う
    if event.inaxes:
-   print(int(event.xdata), int(event.ydata))
+   print(math.floor(event.xdata), math.floor(event.ydata))
```
</details>

__上記__ の __修正後__ に、__下記__ のプログラムを実行し、__先程の図__ の __黄色い部分__ に __相当する場所__ で __マウスを押す__ と、__下記__ の __実行結果__ のように、__x 座標__ や __y 座標__ が __負の値__ になり、__ゲーム盤の中__ の __マス__ とは __異なる座標__ が __表示される__ ようになります。

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

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

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/86483fdc-73da-adb2-0672-c387e2396a53.png" width="200">

:::note
本記事では採用しませんが、__この問題__ を __解決__ する __別の方法__ として、__Axes__ の __x 座標__ と __y 座標__ の __表示範囲__ を __0 ~ 3__ に __設定__ して __ゲーム盤__ の __範囲外__ を __表示しない__ ようにするという __方法__ があります。

ただし、その方法を採用した場合でも、__後から__ ゲーム盤の __範囲外__ を __表示__ する __可能性がある__ のであれば、__切り捨て__ による __座標の変換__ を __行ったほうが良い__ でしょう。
:::

### `draw_board` の修正

__問題が解決__ されたので、__`draw_board`__ を __下記__ のプログラムのように、__Figure__ の __背景色__ を __白に戻し__、__枠と目盛り__ を __表示しない__ ようにします。また、__画像の下部__ に表示される __マウス__ の __Axes の座標__ はもう __必要がなくなった__ ので、__表示しない__ ように __修正__ します。

* __2 行目__：__`facecolor="lightblue"`__ を __削除__ して __Figure__ の __背景色__ を __白に戻す__
* __6 行目__：__画像の下部__ の __マウス__ の __Axes の座標の表示__ を __行わないよう__ にする
* __13 行目__：__コメントを削除__ して、__枠と目盛り__ を __表示しない__ ようにする

```python
 1  def draw_board(self, size=3):
 2      fig, ax = plt.subplots(figsize=[size, size])
 3      fig.canvas.mpl_connect("button_press_event", on_mouse_down)
 4      fig.canvas.toolbar_visible = False
 5      fig.canvas.header_visible = False
 6      fig.canvas.footer_visible = False
 7      fig.canvas.resizable = False
 8       
 9      # y 軸を反転させる
10      ax.invert_yaxis()
11
12      # 枠と目盛りを表示しないようにする
13      ax.axis("off")
以下同じなので略
14
15  Marubatsu.draw_board = draw_board
```

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

```python
def draw_board(self, size=3):   
    fig, ax = plt.subplots(figsize=[size, size])
    fig.canvas.mpl_connect("button_press_event", on_mouse_down)
    fig.canvas.toolbar_visible = False
    fig.canvas.header_visible = False
    fig.canvas.footer_visible = False
    fig.canvas.resizable = False
        
    # 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):   
-   fig, ax = plt.subplots(figsize=[size, size], facecolor="lightblue")
+   fig, ax = plt.subplots(figsize=[size, size])
    fig.canvas.mpl_connect("button_press_event", on_mouse_down)
    fig.canvas.toolbar_visible = False
    fig.canvas.header_visible = False
+   fig.canvas.footer_visible = False
    fig.canvas.resizable = False
        
    # y 軸を反転させる
    ax.invert_yaxis()

    # 枠と目盛りを表示しないようにする
-   #ax.axis("off")
+   ax.axis("off")
以下同じなので略

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

__上記__ の __修正後__ に、__下記__ のプログラムを実行し、__実行結果__ から __意図通り__ の __ゲーム盤__ の __画像が表示される__ ことが __確認__ できました。


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

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

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/be193a79-5ee8-6a26-5818-0c9a0865ce87.png" width="150">


# 今回の記事のまとめ

今回の記事では、__以下__ の内容を __説明__ しました。

* __`%matplotlib widget`__ を __実行__ した場合に、__matplotlib__ の __画像__ の __描画が更新__ される __タイミング__
* __数値__ を __整数に変換__ する __丸めの種類__ と __注意点__
* __ゲーム盤の画像__ の上で __マウスを押した場所__ の、__ゲーム盤__ の __マスの座標__ への __変換方法__

次回の記事では、__マウス__ を __押した場所__ に __着手を行う処理__ を __記述__ します。

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

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

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

以下のリンクは、今回の記事で更新した marubatsu.py です。なお、__`on_mouse_down`__ は __先頭に記述__ しました。

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

# 次回の記事

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

# 更新履歴

| 更新日時 | 更新内容 |
|:--|:--|
| 2024/4/5 | `gui=True` の場合に、`play` メソッドの返り値が `None` になる点に関するノートを追記しました |

[^1]:__銀行員__ が __良く使っていた__ ので、「__銀行丸め__」 と __呼ぶ場合__ も __あります__
[^2]:__`trunc`__ は、__切り詰める__ という __意味__ の __truncate__ という __英単語の略__ です
[^3]:この __表以外__ の __英語表記__ も __存在します__
[^4]:__`inaxes`__ の名前は、__Axes__ の __中__（__in__）に __あるか__ どうかという __意味__ です
[^5]:この __図__ は __説明のため__ に __独自に作った図__ なので、__JupyterLab__ には __表示されません__