Pythonで〇×ゲームのAIを一から作成する　その37　〇×ゲームを遊ぶためのメソッドの定義

# 目次と前回の記事

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

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

# 前回までのおさらい

## 〇×ゲームの仕様と進捗状況

1. `正方形で区切られた 3 x 3 の 2 次元のゲーム盤上でゲームを行う`
2. `ゲーム開始時には、ゲーム盤のすべてのマスは空になっている`
3. `2 人のプレイヤーが遊ぶゲームであり、一人は 〇 を、もう一人は × のマークを受け持つ`
4. `2 人のプレイヤーは、交互に空いている好きなマスに自分のマークを 1 つ置く`
5. `先手は 〇 のプレイヤーである`
6. `プレイヤーがマークを置いた結果、縦、横、斜めのいずれかの一直線の 3 マスに同じマークが並んだ場合、そのマークのプレイヤーの勝利とし、ゲームが終了する`
7. `すべてのマスが埋まった時にゲームの決着がついていない場合は引き分けとする`

仕様の進捗状況は、以下のように表記します。

* 実装が完了した部分を `背景が灰色の長方形` で記述する
* 実装の一部が完了した部分を、__太字__ で記述する

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

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

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

なお、今回の記事では test.py は使用しないので test.py は割愛します。

## 前回までのおさらい

前回の記事までで、`judge` メソッドを実装するための、さまざまなアルゴリズムを紹介しました。今回の記事では〇×ゲームを実際に遊ぶためのメソッドを定義します。

# 〇×ゲームを遊ぶためのメソッドの定義

前回の記事で、〇×ゲームの __すべての仕様__ の __実装が完了__ しました。現時点では、〇×ゲームの AI を実装していないので、AI を相手に〇×ゲームの対戦を行うことはまだできませんが、〇 と × の __両方__ を __人間が担当__ すれば 〇×ゲームを __実際に遊ぶ__ ことができます。

しかし、現状では、〇×ゲームを遊ぶために、`Marubatsu` クラスの __インスタンスを作成した後__ で、下記の __アルゴリズム__ で Python の __プログラムを記述__ し、__実行する必要__ があります。

1. __`restart`__ メソッドを使って、〇×ゲームを __再起動__ する
2. `print` を使って __ゲーム盤を表示__ する
3. __プレイヤーに__ 着手を行う __座標を入力__ させる
4. プレイヤーが __入力した座標__ に __`move`__ メソッドで __着手を行う__
5. __`judge`__ メソッドで __勝敗判定__ を行い、ゲームの __決着がついていない場合__ は __手順 2 へ戻る__  

下記のプログラムは、上記の __アルゴリズム__ を __実際に記述__ して〇×ゲームを遊んだ場合の __例__ ですが、〇×ゲームを __遊ぶ際__ に、下記のようなプログラムを __記述して実行__ するのは __大変__ なので、このままでは〇×ゲームを __気軽に遊ぶ__ ことは __できません__。そこで、〇×ゲームを遊ぶ際に必要となる、上記の __アルゴリズム__ の __処理__ を行う __メソッドを定義__ することにします。

なお、下記のプログラムのそれぞれの処理が、上記のアルゴリズムのどの部分に対応しているかについて、コメントに記しました。

```python
from marubatsu import Marubatsu

# Marubatsu クラスのインスタンスを作成する
mb = Marubatsu()

# ここから〇×ゲームのプレイを開始する
# 手順 1：〇× ゲームを再起動する
mb.restart()
# 手順 2：ゲーム盤の表示
print(mb)
# 手順 3：(1, 1) への着手を表すデータの入力
x = 1
y = 1
# 手順 4：move メソッドによる着手
mb.move(x, y)
# 手順 5：judge メソッドによる勝敗判定
print(mb.judge())
# 上記で決着がついていないことを表す playing が表示されるので手順 2 に戻る
# 手順 2：ゲーム盤の表示
print(mb)
# 手順 3：(0, 0) への着手を表すデータの入力
x = 0
y = 0
# 手順 4：move メソッドによる着手
mb.move(x, y)
# 手順 5：judge メソッドによる勝敗判定
print(mb.judge())
# 以下決着がつくまで繰り返す
```
実行結果
```
Turn o
...
...
...

playing
Turn x
...
.o.
...

playing
```


## `play` メソッドの定義

具体的には、以下のような __メソッドを定義__ します。

* __処理__：〇×ゲームを遊ぶために必要な上記の __手順 1 ~ 5 の処理__ を行う
* __名前__：〇×ゲームを __遊ぶ__ （play）ための __処理__ を行うので、__`play`__ とする
* __入力__：なし
* __出力__：なし

なお、`play` メソッドは __返り値を返さない__ ので __出力__ の所に「__なし__」と記述しましたが、__画面に文字列を表示__ するという __意味__ での __出力は行います__。

このメソッドの定義は下記のプログラムのようになります。今回の記事では、__手順 1 から順番__ に `play` メソッドを __実装__ を行います。

```python
def play(self):
    # 手順 1 の処理
    # 手順 2 の処理
    # 手順 3 の処理
    # 手順 4 の処理
    # 手順 5 の処理

Marubatsu.play = play
```

## 手順 1 と 2 の実装

__手順 1__ の「__`restart` メソッドを使って、〇×ゲームを再起動する__」は、下記のプログラムの 3 行目のように記述できます。__`play`__ メソッドを __呼び出した際__ に、__仮引数 `self`__ に、__`play`__ メソッドを __呼び出した__  `Marubatsu` クラスの __インスタンスが代入__ されるので、__`self.restart()`__ によって __〇×ゲームの再起動__ を行うことができます。

__手順 2__ の「__`print` を使ってゲーム盤を表示する__」は、下記のプログラムの 5 行目のように記述できます。特に難しい点はないと思いますので説明は省略します。

```python
 1  def play(self):
 2      # 〇×ゲームを再起動する
 3      self.restart()
 4      # ゲーム盤の表示
 5      print(self)
 6      # 手順 3 の処理
 7      # 手順 4 の処理
 8      # 手順 5 の処理
 9
10  Marubatsu.play = play
```

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

```python
def play(self):
    # 〇×ゲームを再起動する
    self.restart()
    # ゲーム盤の表示
    print(self)
    # 手順 3 の処理
    # 手順 4 の処理
    # 手順 5 の処理

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

下記は、`play` メソッドの手順 1 と 2 の処理が __正しく実装__ できたかどうかを __確認__ するためのプログラムです。3 行目で、(1, 1) のマスに __着手__ を行っているのは、`play` メソッドを実行することで、__手順 1__ の 〇×ゲームの __再起動__ が行われているかどうかが __確認__ できるようにするためです。実行結果から、__〇 の手番__ で、マークが __一つも配置されていない__ ゲーム盤が __表示__ されるので、手順 1 と 手順 2 が正しく実装されていることが確認できます。

```python
mb = Marubatsu()

mb.move(1, 1)
mb.play()
```
実行結果
```
Turn o
...
...
...
```

## 手順 3 の実装と組み込み関数 `input`

__手順 3__ の「__プレイヤーに着手を行う座標を入力させる__」を実装するためには、プレイヤーが __どのような方法__ で __座標を入力__ するかについての、[__ユーザーインタフェース__](https://qiita.com/ysgeso/items/1b7b1abe942e2d10c0b4#%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%BC%E3%83%95%E3%82%A7%E3%83%BC%E3%82%B9)（以下 UI と表記します)を __決める__ 必要があります[^1]。現時点では、〇×ゲームの __ゲーム盤を表示__ する __UI__ は、__文字だけ__ で行う __CUI__ なので、__座標の入力__ の __UI__、文字だけを使った __CUI で実装__ することにします。なお、ゲーム盤を画像で表示し、マウスを使って座標を入力する GUI については今後の記事で実装する予定です。

Python には、__キーボードから入力__ した __文字列__ を __返り値として返す__ という、__CUI の入力処理__ を行う __`input`__ という __組み込み関数__ があります。__VSCode の JupyterLab__[^2] では、__`input` を呼び出す__ すると、下図のように VSCode の __タイトルバーの付近__ に __テキストボックスが表示__ され、そのテキストボックスにキーボードから __文字を入力__ できるようになります。

```python
print(input())
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/c68b030f-2540-cde7-48d2-db6f90e14af1.png" width="350">

`input` を呼び出すと、上図に記述されているように、__Enter キー__ または、__Escape キー__ を __押すまで__ プログラムの __処理が中断__ され、__Enter キー__ を押した場合はテキストボックスに __入力した文字列__  が、__Escape キー__ を押した場合は __空文字__ が __返り値__ として返されます。 

下記は、上記のプログラムを実行し、テキストボックスに __`"abc"` を入力__ して __Enter キー__ を押した場合の実行結果です。

```
abc
```

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

https://docs.python.org/ja/3/library/functions.html?highlight=sum#input

手順 3 は、下記のプログラムの 7 行目のように実装できます。下記のプログラムでは、キーボードから入力した座標を表す文字列を __`coord`__ という __ローカル変数に代入__ しています。

```python
 1  def play(self):
 2      # 〇×ゲームを再起動する
 3      self.restart()
 4      # ゲーム盤の表示
 5      print(self)
 6      # キーボードからの座標の入力
 7      coord = input()
 8      # 手順 4 の処理
 9      # 手順 5 の処理
10
11  Marubatsu.play = play
```

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

```python
def play(self):
    # 〇×ゲームを再起動する
    self.restart()
    # ゲーム盤の表示
    print(self)
    # キーボードからの座標の入力
    coord = input()
    # 手順 4 の処理
    # 手順 5 の処理

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

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

```diff_python
def play(self):
    # 〇×ゲームを再起動する
    self.restart()
    # ゲーム盤の表示
    print(self)
    # キーボードからの座標の入力
+   coord = input()
    # 手順 4 の処理
    # 手順 5 の処理

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

### `input` に関する注意点

__`input`__ の __返り値__ は __常に文字列型__ のデータになる点に __注意が必要__ です。

例えば、下記のプログラムを実行し、`input` で表示されるテキストボックスに `1` を入力して Enter キーを押すと下記のような __エラーが発生__ します。

```python
print(input() + 2)
```

実行結果
```
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
c:\Users\ys\ai\marubatsu\037\marubatsu.ipynb セル 7 line 1
----> 1 print(input() + 2)

TypeError: can only concatenate str (not "int") to str

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

* __TypeError__
データ型（type）に関するエラー
* __can only concatenate str (not "int") to str__
文字列型（str）には、（整数型ではなく（not "int"））文字列型（str）のみ（only）結合（concatenate）できる（can）
:::

`input` で表示されるテキストボックスに `1` のような、__数値に見える__ ような __データを入力__ しても、__`input`__ の __返り値__ は __必ず__ `"1"` のような __文字列型のデータ__ になります。 従って、`input` で __入力した文字列__ を、__数値として扱いたい__ 場合は、__[`int`](https://qiita.com/ysgeso/items/a189461ae32a6531ff7f#%E7%B5%84%E3%81%BF%E8%BE%BC%E3%81%BF%E5%9E%8B%E3%81%AE%E5%A4%89%E6%8F%9B)__ や __`float`__ を使って、下記のプログラムのように __整数型__ や __浮動小数点数型__ のデータに __変換する必要__ があります。

```python
print(int(input()) + 2)
```
実行結果（テキストボックスに `1` を入力して Enter キーを押した場合） 
```
3
```

__同様の理由__ で、テキストボックスに `[1, 2]` のようなデータを記述しても、`input()` の返り値が __list になる__ ことは __ありません__。この場合は `"[1, 2]"` という __文字列__ が返ります。

:::note warn
__`input` の返り値__ は、どのような場合でも __必ず文字列型__ のデータになる。

`input` で入力した文字列を、数値型 や list などの、__他のデータ型__ として __扱いたい場合__ は、__[明示的な型変換](https://qiita.com/ysgeso/items/51c2eba867aa560808c6#%E6%98%8E%E7%A4%BA%E7%9A%84%E3%81%AA%E5%9E%8B%E5%A4%89%E6%8F%9B%E3%81%A8%E6%9A%97%E9%BB%99%E3%81%AE%E5%9E%8B%E5%A4%89%E6%8F%9B)__ を行う必要がある。
:::

## 手順 4 の実装

__手順 4__ の「__プレイヤーが入力した座標に `move` メソッドで着手を行う__」では、手順 3 でテキストボックスに __入力した座標に__、`move` メソッドで __着手を行う__ 必要がありますが、そのためにはテキストボックスに入力する __座標のデータ構造__（どのように記述するか）を __決める必要__ があります。どのようなデータ構造にすればよいかについて少し考えてみて下さい。

__座標__ を表す __文字列__ の __データ構造_ として、以下のようなものが考えられるでしょう。

1. __数学の座標__ と同様に、__`(1, 2)`__ のように記述する
2. 上記と同様だが、外側の __`(` と `)` を省略__ して __`1,2`__ のように記述する
3. テストケースを記述する際に利用した `A1` のような [Excel 座標](https://qiita.com/ysgeso/items/51c2eba867aa560808c6#%E5%BA%A7%E6%A8%99%E3%82%92%E8%A1%A8%E3%81%99%E3%83%87%E3%83%BC%E3%82%BF%E6%A7%8B%E9%80%A0%E3%81%9D%E3%81%AE-1excel-%E5%BA%A7%E6%A8%99)として記述する

上記のいずれの方法を使ってもかまいませんが、本記事では __2 番目__ の __`1,2`__ のように記述するデータ構造を __採用__ することにします。3 番目の Excel 座標を採用したい人は、[以前の記事](https://qiita.com/ysgeso/items/51c2eba867aa560808c6#excel_to_xy-%E3%81%AE%E5%AE%9A%E7%BE%A9%E3%81%9D%E3%81%AE-5%E6%96%87%E5%AD%97%E5%88%97%E3%81%AE%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E5%88%A9%E7%94%A8%E3%81%99%E3%82%8B) で定義した `excel_to_xy` を利用する必要があります。また、上記以外で、採用したいデータ構造を思いついた方は、そのデータ構造を採用したプログラムを記述してみて下さい。

2 番目のデータ構造を採用した __理由__ は、[以前の記事](https://qiita.com/ysgeso/items/6ca248133ff4d59a81d4#split-%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%AB%E3%82%88%E3%82%8B-list-%E3%81%B8%E3%81%AE%E5%A4%89%E6%8F%9B) で紹介した __`split`__ メソッドを使って、下記のプログラムの 9 行目ように、__文字列__ を __x 座標__ と __y 座標__ を __要素__ とする __list に変換__ し、__[list の展開](https://qiita.com/ysgeso/items/51c2eba867aa560808c6#%E5%8F%8D%E5%BE%A9%E5%8F%AF%E8%83%BD%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E5%B1%95%E9%96%8B)__ を使って __`x`__ と __`y`__ に __それぞれの座標を代入__ できるからです。

`x`、`y` に代入されたデータは __文字列型__ なので、11 行目で `move` を使って (x, y) に着手を行う際に、__`int`__ を使って __整数型__ のデータに __変換__ する __必要がある点__ に注意して下さい。

```python
 1  def play(self):
 2      # 〇×ゲームを再起動する
 3      self.restart()
 4      # ゲーム盤の表示
 5      print(self)
 6      # キーボードからの座標の入力
 7      coord = input()
 8      # x 座標と y 座標の計算
 9      x, y = coord.split(",")
10      # (x, y) に着手を行う
11      self.move(int(x), int(y))
12      # 手順 5 の処理
13
14  Marubatsu.play = play
```

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

```python
def play(self):
    # 〇×ゲームを再起動する
    self.restart()
    # ゲーム盤の表示
    print(self)
    # キーボードからの座標の入力
    coord = input()
    # x 座標と y 座標の計算
    x, y = coord.split(",")
    # (x, y) に着手を行う
    self.move(int(x), int(y))
    # 手順 5 の処理

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

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

```diff_python
def play(self):
    # 〇×ゲームを再起動する
    self.restart()
    # ゲーム盤の表示
    print(self)
    # キーボードからの座標の入力
    coord = input()
    # x 座標と y 座標の計算
+   x, y = coord.split(",")
    # (x, y) に着手を行う
+   self.move(int(x), int(y))
    # 手順 5 の処理

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


下記は、`play` メソッドを呼び出し、2 行目でゲーム盤を表示することで、__手順 4 の着手__ が正しく行われたかどうかを __確認__ するプログラムです。下記は、テキストボックスに `1,1` を入力した場合の実行結果で、手順 3、4 の処理が正しく行われたことが確認できます。

```python
mb.play()
print(mb)
```

実行結果
```
Turn o
...
...
...

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

### `input` でメッセージを表示する方法

テキストボックスに入力する __座標のデータ構造__ は、`play` メソッドを記述した人は当然知っていますが、__`play`__ メソッドを __利用するだけの人__ はそのことを __知りません__ 。そのため、座標の __入力方法__ という、__ゲームの遊び方__ を知らせたほうが __親切__ です。

`input` の __実引数__ に __文字列を記述__ することで、__記述した文字列__ が、下図のように __テキストボックスの下に表示__ されるので、これを利用すると良いでしょう。

```python
input("x,y の形式で座標を入力して下さい")
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/07a7fc2e-951b-5591-6ce5-695441093823.png" width="500">

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

```python
def play(self):
    # 〇×ゲームを再起動する
    self.restart()
    # ゲーム盤の表示
    print(self)
    # キーボードからの座標の入力
    coord = input("x,y の形式で座標を入力して下さい")
    # x 座標と y 座標の計算
    x, y = coord.split(",")
    # (x, y) に着手を行う
    self.move(int(x), int(y))
    # 手順 5 の処理

Marubatsu.play = play
```

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

```diff_python
def play(self):
    # 〇×ゲームを再起動する
    self.restart()
    # ゲーム盤の表示
    print(self)
    # キーボードからの座標の入力
-   coord = input()
+   coord = input("x,y の形式で座標を入力して下さい")
    # x 座標と y 座標の計算
    x, y = coord.split(",")
    # (x, y) に着手を行う
    self.move(int(x), int(y))
    # 手順 5 の処理

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


下記のプログラムを実行することで、修正後に `play` メソッドが正しく動作することを確認できます。実行結果は先ほどと同様なので省略します。

```python
mb.play()
print(mb)
```

## 手順 5 の実装と `while` 文

__手順 5__ の「__`judge` メソッドで勝敗判定を行い、ゲームの決着がついていなければ手順 2 へ戻る__」という処理は、別の言葉で __言い換える__ と、「〇×ゲームの __決着がつくまで__、__手順 2 ~ 5__ の処理を __繰り返す__」という処理です。

これまでに利用してきた、__for 文__ では、__`for i in range(10)`__ などのように、__あらかじめ__ 繰り返す __回数__ が __決まっている__ ような __繰り返し処理__ を __記述__ してきました。

一方、〇×ゲームは、決着がつくまで __何回__ 着手を __行う必要があるか__ は、ゲームを行ってみなければ __わからない__ ので、__手順 2 ~ 5__ の処理を __何回繰り返せばよいか__ を、繰り返し処理を行う前に __あらかじめ知る__ ことは __不可能__ です。

このような、__繰り返す回数__ を __あらかじめ知ることができない__ ような繰り返しは、__while 文__ で記述します。while 文は下記のように記述し、__条件式__ が __`True` の間__、while 文の __ブロック__ に記述された処理を __繰り返し実行__ します。

```python
while 条件式:
    条件式が True の間繰り返し処理を行うブロック
```

下図は while 文のフローチャートです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/5c77afaa-17b2-5e1c-d065-b4b09040aac4.png" width="350">

先程のアルゴリズムの __手順 2 ~ 5__ は、ゲームの決着がつくまで __無限に__ 手順 2 ~ 4 の処理を __繰り返す__ というアルゴリズムです。__無限に__ 処理を __繰り返す__ ような __繰り返し処理__ の事を、__無限ループ__ と呼びます。__while 文__ は、__条件式__ が __`True` の間__ 繰り返し処理を行うので、下記のようなプログラムで、__無限ループを記述__ できます。

```python
while True:
    while 文のブロックの処理
```

単に無限ループを記述しただけでは、プログラムの __処理が永遠に終わらない__ ことになってしまいます。そのため __無限ループ__ が __記述__ された __プログラムを実行__ すると、プログラムが __固まって動かななくなる__ という __バグが発生__ します。そのため、__while 文__ の __条件式__ に __`True` を記述__ した場合は、while 文の __ブロックの中__ で、__特定の条件が満たされた場合__ に __[break 文](https://qiita.com/ysgeso/items/1a8a4319ef3c896ef271#break-%E6%96%87%E3%81%A8-continue-%E6%96%87)__ を __記述する__ ことで、__無限ループ__ から __抜け出す__ ような処理を __記述する必要__ があります。

:::note
while 文のブロックを何度繰り返しても __while 文__ の __条件式__ が __`False` にならない__ ような場合も __無限ループになります__。__while 文__ は、for 文と比べて __無限ループが発生しやすい__ ので、while 文を記述する際には __無限ループが発生しない__ ように __注意しながら__ プログラムを __記述する必要__ があります。
:::

上記のことから、手順 2 ~ 5 の処理は、下記のプログラムのように記述できます。

* __5 行目__：while 文による __無限ループを記述__ する
* __7 ~ 13 行目__：元のプログラムに __インデントを入れて__、__while 文__ の __ブロックの処理にする__
* __15、16 行目__：__`judge`__ メソッドの __返り値__ が __`Marubatsu.PLAYING` 以外__ の場合は、ゲームの __決着がついた場合__ なので、16 行目で __break 文__ を記述して __無限ループを終了__ する

```python
 1  def play(self):
 2      # 〇×ゲームを再起動する
 3      self.restart()
 4      # 無限ループを行う
 5      while True:
 6          # ゲーム盤の表示
 7          print(self)
 8          # キーボードからの座標の入力
 9          coord = input("x,y の形式で座標を入力して下さい")
10          # x 座標と y 座標の計算
11          x, y = coord.split(",")
12          # (x, y) に着手を行う
13          self.move(int(x), int(y))
14          # ゲームの決着がついた場合は、無限ループを抜ける
15          if self.judge() != Marubatsu.PLAYING:
16              break
17
18  Marubatsu.play = play
```

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

```python
def play(self):
    # 〇×ゲームを再起動する
    self.restart()
    # 無限ループを行う
    while True:
        # ゲーム盤の表示
        print(self)
        # キーボードからの座標の入力
        coord = input("x,y の形式で座標を入力して下さい")
        # x 座標と y 座標の計算
        x, y = coord.split(",")
        # (x, y) に着手を行う
        self.move(int(x), int(y))
        # ゲームの決着がついた場合は、無限ループを抜ける
        if self.judge() != Marubatsu.PLAYING:
            break

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

<details><summary><strong>修正箇所（while 文のブロックのインデントの追加に関する修正箇所は、表示するとわかりづらくなるので省略しました）</strong></summary>


```diff_python
def play(self):
    # 〇×ゲームを再起動する
    self.restart()
    # 無限ループを行う
+   while True:
        # ゲーム盤の表示
        print(self)
        # キーボードからの座標の入力
        coord = input("x,y の形式で座標を入力して下さい")
        # x 座標と y 座標の計算
        x, y = coord.split(",")
        # (x, y) に着手を行う
        self.move(int(x), int(y))
        # ゲームの決着がついた場合は、無限ループを抜ける
+       if self.judge() != Marubatsu.PLAYING:
+           break

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

下記のプログラムを実行し、`0,0`、`0,1`、`1,0`、`1,1`、`2,0` の順でテキストボックスに入力すると、下記のような実行結果が表示されます。__(2, 0)__ に __着手を行う__ 事で __〇が勝利する__ ため 16 行目の __break 文が実行__ されるため、__無限ループが終了__ します。そのことは、__テキストボックスが表示されなくなる__ ことで確認できます。

```python
mb.play()
```

実行結果
```
Turn o
...
...
...

Turn x
o..
...
...

Turn o
o..
x..
...

Turn x
oo.
x..
...

Turn o
oo.
xx.
...
```

:::note
VSCode の JupyterLab では、セルの __プログラムが実行中__ の場合は、下図のように、__セルの下__ に __矢印が回転__ するな __アニメーションが表示__ されます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/a9f30b1e-3808-6e19-eb65-71d7d097a194.png" width="150">

また、セルの __プログラムの実行が終了__ すると、下図のように、__セルの下__ に __緑色のチェックマーク__ と、そのセルの __プログラムの処理にかかった時間が表示__ されるので、この部分を見ることでも __プログラムの実行が終了しているか__ を __確認__ できます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/82f42fd5-8dbb-ce21-d595-efd62a8dd928.png" width="150">
:::


### 手順 5 の別の記述方法

上記のプログラムの __アルゴリズム__ は、__無限ループ__ の __ブロックの中__ で、__無限ループを抜け出す__ ための __条件式を記述__ し、その __条件式が `True`__ になった場合に __break 文__ を実行して __無限ループから抜け出す__ というものです。__while 文__ の __条件式__ は、__繰り返し__ を __続ける__ ための __条件__ なので、while 文の __条件式__ に繰り返しを __続けるための条件式__ を __記述__ することで、__break 文__ を __記述する必要__ が __なくなります__。具体的には、下記のプログラムのように記述します。

* __5 行目__：while 文の条件式に、__決着がついていない__ ことを __判定__ する 式を記述する
* 元のプログラムにあった、__if 文と break 文を削除__ する

__while 文__ の __条件式__ は、先程のプログラムの __if 文の条件式__ の __逆__ で、繰り返しを __続けるための条件__ を表すので、`!=` ではなく __`==` を記述__ する必要がある点に __注意__ して下さい。

```python
 1  def play(self):
 2      # 〇×ゲームを再起動する
 3      self.restart()
 4      # ゲームの決着がついていない間繰り返す
 5      while self.judge() == Marubatsu.PLAYING:
 6          # ゲーム盤の表示
 7          print(self)
 8          # キーボードからの座標の入力
 9          coord = input("x,y の形式で座標を入力して下さい")
10          # x 座標と y 座標の計算
11          x, y = coord.split(",")
12          # (x, y) に着手を行う
13          self.move(int(x), int(y))
14
15  Marubatsu.play = play
```

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

```python
def play(self):
    # 〇×ゲームを再起動する
    self.restart()
    # ゲームの決着がついていない間繰り返す
    while self.judge() == Marubatsu.PLAYING:
        # ゲーム盤の表示
        print(self)
        # キーボードからの座標の入力
        coord = input("x,y の形式で座標を入力して下さい")
        # x 座標と y 座標の計算
        x, y = coord.split(",")
        # (x, y) に着手を行う
        self.move(int(x), int(y))

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

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

```diff_python
def play(self):
    # 〇×ゲームを再起動する
    self.restart()
    # ゲームの決着がついていない間繰り返す
-   while True:
+   while self.judge() == Marubatsu.PLAYING:
        # ゲーム盤の表示
        print(self)
        # キーボードからの座標の入力
        coord = input("x,y の形式で座標を入力して下さい")
        # x 座標と y 座標の計算
        x, y = coord.split(",")
        # (x, y) に着手を行う
        self.move(int(x), int(y))
-       if self.judge() != Marubatsu.PLAYING:
-           break
           
Marubatsu.play = play
```
</details>

下記のプログラムを実行し、`0,0`、`0,1`、`1,0`、`1,1`、`2,0` の順でテキストボックスに入力することで、`play` メソッドが正しく動作することを確認できます。実行結果は先ほどと同様なので省略します。

```python
mb.play()
```

### while 文の記述に関する補足

while 文のブロックの中に break を記述して繰り返しを抜けるよりも、上記のように __while 文__ の __条件式__ に繰り返しの __続行を判定する式__ を記述したほうがプログラムが __わかりやすい__ ので、__一般的__ には while 文は __上記のように記述__ したほうが __良い__ でしょう。本記事でも上記の記述方法を採用することにします。

ただし、__常に__ 上記のような記述をすれば良いとは __限りません__。例えば、上記のような記述をしてしまうと、while 文を実行した際に、__最初から__ while 文の __条件式が `False`__ になってしまう場合は、__一度も__  while 文の __ブロックが実行されません__ 。そのため、__while 文のブロック__ を最低でも __1 度は実行したい__ 場合は、while 文の __ブロックの最後__ に無限ループを __抜けるため__ の __if 文と break 文を記述__ する必要があります。他にも、while 文のブロックの __繰り返しを続行__ する __条件が複雑__ な場合は、while 文の __条件式が長くなる__ ため、__if 文と break 文__ を使ったほうが __わかりやすい__ でしょう。言葉だけの説明ではわかりづらいと思いますが、今後の記事で具体的な実例が出た際に、このことを詳しく説明することにします。

## `play` メソッドの問題点

`play` メソッドを実装したことで、座標をテキストボックスに入力するだけで〇×ゲームを遊べるようになりましたが、現状の `play` メソッドには __いくつかの問題__ があるので、次回の記事ではそれらの __問題点を修正__ して `play` メソッドを __改良__ することにします。

次回の記事を見る前に、__実際に__ 何度か __`play`__ メソッドを __実行__ して〇×ゲームを __遊んでみて__、__どのような問題があるか__ を考えておいてください。

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

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

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

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

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

# 次回の記事

https://qiita.com/ysgeso/items/1800a353cb609a0fef4e

[^1]:ユーザーインターフェース、CUI、GUI などの用語の意味について忘れてしまった方は、[以前の記事](https://qiita.com/ysgeso/items/1b7b1abe942e2d10c0b4#%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%BC%E3%83%95%E3%82%A7%E3%83%BC%E3%82%B9) を参照してください
[^2]:VSCode 以外の JupyterLab の場合などでは、`input()` を実行した JupyterLab のセルのすぐ下にテキストボックスが表示される場合があります。また、JupyterLab 以外の方法で Python のプログラムを実行した場合などで、テキストボックスが表示されない場合もありますが、いずれの場合でも、__何らかの方法__ で __キーボードから文字を入力できるようになる__ 点に変わりはありません