Pythonで〇×ゲームのAIを一から作成する　その23　勝敗判定

# 目次と前回の記事

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

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

# 実装の進捗状況と前回までのおさらい

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

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

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

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

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

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

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

## 前回までのおさらい

前回の記事では仕様 4、5 の手番と着手を実装しました。

# 勝敗判定

1 ~ 5 までの仕様の実装が完了しましたので、次は __勝敗判定__ に関する 6、7 の実装を行います。仕様 6 は __勝利条件__、仕様 7 は __引き分けの条件__ についての仕様ですが、先に仕様 6 の勝利条件の実装を行います。

## 勝敗判定を行うメソッドの設計

マークを配置する処理を `place_mark`、着手を行う処理を `move` というメソッドで実装したのと同様に、勝敗判定を行う __メソッドを定義__ することにします。そのためには、メソッドの __名前__、__入力__、__処理__、__出力__ を決める必要があります。

判定は英語で judgement、decision のように表現します。ゲームの勝敗判定の場合は judgement のほうが適切な気がしますので、__メソッドの名前__ は判定の __動詞__ である __`judge`__ にすることにします。

https://ejje.weblio.jp/content/%E5%88%A4%E5%AE%9A

勝敗判定は、その時点での __ゲーム盤の状態__ に対して行い、ゲーム盤の状態はインスタンスの __`board` 属性__ に記録されているので、`judge` メソッドにはインスタンスを表す `self` 以外の引数は __必要ありません__。

勝敗判定が行う __処理__ は、以下の 4 つの __ゲームの状態__ を判定することです。

* 〇 のプレイヤーの勝利
* × のプレイヤーの勝利
* 引き分け
* ゲームの決着がまだついていない

`judge` メソッドの __出力__ である __返り値__ は、`judge` メソッドの処理によって判定された __ゲームの状態__ です。ゲームの状態を返り値として返すためには、上記の 4 つの __ゲームの状態__ を表す __データ構造を決める__ 必要があります。

## 勝敗判定の結果を表すデータ構造

[前回の記事](https://qiita.com/ysgeso/items/ed1eb1cacf9a3e7dfa29#%E6%89%8B%E7%95%AA%E3%82%92%E8%A1%A8%E3%81%99%E3%83%87%E3%83%BC%E3%82%BF%E6%A7%8B%E9%80%A0) で説明したように、__同じデータ__ に対して、__様々な__ データ構造が考えられます。今回の記事では勝敗判定の結果を表す 2 種類のデータ構造を紹介します。

### 2 種類のデータから構成されるデータ構造

最初に、__2 種類のデータ__ から構成されるデータ構造を紹介します。

`judge` メソッドが判定する下記の 4 つのゲームの状態のうち、上の 3 つは __勝者__（本記事では __引き分け__ を勝者に __含む__ ことにします）を、最後の 1 つは __ゲームが続行中かどうか__ を表すデータです。

* 〇 プレイヤーの勝利
* × のプレイヤーの勝利
* 引き分け
* ゲームの決着がまだついていない

上記のように、ゲームの状態は __勝者__ と __ゲームが続行中かどうか__ を表す __2 種類の状態__ から __構成される__ ので、その __2 種類__ のデータで表すというデータ構造が考えられます。

#### 勝者を表すデータ

勝者には、__〇 のプレイヤー__、__× のプレイヤー__、__引き分け__ の __3 種類__ があります。〇 のプレイヤーと、× のプレイヤーを表すデータに関しては、ゲーム盤に配置したマークや、手番と同様に `Marubatsu` __クラスの属性__ である、`Marubatsu.CIRCLE` と `Marubatsu.CROSS` を __そのまま利用__ することが出来ます。以前の記事でも説明しましたが、__同じ意味を表すデータ__ は、__共通のデータを利用__ したほうがプログラムがわかりやすくなります。

これまでに記述してきたプログラムの中に、__引き分け__ を表すデータは __存在しない__ ので、ここで引き分けを表すデータを __決める__ 必要があります。引き分けを表すデータは、〇 のプレイヤーと × のプレイヤーを表すデータと __区別できれば良い__ ので、そのデータは、`Marubatsu.CIRCLE`、`Marubatsu.CROSS` __以外のデータ__ であれば __何でも構いません__。そこで、本記事では、__`"draw"` という文字列__ を引き分けを表すデータにすることにし、`Marubatsu` __クラスの `DRAW` 属性__ にその値を代入し、引き分けを表すデータを __`Marubatsu.DRAW`__ のように記述することにします。

下記のプログラムは、marubatsu モジュールに記述した `Marubatsu` クラスをインポートし、`Marubatsu` クラスの `DRAW` 属性に `"draw"` という文字列を代入しています。

```python
from marubatsu import Marubatsu

Marubatsu.DRAW = "draw"
```

#### ゲームが続行中であるかどうかを表すデータ

ゲームが続行中であるかどうかを表すデータは、__続行中である__ と __続行中でない__ の 2 種類です。__～である__ と __～でない__ のような __2 種類__ のデータは、__論理型__ の __`True`__ と __`False`__ で表現するの __一般的__ なので、本記事でもそのように表現することにします。

#### `judge` メソッドの実装例

初心者の方にとっては、`judge` メソッドの中で、この 2 種類のデータをどのように記述するかがわからない人が多いと思いますので、`judge` メソッドの実装例を紹介します。

プログラムでは、__データを変数に代入__ して扱うのが一般的なので、この 2 つのデータを `judge` メソッドの中で代入する __ローカル変数の名前__ を決めることにします。

英語で __勝者__ のことを __winner__、__ゲームを行っている__ ことを __playing__ と表現するので、__勝者__ を表すローカル変数の __名前__ を __`winner`__、__ゲームが続行中であること__ を表すローカル変数の __名前__ を __`playing`__ と名付けることにします。



下記のプログラムは `judge` メソッドを、__必ず 〇 が勝利する__ という判定を行うように実装しています。2 行でローカル変数 __`winner`__ に __`Marubatsu.CIRCLE` を代入__ することで __〇が勝利した__ ことを、3 行目でローカル変数 __`playing`__ に __`False` を代入__ することで、__ゲームが終了したこと__ を表現しています。

__2 つ以上のデータ__ を関数の __返り値として返す__ 場合は、__tuple__ が良く使われます[^1]。4 行目の return 文では、この 2 つの値を要素とする __tuple__ を使って、ゲームの状態を表す 2 つのデータを返り値として返しています。

:::note
[以前の記事](https://qiita.com/ysgeso/items/44884dcf168aab949508#for-%E6%96%87%E3%81%A7-dict-%E3%81%AE%E3%82%AD%E3%83%BC%E3%81%A8%E5%80%A4%E3%81%AE%E4%B8%A1%E6%96%B9%E3%82%92%E5%8F%96%E3%82%8A%E5%87%BA%E3%81%99%E7%B9%B0%E3%82%8A%E8%BF%94%E3%81%97) で説明したように、tuple の `()` は省略することが出来ます。下記のプログラムの 4 行目は `return (winner, playing)` の `()` を省略しています。
:::

```python
def judge(self):
    winner = Marubatsu.CIRCLE
    playing = False
    return winner, playing

Marubatsu.judge = judge
```

もちろん、上記の `judge` メソッドは __正しい勝敗判定を行っていません__。正しい勝敗判定の実装方法については今回の記事のしばらく後で説明します。

下記のプログラムは、1 行目で `Marubastu` のインスタンスを作成し、2 行目で __tuple を返り値として返す__ `judge` メソッドを呼び出しています。[以前の記事](https://qiita.com/ysgeso/items/44884dcf168aab949508#for-%E6%96%87%E3%81%A7-dict-%E3%81%AE%E3%82%AD%E3%83%BC%E3%81%A8%E5%80%A4%E3%81%AE%E4%B8%A1%E6%96%B9%E3%82%92%E5%8F%96%E3%82%8A%E5%87%BA%E3%81%99%E7%B9%B0%E3%82%8A%E8%BF%94%E3%81%97) で説明したように、2 行目のように記述することで、__tuple の要素__ を __別々の変数に代入__ する処理を __1 行の文で記述__ することが出来ます。具体的には、2 行目では、グローバル変数 `winner` と `playing` に、`judge` メソッドの返り値である tuple の 2 つの要素をそれぞれ代入しています。その結果、実行結果に 〇 のプレイヤーが勝者であることを表す `"o"` と、ゲームが終了したことを表す `False` が表示されます。

:::note
先程のノートで説明したのと同様に、2 行目の `winner, playing =` の部分は、`(winner, playing) =` の __`()` を省略__ しています。
:::

```python
mb = Marubatsu()
winner, playing = mb.judge()
print(winner)
print(playing)
```
実行結果
```
o
False
```

### 1 種類のデータから構成されるデータ構造

次に、__1 種類のデータ__ から構成されるデータ構造を紹介します。

勝敗判定によって、判定される下記の 4 つのゲームの状態は、__同時に発生__ することは __ありません__。このゲームの __勝者__ は、〇 のプレイヤー、× のプレイヤー、引き分けの __いずれか__ であり、例えば 〇 のプレイヤーと × のプレイヤーが同時に勝利することはありません。また、__勝者__ が __決まっていれば__ 必ず __ゲームの決着がついています__ し、勝者が __決まっていなければ__ ゲームの __決着はついていません__。

* 〇 プレイヤーの勝利
* × のプレイヤーの勝利
* 引き分け
* ゲームの決着がまだついていない

つまり、__勝敗判定__ によって、上記の 4 つの状態のいずれか __1 つの状態が決まる__ ということです。従って、__状態を表すデータ__ を __1 つのデータで表現__ することが出来ます。

4 つの状態のうち、3 つは __勝者を表す状態__ なので、`judge` メソッドの中で、勝敗判定を表すデータを代入する __ローカル変数の名前__ は、__`winner`__ にすることにします。また、〇 のプレイヤーの勝利、× のプレイヤーの勝利、引き分けを表すデータは、__先程と同様__ に `Marubatsu.CIRCLE`、`Marubatsu.CROSS`、`Marubatsu.DRAW` で表現することします。

ゲームの決着がまだついていない状態は、ゲームの勝者が __存在しない__ という状態です。Python には __データが存在しないこと__ を表す __`None`__ というデータがあるので、ゲームの __決着がついていない状態__ を表すデータは、__`None` で表現__ するすることにします。

これで、__1 つのデータ__ で __勝敗判定の結果__ を表す状態を __表現する__ ことが出来ます。下記の表に 4 つの状態に対応するデータを示します。

| 状態 | データ |
|:--|:--|
| 〇 の勝利  | `Marubatsu.CIRCLE`  |
| × の勝利  | `Marubatsu.CROSS`  |
| 引き分け  | `Marubatsu.DRAW`  |
| 決着がついていない  | `None`  |

勝敗判定の結果を、2 つのデータと、1 つのデータの __どちらのデータ構造で表現__ しても、問題なく勝敗判定を行う __`judge` メソッドを定義することが出来ます__。本記事では __より簡潔に__ プログラムを __記述できる__、1 つのデータで表現する方法を採用することにします。

## 〇 の勝利の判定

4 つの状態を __一度で判定__ することは __できない__ ので、__順番に判定する必要__ があります。そこで、最初に 〇 のプレイヤーが勝利しているかどうかを判定するプログラムを実装することにします。

下記の仕様 6 から、縦、横、斜めのいずれかの __一直線の 3 マス__ に 〇 のマークが __並んだ場合__ に 〇 のプレイヤーの勝ちとなります。

6. プレイヤーがマークを置いた結果、縦、横、斜めのいずれかの一直線の 3 マスに同じマークが並んだ場合、そのマークのプレイヤーの勝利とし、ゲームが終了する

そのような判定を行うプログラムは、__様々な方法__ で記述することが出来ます。今回の記事ではその中の 1 つの方法を紹介し、他の方法については次回以降の記事で紹介することにします。

### 〇 の勝利条件の列挙

仕様 6 から、〇 が勝利する条件を列挙すると、下図のように、横方向に 3 種類、縦方向に 3 種類、斜め方向に 2 種類の __8 種類__ があることがわかります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/8d58de45-0408-d678-6d7d-afb01960e8cd.png" width="300">

〇 のプレイヤーが勝利する条件は、__ゲーム盤の状態__ が上図の 8 つの __いずれかの状態__ になる場合です。そこで、最初にゲーム盤が上図の __左上の状態__ になっていることを判定するプログラムを記述することにします。

### 0 行の 〇 の勝利の判定の実装

先程の図の左上の状態では、ゲーム盤の __0 行__ の (0, 0)、(1, 0)、(2, 0) の __3 つのマス__ に __〇 が配置__ されています。このような、3 つの条件を同時に満たすことを判定するプログラムは、下記のように、__if 文を入れ子__ して記述することが出来ます。

```python
def judge(self):
    # (0, 0) が 〇 であることを判定する
    if self.board[0][0] == Marubatsu.CIRCLE:
        # さらに、(1, 0) が 〇 であることを判定する
        if self.board[1][0] == Marubatsu.CIRCLE:
            # さらに、(2, 0) が 〇 であることを判定する
            if self.board[2][0] == Marubatsu.CIRCLE:
                # 〇 が勝利する
                winner = Marubatsu.CIRCLE

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

Marubatsu.judge = judge
```

### 実装の確認

下記は、上記の `judge` メソッドが正しく動作しているかどうかを確認するために、__ゲームの開始直後__ に `judge` メソッドを呼び出して勝敗判定を行った場合と、`place_mark` で __(0, 0)、(1, 0)、(2, 0) のマスに 〇 を配置__ してから `judge` メソッドを呼び出して勝敗判定を行った場合に、`judge` メソッドの返り値を表示するプログラムです。

本当は、__実際に〇×ゲームを遊ぶ場合__ と同様に、__`move` メソッドを使って__ 〇 と × を交互に配置した状況で __確認すべき__ ですが、上記の __if 文の動作を確認するだけ__ であれば、__× を配置する必要がない__ ので、今回は `place_mark` メソッドを使って __〇 だけを配置__ した __簡略化した確認__ を行っています。`move` メソッドを使ったより本格的な確認については、次回の記事で紹介します。

下記のプログラムを実行すると、実行結果のようなエラーが発生します。

```python
 1  mb = Marubatsu()
 2
 3  # ゲーム開始直後に judge メソッドを呼び出して表示する
 4  print(mb.judge())
 5
 6  # 0 列に 〇 を配置してから judge メソッドを呼び出して表示する
 7  mb.place_mark(0, 0, Marubatsu.CIRCLE)
 8  mb.place_mark(1, 0, Marubatsu.CIRCLE)
 9  mb.place_mark(2, 0, Marubatsu.CIRCLE)
10  print(mb.judge())
```

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

```python
mb = Marubatsu()

# ゲーム開始直後に judge メソッドを呼び出して表示する
print(mb.judge())

# 0 列に 〇 を配置してから judge メソッドを呼び出して表示する
mb.place_mark(0, 0, Marubatsu.CIRCLE)
mb.place_mark(1, 0, Marubatsu.CIRCLE)
mb.place_mark(2, 0, Marubatsu.CIRCLE)
print(mb.judge())
```
</details>

実行結果
```
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
c:\Users\ys\ai\marubatsu\023\marubatsu.ipynb セル 5 line 4
      1 mb = Marubatsu()
      3 # ゲーム開始直後に judge メソッドを呼び出して表示する
----> 4 print(mb.judge())
      6 # 0 列に 〇 を配置してから judge メソッドを呼び出して表示する
      7 mb.place_mark(0, 0, Marubatsu.CIRCLE)

c:\Users\ys\ai\marubatsu\023\marubatsu.ipynb セル 5 line 1
      9             winner = Marubatsu.CIRCLE
     11 # winner を返り値として返す
---> 12 return winner

UnboundLocalError: cannot access local variable 'winner' where it is not associated with a value
```

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

* __UnboundLocalError__
ローカル（Local）変数が名前空間に登録されていない（unbound[^2]）ことを表すエラー
* __local variable 'winner' referenced before assignment__
`winner` というローカル（local）変数（variable）が、値を代入（assignment）する前（before）に参照された（referenced）
:::

### エラーの原因

実行結果からわかるように、`judge` メソッドのブロックの中の 12 行目の __`return winner`__ で、__`winner`__ という __ローカル変数__ が __値を代入する前に参照__ されたという意味の __エラーが発生__ しています。このエラーの原因を見つけるために、プログラムで行われる処理を、__順を追って確認__ することにします。

まず、1 行目で `mb` に `Marubatsu` クラスから作成したインスタンスを代入しています。この処理はこれまで何度も行ってきたのでエラーの原因である可能性は低いでしょう。

次に、4 行目で先ほど実装した `judge` メソッドを呼び出しています。下記は、`judge` メソッドの定義を再掲したものです。

```python
 1  def judge(self):
 2     # (0, 0) が 〇 であることを判定する
 3     if self.board[0][0] == Marubatsu.CIRCLE:
 4         # さらに、(1, 0) が 〇 であることを判定する
 5         if self.board[1][0] == Marubatsu.CIRCLE:
 6             # さらに、(2, 0) が 〇 であることを判定する
 7             if self.board[2][0] == Marubatsu.CIRCLE:
 8                 # 〇 が勝利する
 9                 winner = Marubatsu.CIRCLE
10
11      # winner を返り値として返す
12      return winner
```

`judge` メソッドでは、3 行目の if 文の条件式で `self.board[0][0]` が `"〇"` であるかどうかを判定しています。`self` に代入されている `Marubatsu` クラスのインスタンスには、この時点で __一度も__ `place_mark` や `move` メソッドを使って __マークを配置していない__ ので、`self.board` が表す __ゲーム盤__ には __一つも__ マークは __配置されていません__。従って 3 行目の if 文の __条件式の計算結果__ は __`False`__ になり、この if 文の __ブロック内の__  5 ~ 9 行目のプログラムは __実行されません__。

次に、12 行目の `return winner` が実行されますが、ここまでの `judge` メソッドのブロックの処理では、一度も `winner` という __ローカル変数__ に値が __代入されていません__。そのため、`winner` という名前は __ローカル名前空間__ に __登録されていない__ ので __名前解決が失敗__ し、__エラーが発生__ します。

### フローチャート

言葉だけの説明ではわかりづらいと思った方は、__プログラムの処理の流れ__ （flow）を表す __フローチャート__（flow chart）と呼ばれる図（chart）を書くと良いでしょう。下図は、`judge` メソッドのブロックの処理の流れを表すフローチャートです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/65c9a113-2b56-7f0c-981f-89f3d2d964f9.png" width="500">


フローチャートでは、__条件分岐__ を上図のように __ひし形の図形__ で記述し、__処理__ を __長方形の図形__ で記述します。上図では、3 ~ 7 行目の __3 つの if 文の入れ子による条件分岐__ を __1 つのひし形の図形__ で __まとめて表記__ しています。また、9 行目の処理を、図の右にある長方形で表記しています。他にもいくつかの図形が使われますが、それらについては必要になった時点で説明します。

フローチャートは __JIS__（日本工業規格）によって __厳密な記述方法が定められています__。興味がある方は下記のリンク先を参照して下さい。


https://kikakurui.com/x0/X0121-1986-01.html

__フローチャートの目的__ は、__プログラムの流れをわかりやすく理解すること__ なので、__個人的には__ それほど厳密な記述方法にこだわる必要はないと思います。ただし、独自の図形を作ったり、厳密な記述方法から大きく逸脱するような記述方法はあまりお勧めしません。

:::note
これはあくまで __筆者の意見__ なので、どんな場合でも厳密な記述方法でフローチャートを記述すべきだという意見を否定するものではありません。
:::

本記事でも厳密な記述方法には __あまりこだわらず__ にフローチャートの図を示すことにします。例えば、正式な規格では、上から下と、左から右への流れを表す線には矢印を記述しませんが、本記事のフローチャートでは、__分かりやすさを重視__ して __全ての線に矢印を記述する__ ことにします。

:::note
__グループ__ でプログラムを作成する場合に __自己流__ のフローチャートを記述してグループの仲間と共有する場合は、__混乱の元になる__ ので __規格に厳密に従ったフローチャートを記述したほうが良い__ でしょう。
:::

なお、フローチャートは、必ず記述しなければならないものでは __ありません__。処理の流れが複雑でない場合や、ある程度プログラミングに慣れてきた場合などでは、フローチャートを全く記述せずにプログラムを記述することが実際に良くあります。

### フローチャートによるエラーの原因の説明

下記は、ゲーム開始時と、0 列に〇を配置した際に `judge` メソッドを呼び出した場合の処理の流れを示したフローチャートです。

図の上部の凡例は、__ゲーム開始時__ に `judge` メソッドを呼び出した場合の __処理の流れ__ を __赤い矢印__ で、__0 列に 〇を配置した場合__ の __処理の流れ__ を __緑の矢印__ で表記することを表しています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/e51b5049-8c03-aaed-4d53-fdb2fa85ac66.png" width="500">

上図から、__ゲーム開始時__ に `judge` メソッドを呼び出した場合の赤い矢印の処理の流れでは、ローカル変数 __`winner` に一度も値を代入せずに__、`winner` を関数の返り値として返す処理が行われることがわかります。

### エラーの修正方法

エラーの原因は、`judge` メソッドのブロックを実行してから、12 行目の `return winner` を実行するまでの間でローカル変数 `winner` __値が代入されていない__ ことです。従って、12 行目を実行する __より前__ で `winner` に値が __必ず代入される__ ように修正すれば、このエラーは発生しなくなります。

12 行目を実行する前に、確実に `winner` に値を代入する方法は、下記のプログラムの 2 行目のように、`judge` メソッドの __最初の行__ で、`winner` に __何らかの値を代入しておく__ ことです。下記の 2 行目の `???` に何を記述するべきかについて考えてみて下さい。

```python
def judge(self):
    self.winner = ???
    if self.board[0][0] == "〇":
        以下略
```

`judge` メソッドは、3 ~ 9 行目の 3 つの if 文によって、ゲーム盤の 0 行に 〇 が 3 つ配置されていた場合に、`winner` に `Marubatsu.CIRCLE` を代入する処理を行いますが、__それ以外の場合__ は __何の処理も行いません__。現時点では 0 行目しか調べていませんが、将来的には、上記のプログラムを、__〇 が勝利した場合__ に、__`winner` に `Marubatsu.CIRCLE` を代入する__ 処理に修正します。その場合でも __〇 の勝利以外__ の「× の勝利、引き分け、決着がついていない」場合は __何の処理も行いません__。

その後で __× の勝利を判定する__ プログラムを実装する際も、__同様の処理__ を行うプログラムを記述します。従って、〇 の勝利でも × の勝利でもない場合、すなわち、「引き分け、決着がついていない場合」 は __何の処理も行われません__。

その後で __引き分けを判定する__ プログラムを実装する際も、__同様の処理__ を行うプログラムを記述します。従って、__何の処理も行われない__ のは、残った __決着がついていない場合だけ__ です。

上記から、`judge` メソッドで __何の処理も行われない__ のは、__決着がついていない場合だけ__ なので、__最初に__ `winner` に、決着がついていないことを表す __`None` を代入すれば良い__ ことがわかります。下記のプログラムはそのように修正したプログラムです。

別の言葉で説明すると、ローカル変数 `winner` に __最初は__ 決着がついていないことを表す __`None` を代入しておき__、__勝者が判定できた場合__ に、その値を勝者を表す値で __上書きする__ ということです。

```python
def judge(self):
    # 判定を行う前に、決着がついていないことにしておく
    winner = None
    # (0, 0) が 〇 であることを判定する
    if self.board[0][0] == Marubatsu.CIRCLE:
        # さらに、(1, 0) が 〇 であることを判定する
        if self.board[1][0] == Marubatsu.CIRCLE:
            # さらに、(2, 0) が 〇 であることを判定する
            if self.board[2][0] == Marubatsu.CIRCLE:
                # 〇 が勝利する
                winner = Marubatsu.CIRCLE
  
    # winner を返り値として返す
    return winner

Marubatsu.judge = judge
```

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

```diff_python
def judge(self):
+   # 判定を行う前に、決着がついていないことにしておく
+   winner = None
    # (0, 0) が 〇 であることを判定する
    if self.board[0][0] == Marubatsu.CIRCLE:
        # さらに、(1, 0) が 〇 であることを判定する
        if self.board[1][0] == Marubatsu.CIRCLE:
            # さらに、(2, 0) が 〇 であることを判定する
            if self.board[2][0] == Marubatsu.CIRCLE:
                # 〇 が勝利する
                winner = Marubatsu.CIRCLE
    
    # winner を返り値として返す
    return winner

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

下記は、先程の確認を行うプログラムと同じものです。実行結果から、今度は __エラーが発生せず__、ゲームの __開始直後__ の `judge` メソッドの返り値が、決着がついていないことを表す __`None` である__ こと、ゲーム盤の __0 行に 〇 を配置した後__ の `judge` メソッドの返り値が、〇 が勝利したことを表す __`"o"` になる__ ことが確認できます。

```python
mb = Marubatsu()

# ゲーム開始直後に judge メソッドを呼び出して表示する
print(mb.judge())

# 0 列に 〇 を配置してから judge メソッドを呼び出して表示する
mb.place_mark(0, 0, Marubatsu.CIRCLE)
mb.place_mark(1, 0, Marubatsu.CIRCLE)
mb.place_mark(2, 0, Marubatsu.CIRCLE)
print(mb.judge())
```
実行結果
```
None
o
```

下図は、エラーを修正した場合の `judge` のフローチャートです。図から、__どちらの場合__ でも、ローカル変数 `winner` を関数の返り値として返す処理を __行う前__ に、`winner` に必ず __何らかの値が代入される__ ことが確認できます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/67d00385-b993-9b30-e8ab-6c06300f1ce0.png" width="500">

:::note
他にも、下記のプログラムのように、__else を記述__ し、その中で `winner = None` を記述するという修正方法があります。この方法では、if 文の条件式が、どのような計算結果になっても、__必ず__ どこかで __`winner` に何らかの値を代入__ します。

この方法の場合は、else と `winner = None` を 3 箇所に記述する必要があるという欠点がありますが、その欠点は次で説明する論理演算子を使うことで解消することができます。この方法については次回以降の記事で紹介します。

```python
def judge(self):
    # (0, 0) が 〇 であることを判定する
    if self.board[0][0] == Marubatsu.CIRCLE:
        # さらに、(1, 0) が 〇 であることを判定する
        if self.board[1][0] == Marubatsu.CIRCLE:
            # さらに、(2, 0) が 〇 であることを判定する
            if self.board[2][0] == Marubatsu.CIRCLE:
                # 〇 が勝利する
                winner = Marubatsu.CIRCLE
            else:
                winner = None
        else:
            winner = None
    else:
        winner = None

    # winner を返り値として返す
    return winner
```

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

```diff_python
def judge(self):
    # (0, 0) が 〇 であることを判定する
    if self.board[0][0] == Marubatsu.CIRCLE:
        # さらに、(1, 0) が 〇 であることを判定する
        if self.board[1][0] == Marubatsu.CIRCLE:
            # さらに、(2, 0) が 〇 であることを判定する
            if self.board[2][0] == Marubatsu.CIRCLE:
                # 〇 が勝利する
                winner = Marubatsu.CIRCLE
+           else:
+               winner = None
+       else:
+           winner = None
+   else:
+       winner = None

    # winner を返り値として返す
    return winner
```
</details>

下図は、上記のプログラムの場合のフローチャートです。図から、どちらの場合でも、ローカル変数 `winner` を関数の返り値として返す処理を行う前に、`winner` に必ず何らかの値が代入されることが確認できます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/47f6936d-5e18-68b8-3a4d-c225eaf688d4.png" width="500">

:::

### 論理演算子

先程のプログラムでは、__複数の条件式__ が __同時に満たされる__ ことを判定するために、if 文を入れ子にしたプログラムを記述しましたが、そのようなプログラムは __記述するのが大変__ です。このような場合は、__and__ という __論理演算子__ を使うと便利です。

__論理演算子__ とは、__論理型__ のデータである __`True` と `False`__ に対する演算を行う演算子です。そのような演算を __ブール（boolean）演算__ と呼ぶので、論理演算子の事を __ブール演算子__ または __ブーリアン演算子__ と呼ぶ場合があります。同様に、__論理型__ のことを __ブール型__ または __ブーリアン型__ と呼ぶ場合があります。

:::note
実際には、論理型以外のデータに対しても、論理演算子を使って演算を行うことが出来ます。そのような場合については必要になった時点で説明します。
:::

Python には、__`and`__、__`or`__、__`not`__ という __3 種類__ の論理演算子があります。

#### and 演算子

`and` は、日本語では「__なおかつ__」という意味を表すような演算を行う論理演算子で、__前後の値__ が __共に `True`__ の場合は __`True`__ に、__どちらか片方でも `False`__ の場合は __`False`__ になるという計算を行う演算子です。このことを表にすると以下のようになります。

|  | True | False |
|:-:|:-:|:-:|
| __True__  | True  | False  |
| __False__  | False  | False  |

下記は、上記の 4 通りの計算結果を表示するプログラムです。

```python
print(True and True)
print(True and False)
print(False and True)
print(False and False)
```
実行結果
```
True
False
False
False
```

#### or 演算子
`or` は、日本語では「__または__」という意味を表すような演算を行う論理演算子で、__前後の値__ の __いずれか `True`__ の場合は __`True`__ に、__共に `False`__ の場合は __`False`__ になるという計算を行う演算子です。このことを表にすると以下のようになります。

|  | True | False |
|:-:|:-:|:-:|
| __True__  | True  | True  |
| __False__  | True  | True  |

下記は、上記の 4 通りの計算結果を表示するプログラムです。

```python
print(True or True)
print(True or False)
print(False or True)
print(False or False)
```
実行結果
```
True
True
True
False
```

#### not 演算子

not は、日本語で「__~でない__」という意味の演算を行う論理演算子で、__後ろの値__ が __`True`__ の場合は __`False`__ に、__`False`__ の場合は __`True`__ になるという計算を行う演算子です。このことを表にすると以下のようになります。

<table>
    <tr>
        <td><strong>True</strong></td>
        <td>False</td>
    </tr>
    <tr>
        <td><strong>False</strong></td>
        <td>True</td>
    </tr>
</table>

下記は not を使った 2 通りの計算結果を表示するプログラムです。

```python
print(not True)
print(not False)
```
実行結果
```
False
True
```

#### 論理演算子の優先順位

論理演算子、四則演算子、比較演算子を __一つの式で同時に記述__ した場合は、下記のような __優先順位__ で演算が行われます。上に記述された演算子のほうが優先されます。__同じ行__ に記述された演算子は __記述された順番__ で演算が行われます。

1. `*` `/`
1. `+` `-`
1. `==` `!=` `<=` `>=` `<` `>` 
1. `not`
1. `and`
1. `or`

例えば `1 + 2 * 3 >= 4 or 5 > 6 and not True` という式は、下記の表のような手順で計算が行われます。厳密には、この後で説明する短絡評価という仕組みによって下記とは若干異なる手順で計算が行われますが、__ほとんどの場合__ は下記の計算手順で計算を行っても __計算結果は変わらない__ ので、まずは下記の __計算の手順を理解__ して下さい。

| 演算子 | 計算結果 |
|:-:|:-:|
| `*` | `1 + 6 >= 4 or 5 > 6 and not True`  |
| `+` | `7 >= 4 or 5 > 6 and not True`  |
| `>=` `>`  | `True or False and not True`  |
| `not`  | `True or False and False`  |
| `and`  | `True or False ` |
| `or`  | `True` |

下記のプログラムは、上記の式の計算結果を表示するプログラムです。

```python
print(1 * 2 + 3 >= 4 or 5 > 6 and not True)
```
実行結果
```
True
```

四則演算と比較演算子の優先順位は、小学校から慣れ親しんでいるので理解するのは困難ではないと思いますが、__論理演算子と他の演算子__ の優先順位は __慣れないとわかりづらく__、優先順位を __間違える__ ことが __バグの原因__ となることが非常に良くあります。

そこで、下記のプログラムのように、記述する __必要がない場合__ でも __`()`__ を使って __計算の順番__ を __明確にする__ ことが良くあります。

```python
print((1 * 2 + 3 >= 4) or ((5 > 6) and (not True)))
```
実行結果
```
False
```

Python の演算子の優先順位の詳細については、下記のリンク先を参照して下さい。

https://docs.python.org/ja/3/reference/expressions.html#operator-precedence

### and 演算子と or 演算子の短絡評価

__and 演算子__ で連結された式は、__1 つでも__ 計算結果が __`False` になる__ と、全体の式の計算結果が __`False` になります__。従って、and 演算子で連結された __式を計算する過程__ で、計算結果が __`False`__ になる __式が見つかった場合__ は、__その時点で__ 全体の式の計算結果は __`False` になる__ ので、__残りの式__ を計算する __必要はありません__。

__or 演算子__ で連結された式も __同様__ に、__1 つでも__ 計算結果が __`True` になる__ と、全体の式の計算結果が __`True` になります__。計算結果が __`True`__ になる __式が見つかった場合__ は __残りの式__ を計算する __必要がない__ 点も __同様__ です。

例えば、先程の `1 + 2 * 3 >= 4 or 5 > 6 and not True` という式は、下記の表のような手順で計算が行われると説明しました。しかし、この式は、__先頭の `1 + 6 >= 4`__ の式が __or 演算子で連結__ されているので、__`1 + 6 >= 4`__ の計算結果が __`True` である__ ことが __分かった時点__ で、残りの式を計算しなくても __`True` になる__ ことが確定します。下記の表の手順で行っている、`5 > 6` や `not True` の計算を __行う必要はありません__。

| 演算子 | 計算結果 |
|:-:|:-:|
| `*` | `1 + 6 >= 4 or 5 > 6 and not True`  |
| `+` | `7 >= 4 or 5 > 6 and not True`  |
| `>=` `>`  | `True or False and not True`  |
| `not`  | `True or False and False`  |
| `and`  | `True or False ` |
| `or`  | `True` |

多くのプログラム言語[^3]では、__and 演算子__ で __連結__ された __式__ を __先頭から順番に計算__ し、計算結果が __`False` の式が見つかった__ 時点で、__残りの式を計算しません__。__or 演算子__ も同様に、計算結果が __`True` の式が見つかった__ 時点で、__残りの式を計算しません__。

このような __and__ と __or__ 演算子の __式の演算の省略__ のことを __短絡評価__ と呼びます。

先程の `1 + 2 * 3 >= 4 or 5 > 6 and not True` という式は、__実際には__ 下記の表のような手順で計算が行われます。表からわかるように、`1 + 2 * 3 >= 4` の計算結果が `True` になった時点で __短絡評価__ が行われるので、`5 > 6` と `not True` は __計算されません__。

|  | 計算結果 |
|:-:|:-:|
| __先頭の式__ の `*` | `1 + 6 >= 4 or 5 > 6 and not True`  |
| __先頭の式__ の `+` | `7 >= 4 or 5 > 6 and not True`  |
| __先頭の式__ の`>=`  | `True or 5 > 6 False and not True`  |
| __短絡評価__  | `True` |

短絡評価を行っても __論理演算子の優先順位__ は __変わらない__ 点に注意が必要です。例えば、`1 > 2 or 3 < 4 and 5 > 6` という式は下記の表のような手順で計算されます。

|  | 計算結果 |
|:-:|:-:|
| __先頭の式__ | `False or 3 > 4 and 5 > 6`  |
| __真ん中の式__ | `False or True and 5 > 6`  |
| __右の式__  | `False or True and False`  |
| __and__  | `False or False` |
| __or__  | `False` |


真ん中の式を計算した結果、式の前半が `False or True` となるので計算結果は `True` となると __勘違いする__ 人がいるかもしれませんが、__and 演算子の方__ が、or 演算子より __優先順位が高い__ ので、__先に__ `True and 5 > 6` を計算する必要があります。従って、上記の表の手順で短絡評価が行われずに計算が行われ、計算結果は __`False`__ になります。

下記は、プログラムで上記の式を計算したもので、実行結果は `False` になります。
```python
print(1 > 2 or 3 < 4 and 5 > 6)
```
実行結果
```
False
```

短絡評価は、__無駄な演算を行わない__ という処理なので、__基本的__ には __短絡評価を行わずに計算を行う__ 場合と比べて、__計算結果が変わることはありません__。例えば、and や or 演算子で連結された式の中に、__関数呼び出し__ が __一つも記述されていない__ 場合は、短絡評価を行った場合と、行わない場合の計算結果は __ほとんどの場合で同じになります__。

ただし、関数呼び出しの返り値を使って計算を行うような式を記述した場合などでは、__短絡評価のことを考慮に入れずに__ プログラムを __記述すると__ 思わぬ __バグが発生する場合__ があります。他にも、and や or 演算子で連結された __式の順番を間違える__ と __エラーが発生する__ 場合があります。それらの具体例については必要になった時点で説明します。

本記事では、__短絡評価__ のことを __考慮__ してプログラムを記述する __必要がある__ 場合については、__そのことを言及する__ ことにします。__言及がない場合__ は短絡評価のことを __考慮しなくても良い__ 場合だと考えて下さい。

:::note
下記のような、__実引数の値を `print` で表示__ し、__実引数の値__ をそのまま __返り値として返す__ `p` という関数を定義する事で、短絡評価が行われていることを __実際に確認する__ ことが出来ます。この関数の名前を 1 文字の短い名前にしたのは、この後で 1 行の文の中で何度もこの関数を呼び出して利用するからです。

```python
def p(x):
    print(x)
    return x
```

例えば、先程の `1 + 2 * 3 >= 4 or 5 > 6 and not True` という式で記述されている __それぞれのデータ__ が、__実際に__ この式の計算を行う際に __使われているか__ を、下記のプログラムのように、それぞれの値を `p` の実引数で呼び出すようにすることで確認することが出来ます。なお、__`p` で表示した値__ と __区別できる__ ように、__この式の計算結果__ は __先頭に `answer = ` を表示する__ ようにしています。

```python
print("answer = ", p(1) + p(2) * p(3) >= p(4) or p(5) > p(6) and not p(True))
```

実行結果
```
1
2
3
4
answer =  True
```

`p` という関数は、__実引数の値を表示__ し、そのまま返り値として返す関数なので、式の計算で __実際に使われた値__ は __実行結果に表示される__ ことになります。実行結果から、or と and で連結された __最初の式__ に記述された `1`、`2`、`3`、`4` が表示されているので、__最初の式の計算__ は、確かに __行われた__ ことが確認できます。一方、__残りの式__ に記述された `5`、`6`、`True` は実行結果に __表示されない__ ので、`5 > 6` と `not True` の計算は __行われていない__ ことが確認できます。
:::

### 短絡評価のフローチャート

下図は、下記の and と or 演算子を連結した if 文で行われる処理のフローチャートです。__短絡評価__ によって、__式2 と 式3 の計算が省略__ される __場合がある__ ことを確認して下さい。

```python
if 式1 and 式2 and 式3:
    処理
```

``` python
if 式1 or 式2 or 式3:
    処理
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/d39520b0-6543-fa0e-7e3f-a46a91b70bcf.png" width="600">

下図の左は、下記の and と or 演算子が混在する場合のフローチャートです。図では、__優先順位の高い and 演算子__ の処理の流れの部分を __赤色の図形__ で表記しています。

分かりづらいと思った方は、__`式2 and 式3 and 式4`__ の部分を __`and の部分`__ のひし形の __図でまとめた__ 下図の右のフローチャートを見て下さい。下図の右は、先ほどの、__`or` 演算子のみ__ で連結した場合のフローチャートと __同じ構造__ になっています。

``` python
if 式1 or 式2 and 式3 and 式4 or 式5:
    処理
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/405fa5e3-bcd8-5c01-ad79-d7ed05a65753.png" width="600">

### and 演算子を使った `judge` の修正

`judge` メソッドは、__and 演算子を使う__ ことで、下記のプログラムのように __if 文を 1 つだけ記述__ するように修正することが出来ます。

```python
def judge(self):
    # 判定を行う前に、決着がついていないことにしておく
    winner = None
    # 0 行に 〇 が並んでいることを判定する
    if self.board[0][0] == Marubatsu.CIRCLE and self.board[1][0] == Marubatsu.CIRCLE and self.board[2][0] == Marubatsu.CIRCLE:
        # 〇 が勝利する
        winner = Marubatsu.CIRCLE
  
    # winner を返り値として返す
    return winner

Marubatsu.judge = judge
```

上記のように、__条件式が長くなる__ とプログラムが __読みづらく__ なります。そのような場合は、下記のプログラムのように、__行の最後__ に __半角の `\`__（バックスラッシュ。日本語のキーボードでは `￥` で入力します） を記述することで、__式の途中で改行__ を行うことが出来ます。__`\` を記述せずに改行__ すると __エラーになる__ 点に注意して下さい。

```python
def judge(self):
    # 判定を行う前に、決着がついていないことにしておく
    winner = None
    # 0 行に 〇 が並んでいることを判定する
    if self.board[0][0] == Marubatsu.CIRCLE and \
       self.board[1][0] == Marubatsu.CIRCLE and \
       self.board[2][0] == Marubatsu.CIRCLE:
        # 〇 が勝利する
        winner = Marubatsu.CIRCLE
  
    # winner を返り値として返す
    return winner

Marubatsu.judge = judge
```

記事が長くなるのと、__先ほどと同じ内容__ なので記述は省略しますが、先程と同様の方法で、上記のプログラムが正しく動作するかどうかを確認することが出来ます。プログラムの動作の確認は、Marubatsu.ipynb の方では行っているので、そちらを見て下さい。

### Python の比較演算子の連鎖による and 演算子の省略

一般的なプログラム言語では、__3 つ以上の式__ の計算結果が __等しい__ ことを __判定する__ ためには、__and と同じ意味__[^4] を持つ __論理演算子__ を利用する必要があります。例えば、変数 `a` と `b` と `c` の 3 つの値が等しいかどうかを判定するために、`a == b == c` のような式を記述して判定することは、__一般的なプログラム言語__ では __できません__[^5]。

一方、Python では、そのような式を記述して、__複数の式の値が等しい__ ことを __簡潔に記述する__ ことが出来ます。

このような、__複数の式__ を __比較演算子でつなぐ__ 事を、比較演算子の __連鎖__ と呼びます。比較演算子の連鎖の詳細については、下記のリンク先を参照して下さい。

https://docs.python.org/ja/3/reference/expressions.html#comparisons

具体的には、Python では、`式1 比較演算子1 式2 比較演算子2 式3` という式は、`式1 比較演算子1 式2 and 式2 比較演算子2 式3` とほぼ同じ計算が行われます。

:::note
実際には、上記の 2 つの式は __微妙に異なる処理__ が行われます。その違いは、`式1 比較演算子1 式2 and 式2 比較演算子2 式3` では、`式2` が __2 回記述__ されているので、`式2` の計算が __2 回行われる場合があります__ が、`式1 比較演算子1 式2 比較演算子2 式3` の場合は、`式2` の計算は __1 回しか行われません__。

この違いは __多くの場合__ は計算結果に __影響を与えません__。例えば、式の中に __関数呼び出し__ が __記述されていない場合__ は、__ほとんどの場合で同一の計算__ が行われますが、そうでない場合に計算結果が __異なる場合がある__ ので __注意する必要__ があります。現時点のプログラムでは、この違いは計算結果に影響は与えません。また、この違いを説明すると長くなるので今回の記事では説明は省略し、この違いが重要になるような場合が出てきた時点で詳しく説明します。
:::

__比較演算子の連鎖__ を利用することで、`judge` メソッドの if 文を下記のプログラムのように修正して __簡潔に記述__ することが出来ます。プログラムの動作確認は省略します。

```python
def judge(self):
    # 判定を行う前に、決着がついていないことにしておく
    winner = None
    # 0 行に 〇 が並んでいることを判定する
    if self.board[0][0] == self.board[1][0] == self.board[2][0] == Marubatsu.CIRCLE:
        # 〇 が勝利する
        winner = Marubatsu.CIRCLE
  
    # winner を返り値として返す
    return winner

Marubatsu.judge = judge
```


### 残りの 〇 の勝利の判定

残りの 7 種類の 〇 の勝利の判定は、__同様の方法__ で下記のプログラムのように __if 文を並べて記述__ することが出来ます。

```python
def judge(self):
    # 判定を行う前に、決着がついていないことにしておく
    winner = None
    # 0 行の 〇 の勝利の判定
    if self.board[0][0] == self.board[1][0] == self.board[2][0] == Marubatsu.CIRCLE:
        winner = Marubatsu.CIRCLE
    # 1 行の 〇 の勝利の判定
    if self.board[0][1] == self.board[1][1] == self.board[2][1] == Marubatsu.CIRCLE:
        winner = Marubatsu.CIRCLE
    # 2 行の 〇 の勝利の判定
    if self.board[0][2] == self.board[1][2] == self.board[2][2] == Marubatsu.CIRCLE:
        winner = Marubatsu.CIRCLE
    # 0 列の 〇 の勝利の判定
    if self.board[0][0] == self.board[0][1] == self.board[0][2] == Marubatsu.CIRCLE:
        winner = Marubatsu.CIRCLE
    # 1 列の 〇 の勝利の判定
    if self.board[1][0] == self.board[1][1] == self.board[1][2] == Marubatsu.CIRCLE:
        winner = Marubatsu.CIRCLE
    # 2 列の 〇 の勝利の判定
    if self.board[2][0] == self.board[2][1] == self.board[2][2] == Marubatsu.CIRCLE:
        winner = Marubatsu.CIRCLE
    # 右下方向の斜めの 〇 の勝利の判定
    if self.board[0][0] == self.board[1][1] == self.board[2][2] == Marubatsu.CIRCLE:
        winner = Marubatsu.CIRCLE
    # 左下方向の斜めの 〇 の勝利の判定
    if self.board[2][0] == self.board[1][1] == self.board[0][2] == Marubatsu.CIRCLE:
        winner = Marubatsu.CIRCLE

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

Marubatsu.judge = judge
```

上記の 8 つの if 文は __どのブロックでも同じ処理__ を行うので、__1 つにまとめたほうが良い__ でしょう。〇 が勝利する条件は、8 つの if 文の条件式の __いずれかが True__ の場合なので、先ほど説明した __or 演算子__ を使って下記のプログラムのように `judge` メソッドを修正することが出来ます。

```python
def judge(self):
    # 判定を行う前に、決着がついていないことにしておく
    winner = None
    # 〇 の勝利の判定
    if self.board[0][0] == self.board[1][0] == self.board[2][0] == Marubatsu.CIRCLE or \
       self.board[0][1] == self.board[1][1] == self.board[2][1] == Marubatsu.CIRCLE or \
       self.board[0][2] == self.board[1][2] == self.board[2][2] == Marubatsu.CIRCLE or \
       self.board[0][0] == self.board[0][1] == self.board[0][2] == Marubatsu.CIRCLE or \
       self.board[1][0] == self.board[1][1] == self.board[1][2] == Marubatsu.CIRCLE or \
       self.board[2][0] == self.board[2][1] == self.board[2][2] == Marubatsu.CIRCLE or \
       self.board[0][0] == self.board[1][1] == self.board[2][2] == Marubatsu.CIRCLE or \
       self.board[2][0] == self.board[1][1] == self.board[0][2] == Marubatsu.CIRCLE:
        winner = Marubatsu.CIRCLE

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

Marubatsu.judge = judge
```

本当は、上記のプログラムが正しく動作するかどうかについての __確認を行うべき__ なのですが、上記のような __複雑な__ if 文の __条件式__ が記述されたプログラムの確認はこれまでのように __簡単に行うことはできません__。上記のプログラムの動作の確認については次回の記事で行うことにします。

### × の勝利の判定

× の勝利の判定は、下記のプログラムのように、〇 の勝利判定と __同様の方法__ で記述することが出来ます。下記のプログラムの確認も次回の記事で行います。

```python
def judge(self):
    # 判定を行う前に、決着がついていないことにしておく
    winner = None
    # 〇 の勝利の判定
    if self.board[0][0] == self.board[1][0] == self.board[2][0] == Marubatsu.CIRCLE or \
       self.board[0][1] == self.board[1][1] == self.board[2][1] == Marubatsu.CIRCLE or \
       self.board[0][2] == self.board[1][2] == self.board[2][2] == Marubatsu.CIRCLE or \
       self.board[0][0] == self.board[0][1] == self.board[0][2] == Marubatsu.CIRCLE or \
       self.board[1][0] == self.board[1][1] == self.board[1][2] == Marubatsu.CIRCLE or \
       self.board[2][0] == self.board[2][1] == self.board[2][2] == Marubatsu.CIRCLE or \
       self.board[0][0] == self.board[1][1] == self.board[2][2] == Marubatsu.CIRCLE or \
       self.board[2][0] == self.board[1][1] == self.board[0][2] == Marubatsu.CIRCLE:
        winner = Marubatsu.CIRCLE
        
    # × の勝利の判定
    if self.board[0][0] == self.board[1][0] == self.board[2][0] == Marubatsu.CROSS or \
       self.board[0][1] == self.board[1][1] == self.board[2][1] == Marubatsu.CROSS or \
       self.board[0][2] == self.board[1][2] == self.board[2][2] == Marubatsu.CROSS or \
       self.board[0][0] == self.board[0][1] == self.board[0][2] == Marubatsu.CROSS or \
       self.board[1][0] == self.board[1][1] == self.board[1][2] == Marubatsu.CROSS or \
       self.board[2][0] == self.board[2][1] == self.board[2][2] == Marubatsu.CROSS or \
       self.board[0][0] == self.board[1][1] == self.board[2][2] == Marubatsu.CROSS or \
       self.board[2][0] == self.board[1][1] == self.board[2][2] == Marubatsu.CROSS:
        winner = Marubatsu.CROSS        

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

Marubatsu.judge = judge
```

### 引き分けの判定

下記の仕様 7 から、__全てのマスが埋まったかどうか__ を調べることで引き分けの判定を行うことが出来ます。

7\. すべてのマスが埋まった時にゲームの決着がついていない場合は引き分けとする

(0, 0) のマスが埋まっているということは、__〇 または ×__  のマークが __配置__ されているということなので、下記のような if 文を記述することで (0, 0) のマスが埋まっているかどうかを判定することが出来ます。

```python
if self.board[0][0] == Marubatsu.CIRCLE or self.board[0][0] == Marubatsu.CROSS:
```

残りの 8 マスに対しても同じ条件が満たされている場合に引き分けになるので、下記のプログラムのような条件式を記述すれば __引き分け__ であることを __判定__ することが出来ます。__and 演算子__ のほうが __or 演算子__ より __優先順位が高い__ ので、各マスの判定を行う条件式を __`()` で囲う__ 必要がある点に注意が必要です。

```python
if (self.board[0][0] == Marubatsu.CIRCLE or self.board[0][0] == Marubatsu.CROSS) and \
   (self.board[1][0] == Marubatsu.CIRCLE or self.board[1][0] == Marubatsu.CROSS) and \
   略
   (self.board[2][2] == Marubatsu.CIRCLE or self.board[2][2] == Marubatsu.CROSS):
   self.winner = Marubatsu.DRAW
```

上記の式は長くて記述するのが大変ですが、__別の見方__ をすることで、上記の式を __短く__ 記述することが出来ます。ただし、上記の式が間違っている __わけではない__ ので、上記の式のほうがわかりやすいと思った方は、上記の式を __採用しても問題はありません__。

全てのマスが埋まっているということは、__逆の見方__ をすると __空いているマス__ が __存在しない__ ということです。つまり、空いているマスが 1 つでもあれば __引き分けではない__ ということです。下記は、引き分け __でない__ ことを判定する if 文です。

```python
if self.board[0][0] == Marubatsu.EMPTY or \
   self.board[1][0] == Marubatsu.EMPTY or \
   略
   self.board[2][2] == Marubatsu.EMPTY:
```

__引き分けの場合__ は、上記の __条件式__ が __`False` になる__ なので、下記のプログラムのように、__not 演算子__ を使うことで、__引き分けを判定__ することが出来ます[^6]。__not 演算子__ は __or 演算子__ よりも __優先順位が高い__ ので、not の後の式の全体を __`()` で囲う必要がある__ 点に注意が必要です。

```python
if not(self.board[0][0] == Marubatsu.EMPTY or \
       self.board[0][0] == Marubatsu.EMPTY or \
       略
       self.board[0][0] == Marubatsu.EMPTY):
    winner = Marubatsu.DRAW
```

:::note
not 演算子がわかりづらいと思った方は、下記のように、__`False` と等しいかどうか__ を判定するような条件式を記述してもかまいません。

```python
if (self.board[0][0] == Marubatsu.EMPTY or \
    self.board[0][0] == Marubatsu.EMPTY or \
    略
    self.board[0][0] == Marubatsu.EMPTY) == False:
    winner = Marubatsu.DRAW
```
:::

### 勝敗判定を行うプログラム

上記をまとめると、`judge` メソッドを以下のように定義する事で勝敗判定を行うことができます。下記のプログラムの確認も次回の記事で行います。

```python
def judge(self):
    # 判定を行う前に、決着がついていないことにしておく
    winner = None
    # 〇 の勝利の判定
    if self.board[0][0] == self.board[1][0] == self.board[2][0] == Marubatsu.CIRCLE or \
       self.board[0][1] == self.board[1][1] == self.board[2][1] == Marubatsu.CIRCLE or \
       self.board[0][2] == self.board[1][2] == self.board[2][2] == Marubatsu.CIRCLE or \
       self.board[0][0] == self.board[0][1] == self.board[0][2] == Marubatsu.CIRCLE or \
       self.board[1][0] == self.board[1][1] == self.board[1][2] == Marubatsu.CIRCLE or \
       self.board[2][0] == self.board[2][1] == self.board[2][2] == Marubatsu.CIRCLE or \
       self.board[0][0] == self.board[1][1] == self.board[2][2] == Marubatsu.CIRCLE or \
       self.board[2][0] == self.board[1][1] == self.board[0][2] == Marubatsu.CIRCLE:
        winner = Marubatsu.CIRCLE
        
    # × の勝利の判定
    if self.board[0][0] == self.board[1][0] == self.board[2][0] == Marubatsu.CROSS or \
       self.board[0][1] == self.board[1][1] == self.board[2][1] == Marubatsu.CROSS or \
       self.board[0][2] == self.board[1][2] == self.board[2][2] == Marubatsu.CROSS or \
       self.board[0][0] == self.board[0][1] == self.board[0][2] == Marubatsu.CROSS or \
       self.board[1][0] == self.board[1][1] == self.board[1][2] == Marubatsu.CROSS or \
       self.board[2][0] == self.board[2][1] == self.board[2][2] == Marubatsu.CROSS or \
       self.board[0][0] == self.board[1][1] == self.board[2][2] == Marubatsu.CROSS or \
       self.board[2][0] == self.board[1][1] == self.board[2][2] == Marubatsu.CROSS:
        winner = Marubatsu.CROSS     

    # 引き分けの判定
    if not(self.board[0][0] == Marubatsu.EMPTY or \
           self.board[1][0] == Marubatsu.EMPTY or \
           self.board[2][0] == Marubatsu.EMPTY or \
           self.board[0][1] == Marubatsu.EMPTY or \
           self.board[1][1] == Marubatsu.EMPTY or \
           self.board[1][1] == Marubatsu.EMPTY or \
           self.board[0][2] == Marubatsu.EMPTY or \
           self.board[1][2] == Marubatsu.EMPTY or \
           self.board[2][2] == Marubatsu.EMPTY):
        winner = Marubatsu.DRAW

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

Marubatsu.judge = judge
```

### `judge` メソッドのバグについて

`judge` メソッドのように、__ある程度複雑__ な処理を行うプログラムを実装した場合は、正しく動作するかどうかを __確認する__ ことが __特に重要__ になります。

既に気づいている人がいるかもしれませんが、そのことを __実感できるようにする__ ために、上記の `judge` メソッドには __わざと__ いくつのか __バグを入れてあります__。次回の記事では、それらのバグを、__関数のテスト__ を行うことで __見つける方法__ について説明します。余裕があれば、上記のプログラムにどのようなバグがあるかについて考えてみて下さい。バグのヒントを見たいは、下記をクリックして下さい。

<details><summary><strong>バグのヒント</strong></summary>

下記の 3 つのバグがあります。なお、3 つ目のバグを見つけるのは初心者にはかなり難しいかもしれません。

* 単純な入力ミスが原因のバグが 2 つ
* いずれかの判定を行う方法が間違っていることが原因のバグ
 
</details>

# 今回の記事で作成したプログラムについて

これで勝敗判定を行う `judge` メソッドが実装できましたが、実装があまりにも __長くまだるっこしい__ と思った人が多いのではないかと思います。また、ある程度のプログラムの知識がある方は、もっと __効率的な実装方法__ を思いついた人が多いかもしれません。

もちろん、もっと効率的な実装ができる人はそのような方法でいきなり `judge` メソッドを実装しても構いませんが、上記のような実装が __間違っているわけではありません__。

また、__効率を重視__ したプログラムは、どうしても記述したプログラムが __わかりづらくなる__ という __欠点__ があります。その点では、上記のような __効率を考えず__ に、__行いたいこと__ をそのまま __地道に記述する__ プログラムは、プログラムが長くなるという欠点はありますが、__何を行っているかが明確__ なため、初心者にとってはバグが発生した場合でもどこにバグがあるかを見つけることが __比較的容易__ であるという利点があります。

そもそも、__正しく動作する__ プログラムを記述することができなければ __先に進むことは不可能です__。初心者のうちは、__最初は効率を考えず__ にプログラムを記述し、プログラムが正しく動作する事が __確認できてから__ 効率の良いプログラムになるように __修正する__ という方法があるということは覚えておいてください。

他の勝敗判定を行うプログラムの実装方法については、次回以降の記事で説明します。

# 複数のデータ型に対する型アノテーション

今回の記事で実装した `judge` メソッドは、__文字列型__ と、__None 型__ の 2 種類のデータ型を返す関数です。そのような場合は、過去の記事の [関数の引数と返り値のデータ型のヒント（型アノテーション）の表記方法](https://qiita.com/ysgeso/items/4d55e73d6571fe85aa49#%E9%96%A2%E6%95%B0%E3%81%AE%E5%BC%95%E6%95%B0%E3%81%A8%E8%BF%94%E3%82%8A%E5%80%A4%E3%81%AE%E3%83%87%E3%83%BC%E3%82%BF%E5%9E%8B%E3%81%AE%E3%83%92%E3%83%B3%E3%83%88%E3%81%AE%E8%A1%A8%E8%A8%98) で説明した方法では、関数の返り値の __型アノテーション__ を記述することが出来ません。

__複数のデータ型__ を表す型アノテーションは、下記のプログラムのように、複数のデータ型を表す文字を、__半角の `|`__[^7]で区切って表記します。なお、`|` は、Shift キーを押しながら、`￥` が印字されたキーを押すことで入力することが出来ます。

```python
def judge(self) -> str | None:
```

型アノテーションで使用する `|` は Python の __バージョン 3.10 で追加された機能__ なので、__3.9 以前__ のバージョンで記述すると __エラーが発生します__。ただし、3.7 から 3.9 のバージョンでは `from __future__ import annotations` を記述することで利用できるようになります。

次回以降の記事からは、3.7 から 3.9 のバージョンを使っている人で __エラーが発生しないようにするため__ に、marubatsu.py の __先頭に `from __future__ import annotations` を記述する__ ことにします。3.6 以前のバージョンを使っている方は、この機会に最新の python のバージョンの仮想環境を作成することをお勧めします。


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

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

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

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

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

# 次回の記事

https://qiita.com/ysgeso/items/728cee8982fe0ed9da38

# 更新履歴

| 更新日時 | 更新内容 |
|:--|:--|
| 2023/11/25 | データ型のヒントを、型アノテーションに修正しました |

[^1]:今回の記事では紹介しませんが、list や dict を使って複数のデータを返すという方法も良く使われます
[^2]:bound（束縛）とは、__対応づけ__ を表すプログラム用語です。従って、unbound local とは、ローカル変数がオブジェクトに対応付けられていない（unbound）こと、すなわち名前空間に登録されていないことを意味します
[^3]:Microsoft Office で使われる VBA というプログラム言語など、一部のプログラム言語では、and と or の論理演算子で短絡評価を行わないものもあるようです
[^4]:プログラム言語によって、論理演算子を表す __表記が異なる__ 場合があります。例えば、C 言語や JavaScript では、and 演算子を `&&` のように表記します
[^5]:詳細は長くなるので省略しますが、そのような式を他のプログラム言語で記述しても __エラーにはなりません__ が、Python の場合とは __全く異なる計算__ が行われます
[^6]:最初の and 演算子を使った条件式を、or 演算子と not 演算子を組み合わせて置き換える際に、__ド・モルガンの法則__ という法則を使っています。参考までにド・モルガンの法則の wikipedia の [リンク](https://ja.wikipedia.org/wiki/%E3%83%89%E3%83%BB%E3%83%A2%E3%83%AB%E3%82%AC%E3%83%B3%E3%81%AE%E6%B3%95%E5%89%87) を貼っておきます
[^7]:`|` は、パイプ（pipe）、バーチカルバー（垂直（vertical）の棒（bar））など、いくつかの呼び方があります