Pythonで〇×ゲームのAIを一から作成する　その18　ゲーム盤の表示

# 目次と前回の記事

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

https://qiita.com/ysgeso/items/4d55e73d6571fe85aa49

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

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

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

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

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

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

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

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

## 前回までのおさらい

前回の記事では、モジュールの作成とインポートの方法、docstring 等の書き方について説明しました。

# ゲーム盤の表示

ここまでの記事では、ゲーム盤のデータを下記のプログラムのように __`print` で直接__ 表示していました。

なお、今回の記事から、marubatsu.py に保存した marubatsu モジュールから __必要な関数をインポート__ してプログラムを記述します。前回の記事で説明したように、marubatsu.py は __記事ごとに更新していく__ ので、記事のプログラムを VSCode に __実際に入力して実行__ する方は、記事の最初にあるリンク先から __これまでに作成したモジュールをダウンロード__ して下さい。下記のプログラムの 1 行目では、marubatsu モジュールから __`initialize_board` をインポート__ しています。

```python 
from marubatsu import initialize_board

board = initialize_board()
print(board)
```
実行結果
```
[[' ', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]
```

実行結果のような、ゲーム盤のデータを __`print` で直接表示__ するという方法は __直観的ではなく__、__わかりづらい__ ので、今回の記事では、ゲーム盤を __わかりやすく表示する__ プログラムを記述することにします。

ゲーム盤の表示は、「その 3」の記事で説明した、__UI の出力__ に相当する処理です。UI には __文字だけを使う CUI__ と __画像を使う GUI__ があります。__GUI__  は CUI に比べて __見栄えが良いという利点__ がありますが、__実装__ するのが CUI に比べて __大変__ なので、今回の記事では __実装が簡単__ な __CUI でゲーム盤の表示__ を行うことにします。GUI のゲーム盤の表示は、一通り〇×ゲームが完成してから実装することにします。

## CUI でのゲーム盤の表示

CUI は __文字だけ__ でゲーム盤を __表示__ するので、これまで何度も利用してきた、文字列を表示する処理を行う組み込み関数 __`print` を使って実装__ することが出来ます。また、ゲーム盤のデータは、マークを表す __文字列__ が要素に代入された __2 次元配列を表す list__ で表現されているので、ゲーム盤の __全ての要素__ を `print` で __並べて表示__ する方法が考えられるでしょう。

__2 次元配列__ を表す list の __全ての要素__ を `print` で表示する処理は、下記のプログラムのように、__入れ子にした for 文__ による __繰り返し処理__ で行うことが出来ます。同様の繰り返し処理については「[その6](https://qiita.com/ysgeso/items/2a436b3819bef9a6a2b4#2-%E6%AC%A1%E5%85%83%E9%85%8D%E5%88%97%E3%82%92%E8%A1%A8%E3%81%99-list-%E3%81%AE%E8%A8%98%E8%BF%B0%E6%96%B9%E6%B3%95)」の記事で説明しましたので、忘れた方は復習して下さい。

```python
# 各列に対する繰り返しの処理を行う
for x in range(3):
    # x 列の各マスに対する繰り返しの処理を行う
    for y in range(3):
        # (x, y) のマスの要素を表示する
        print(board[x][y])
```
実行結果
```
 
 
 
 
 
 
 
 
 
```

実行結果を見ると、__何も表示されていないように見える__ かもしれませんが、実際には __半角の空白文字__ が __9 行分表示__ されています。そのことは、上記の実行結果の黒い部分を __マウスでドラッグして選択__ することで、下図のように確認することが出来ます。下図の __青い長方形__ が、__選択された半角の空白文字__ です。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/7149ebd4-4dba-afa6-ce65-f0ce9a179733.png" width="120">

このように、__空白文字__ を __`print` で表示__ すると、__何も表示されていないように見える__ ため、__CUI__ では __空白文字の代わり__ に __半角の `.`（ピリオド）など__ の別の文字列を表示することが良くあります。そこで、本記事でも、__空白のマス__ に対して、__半角の `.`__ を表示することにします。

:::note
空白のマスを、別の文字で表示したい人は、__自由に変更してもらってもかまいません__。お手本通りにプログラムを記述するのではなく、__自分でアレンジ__ を行うことは、__プログラミングの上達__ にもつながります。
:::

### 他の for 文を使った繰り返し

上記のプログラムでは、ゲーム盤の行と列の数が 3 であることから、__`range(3)`__ を使って for 文の繰り返しの処理を記述しましたが、__2 次元配列__ を表す list が代入された __`board` を直接使って__ for 文の繰り返しの処理を記述することが出来ます。

`board` の __3 つの要素__ は、ゲーム盤の __各列を表す list__ なので、下記のように for 文を記述することで、繰り返しのたびに、`board` から __各列のデータ__ を表す list を __取り出して__、__`col`__[^1] という変数に __代入する__ という処理を記述することが出来ます。

```python
# 繰り返しのたびに、board から各列のデータを取り出して col に代入する
for col in board:
```

この `col` は __列のデータ__ を表す __list__ なので、この `col` に対して下記の 4 行目のような for 文を記述することで、繰り返しのたびに __`col` から__ その __列の各マスのデータ__ を表す文字列を取り出して、__`cell`__ という変数に __代入する__ という処理を行うことが出来ます。そのため、6 行目のように、この 2 重の for 文の繰り返しのブロックで、`cell` を表示することで、ゲーム盤の __すべてのマスを表示__ することが出来ます。

```python
1  # 繰り返しのたびに、board から列のデータを取り出して col に代入する
2  for col in board:
3      # 繰り返しのたびに、col からマスのデータを取り出して cell に代入する
4      for cell in col:
5          # 取り出したマスのデータを表示する
6          print(cell)
``` 

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

```python
# 繰り返しのたびに、board から列のデータを取り出して col に代入する
for col in board:
    # 繰り返しのたびに、col からマスのデータを取り出して cell に代入する
    for cell in col:
        # 取り出したマスのデータを表示する
        print(cell)
```
</details>

実行結果
```
 
 
 
 
 
 
 
 
 
```

理由は後で説明しますが、この方法を __ゲーム盤を表示する処理に使うことはできない__ ので、本記事では __`range` を使う方法を採用__ します。

## 空白のマスに `.` を表示する方法

ゲーム盤を表示する際に、空白のマスを __半角の `.`__ で表示する方法は __いくつかあります__ が、本記事ではその中の 2 つの方法を紹介します。

### if 文を使う方法

__if 文__ を使って、下記の __7~11 行目__ のプログラムを記述することで、__空白のマスに対して半角の `.` を表示__ することが出来ます。__マークが配置されていないマス__ を表す要素には __半角の空白文字が代入__ されているので、7 行目の if 文の __条件式__ で (x, y) のマスが __空であるかどうかを判定__ し、その場合は 8 行目で半角の `"."` を、そうでなければ 11 行目で (x, y) のマスの要素の値をそのまま表示しています。

実行結果から __9 つの半角の `.`__  が表示されることを確認することが出来ます。

```python
 1  # 各列に対する繰り返しの処理を行う
 2  for x in range(3):
 3      # x 列の各マスに対する繰り返しの処理を行う
 4      for y in range(3):
 5          # (x, y) のマスの要素を表示する
 6          # 空白のマスの場合は . を表示する
 7          if board[x][y] == " ":
 8              print(".")
 9          # そうでなければ、(x, y) のマスの要素をそのまま表示する
10          else:
11              print(board[x][y])
```

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

```python
# 各列に対する繰り返しの処理を行う
for x in range(3):
    # x 列の各マスに対する繰り返しの処理を行う
    for y in range(3):
        # (x, y) のマスの要素を表示する
        # 空白のマスの場合は . を表示する
        if board[x][y] == " ":
            print(".")
        # そうでなければ、(x, y) のマスの要素をそのまま表示する
        else:
            print(board[x][y])
```
</details>

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

```diff_python
# 各列に対する繰り返しの処理を行う
for x in range(3):
    # x 列の各マスに対する繰り返しの処理を行う
    for y in range(3):
        # (x, y) のマスの要素を表示する
-       print(board[x][y])
+       # 空白のマスの場合は . を表示する
+       if board[x][y] == " ":
+           print(".")
+       # そうでなければ、(x, y) のマスの要素をそのまま表示する
+       else:
+           print(board[x][y])
```
</details>


実行結果
```
.
.
.
.
.
.
.
.
.
```

### 空白のマスを表す文字列を変更する方法

これまでは空白のマスを表す __要素__ に __半角の空白文字__ を代入していましたが、空白のマスを表す要素に __半角の `.` を代入__ することで、__if 文を使わずに__ 空白のマスに対して半角の `.` を表示することが出来ます。

そのためには、下記のプログラムのように __`initialize_board` を修正__ する必要があります。修正した内容は docstring の説明文と、__return 文__ の __`" "` を `"."` に変更__ しただけです。具体的な修正を行った行については、下記の修正箇所をクリックして見て下さい。

関数を修正する際に、docstring の修正箇所も表記すると長くなるので、以降は __関数の修正__ の際に __docstring は省略__ します。修正した docstring については記事の最後の marubatsu_new.py を見て下さい。

```python
def initialize_board() -> list[list[str]]:
    """ 初期化されたゲーム盤のデータを返す.

    Returns:
        初期化されたゲーム盤を表す 2 次元配列の list.
        全ての要素に半角の "." が代入されている.
    """

    return [["."] * 3 for x in range(3)]
```

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

```diff_python
def initialize_board() -> list[list[str]]:
    """ 初期化されたゲーム盤のデータを返す.

    Returns:
        初期化されたゲーム盤を表す 2 次元配列の list.
-       全ての要素に半角の空白文字が代入されている.
+       全ての要素に半角の "." が代入されている.
    """

-   return [[" "] * 3 for x in range(3)]
+   return [["."] * 3 for x in range(3)]
```
</details>

`from marubatsu import initialize_board` を実行した後で、上記のプログラムのように __インポートした `initialize_board`__ と __同じ名前の関数の定義__ を実行すると、__新しく定義した関数__ で、__`initialize_board` が上書き__ されます。このように、__既に定義__ されている関数と __同じ名前の関数の定義を実行__ して上書きすることを、__関数の再定義__ と呼びます。

__修正した `initialize_board`__ と `range` を使った for 文による繰り返し処理を記述することで、下記のプログラムのように、__if 文を使うことなく__、__空白のマスに__ 対して __半角の `.` を表示__ することが出来ます。

```python
board = initialize_board()

# 各列に対する繰り返しの処理を行う
for x in range(3):
    # x 列の各マスに対する繰り返しの処理を行う
    for y in range(3):
        # (x, y) のマスの要素を表示する
        print(board[x][y])
```
実行結果
```
.
.
.
.
.
.
.
.
.
```

こちらのほうがプログラムの変更が少ないので、__本記事ではこちらの方法を採用__ することにします。

## print で改行を行わない方法

上記のプログラムでは、マスのデータを __表示するたび__ に __改行__ が行われますが、それは __`print`__ が文字列を表示した後で、__自動的に改行を行う__ からです。

`print` は、下記のプログラムの 8 行目のように、__実引数の最後に `end=""`__ を記述することで、__改行を行わずに__ 文字列を表示することが出来ます。

```python
1  board = initialize_board()
2
3  # 各列に対する繰り返しの処理を行う
4  for x in range(3):
5      # x 列の各マスに対する繰り返しの処理を行う
6      for y in range(3):
7          # (x, y) のマスの要素を改行せずに表示する
8          print(board[x][y], end="")
```

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

```python
board = initialize_board()

# 各列に対する繰り返しの処理を行う
for x in range(3):
    # x 列の各マスに対する繰り返しの処理を行う
    for y in range(3):
        # (x, y) のマスの要素を改行せずに表示する
        print(board[x][y], end="")
```
</details>

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

```diff_python
board = initialize_board()

# 各列に対する繰り返しの処理を行う
for x in range(3):
    # x 列の各マスに対する繰り返しの処理を行う
    for y in range(3):
        # (x, y) のマスの要素を改行せずに表示する
-       print(board[x][y])
+       print(board[x][y], end="")
```
</details>


実行結果
```
.........
```

:::note warn
`print` の __最後の実引数__ に、__`end=""` を記述__ することで、__改行を行わずに文字列を表示__ することが出来る。
:::

この __`end=""`__ の意味について正しく理解するためには、Python の __位置引数__、__キーワード引数__、__デフォルト引数__ について理解する必要があります。

### 位置引数とキーワード引数

これまでのプログラムでは、関数呼び出しを行う際に記述する __実引数__ は、関数の定義に記述された __仮引数の順番と対応__ して記述していました。このような、__仮引数の順番と対応__ して記述する __実引数__ の事を、仮引数の __位置に対応__ した場所に記述することから、__位置引数__ と呼びます。

__多くの仮引数__ を持つ関数に対して、__位置引数__ として __実引数を記述__ すると、どのような __順番__ で実引数を記述するかや、記述した実引数が __何のデータを表しているか__ が __わかりづらくなる__ という欠点があります。そのような場合は、実引数を __`仮引数名=実引数`__ のように記述して呼び出すという方法が便利です。この方法では、実引数を __仮引数の名前__ という __キーワード__ を使って __指定__ するので、このような方法で記述された実引数のことを、__キーワード引数__ と呼びます。

具体例を挙げます。これまでに作成した、`place_mark` という関数の仮引数は、先頭から順番に `board`、`x`、`y`、`mark` という名前が付けられています。下記のプログラムは、この関数呼び出しの実引数をキーワード引数で記述したものです。なお、今回の記事ではまだ `place_mark` をインポートしていないので、1 行目でインポートしています。

下記のプログラムの 4 行目のように、__キーワード引数__ を使うことで、関数の定義に記述された __仮引数の順番__ とは __異なる順番__ で、__実引数を記述__ することが出来ます。また、それぞれの実引数が __何のデータ__ を表しているかが __明確__ になります。

:::note warn
__実引数__ が何のデータを表しているかを __明確にする__ ために、__すべての実引数の位置__ が仮引数と __同じ場合でも__、__キーワード引数__ を使って __実引数を記述__ する場合が __良くあります__。
:::

```python
from marubatsu import place_mark

board = initialize_board()
place_mark(mark="〇", y=1, x=0, board=board)
print(board)
```
実行結果
```
( 0 , 1 ) のマスにはマークが配置済です
[['.', '.', '.'], ['.', '.', '.'], ['.', '.', '.']]
```

キーワード引数を記述する際にわかりづらいのは、上記の __`board=board`__ のように、__変数に同じ名前の変数を代入__ するという、__一見すると意味のない__ 記述を行っているように __見える__ 部分です。

キーワード引数では、関数呼び出しの際に行われる、__仮引数に実引数を代入__ するという __処理を記述__ します。従って、キーワード引数の __`=` の前__ に記述されているのは、関数の __ローカル変数__ である __仮引数__、__`=` の後ろ__ に記述されているのは __グローバル変数__ なので、この 2 つは __同じ名前__ であっても __異なる変数__ です。

:::note warn
キーワード引数を記述する際に良く見られる、__`board=board`__ のような記述は、__ローカル変数__ である __仮引数__ `board` に、__グローバル変数__ である __実引数__ `board` を __代入する処理__ を表す。
:::

### 上記のプログラムのバグ

上記のプログラムを実行すると、実行結果に「__( 0 , 1 ) のマスにはマークが配置済です__」が表示されてしまいます。これは、先ほど __マークが配置されていないマス__ を表す文字列を __半角の空白文字__ から __半角の `.`__ に __変更__ したにも関わらず、`place_mark` の処理で、(x, y) のマスに __マークが配置されていない__ ことを __判定する if 文__ を __変更していない__ ことが原因です。

このように、プログラムで __何かを変更__ した場合は、その変更した内容に __関連する部分をすべて修正__ する必要があり、その __修正を忘れる__ と上記のような __バグが発生してしまう__ 点に注意が必要です。

下記のプログラムのように、`place_mark` の 2 行目の if 文の __`" "` を `"."` に修正__ することで、このバグを修正することができます。実際に実行結果にマークが配置済というメッセージが表示されなくなります。

```python
1  def place_mark(board: list[list[str]], x: int, y: int , mark: str):
2      if board[x][y] == ".":
3          board[x][y] = mark
4      else:
5          print("(", x, ",", y, ") のマスにはマークが配置済です")
6
7  board = initialize_board()
8  place_mark(mark="〇", y=1, x=0, board=board)
9  print(board)
```

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

```python
def place_mark(board: list[list[str]], x: int, y: int , mark: str):
    if board[x][y] == ".":
        board[x][y] = mark
    else:
        print("(", x, ",", y, ") のマスにはマークが配置済です")

board = initialize_board()
place_mark(mark="〇", y=1, x=0, board=board)
print(board)
```
</details>

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

```diff_python
def place_mark(board: list[list[str]], x: int, y: int , mark: str):
-   if board[x][y] == " ":
+   if board[x][y] == ".":
        board[x][y] = mark
    else:
        print("(", x, ",", y, ") のマスにはマークが配置済です")

board = initialize_board()
place_mark(mark="〇", y=1, x=0, board=board)
print(board)
```
</details>


実行結果
```
[['.', '〇', '.'], ['.', '.', '.'], ['.', '.', '.']]
```

### 位置引数とキーワード引数の両方を使う場合

関数呼び出しを記述する際に、__位置引数__ と __キーワード引数__ の __両方を使う__ こともできます。ただし、その場合は、必ず __すべての位置引数__ を __キーワード引数よりも先__ に記述する必要があります。下記のプログラムはそのように記述されているので正しく動作します。

```python
board = initialize_board()
place_mark(board, 0, mark="〇", y=1)
print(board)
```
実行結果
```
[['.', '〇', '.'], ['.', '.', '.'], ['.', '.', '.']]
```

一方、下記のプログラムは、位置引数 __よりも前に__ キーワード引数を記述しているので、下記の実行結果のように、実行すると __エラーが発生__ します。

```python
board = initialize_board()
place_mark(y=1, 0, mark="〇", board=board)
print(board)
```
実行結果
```
  Cell In[11], line 2
    place_mark(y=1, 0, mark="〇", board=board)
                                            ^
SyntaxError: positional argument follows keyword argument
```

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

* __SyntaxError__
構文（文法のこと）（syntax）に関するエラー
* __positional argument follows keyword argument__
位置引数（positional argument）が、キーワード引数（keyword argument）の後（follows）に記述されている
:::

### デフォルト引数とデフォルト引数値

関数の仮引数には、__デフォルト引数値__[^2] という、__実引数を記述しなかった場合__ に __代入される値__ を設定することが出来ます。デフォルト引数値は、関数の定義の際に __`仮引数名=デフォルト引数値`__ のように記述します。また、デフォルト引数値が設定された仮引数の事を、__デフォルト引数__ と呼びます。

:::note warn
__デフォルト引数__ と __デフォルト引数値__ は名前が似ていて __紛らわしい__ ので、本記事では以降は、__デフォルト引数値__ の事を __デフォルト値__ と表記します。
:::

下記のプログラムは、1、2 行目で、3 つの仮引数の合計を計算して返り値として返す `add` という関数を定義しています。3 つの仮引数のうちの最後の仮引数 `z` に __`z=0` を記述__ することで、関数呼び出しの際に、`z` 対応する __実引数を記述しなかった場合__ は、__`z` に `0` が代入__ されて関数の処理が行われます。

この関数の __仮引数 `z`__ は __デフォルト引数__ で、その __デフォルト値は `0`__ です。


```python
def add(x, y, z=0):
    return x + y + z

print(add(1, 2))
print(add(1, 2, 3))
```
実行結果
```
3
6
```

上記のプログラムの 4 行目では、__デフォルト引数 `z` に対応__ する __実引数を記述せず__ に関数呼び出しを行っているので、__`z` に `0` が代入__ されて処理が行われた結果、`1 + 2 + 0` が計算されて `3` が返り値として返ります。

上記のプログラムの 5 行目では、__デフォルト引数 `z` に対応__ する __実引数に `3` を記述__ して関数呼び出しを行っているので、__`z` に `3` が代入__ されて処理が行われた結果、`1 + 2 + 3` が計算されて `6` が返り値として返ります。

:::note
上記のプログラムでは、2 つまたは 3 つの実引数の合計を計算する関数を定義していますが、この方法では、__任意の数の仮引数__ の合計を計算する関数を定義する事は __できません__。任意の数の仮引数を扱う関数は、デフォルト引数とは __別の方法__ で定義することが出来ます。その方法については、必要になった時点で説明します。
:::

デフォルト引数は、デフォルト値を指定しない __通常の仮引数より後__ に記述する必要があります。例えば、下記のプログラムの `add` では、デフォルト値を指定しない __通常の仮引数 `y`__ が、__デフォルト引数 `x` より後__ に記述されているので、実行すると __エラーが発生__ します。

```python
def add(x=0, y, z=0):
    return x + y + z
```
実行結果
```
  Cell In[13], line 1
    def add(x=0, y, z=0):
                 ^
SyntaxError: non-default argument follows default argument
```

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

* __SyntaxError__
構文（文法のこと）（syntax）に関するエラー
* __non-default argument follows default argument__
デフォルト引数でない仮引数（non-default argument）が、デフォルト引数（default argument）の後（follows）に記述されている
:::

### キーワード引数とデフォルト引数

__デフォルト引数が複数記述__ されている関数に対して、__途中のデフォルト引数__ に対応する __実引数を省略__ して関数呼び出しを記述する場合は、__キーワード引数を利用する必要__ があります。

言葉の説明ではわかりづらいと思いますので、具体例を挙げます。下記のプログラムは、5 つの仮引数の値を `print` で表示するという処理を行う `print_data` という関数を定義しています。この関数は、5 つの仮引数のうち、__`c` と `d` と `e` はデフォルト引数__ で、いずれも __デフォルト値は `None`__ です。

この関数に対する関数呼び出しの際に、__`c` と `d` に対応する実引数を省略__ する場合は、下記のプログラムの 4 行目のように、__省略しない `e` に対応する実引数__ は __キーワード引数で記述する必要__ があります。
```python
def print_data(a, b, c=None, d=None, e=None):
    print("a =", a, "b =", b, "c =", c, "d =", d, "e =", e)

print_data(1, 2, e=5)
```
実行結果
```
a = 1 b = 2 c = None d = None e = 5
```

上記のように記述する必要がある理由は、下記のプログラムのように、__キーワード引数を使わず__ に __3 つ目の実引数を記述__ した場合は、3 つ目の実引数は __位置引数__ になり、__仮引数 `c` に対応__ する実引数だとみなされてしまうからです。下記のプログラムの場合は、実行結果からわかるように、__`d` と `e`__ に対応する実引数が __省略__ されたと __みなされます__。

```python
print_data(1, 2, 5)
```
実行結果
```
a = 1 b = 2 c = 5 d = None e = None
```

### デフォルト引数に関する注意点

デフォルト引数の __デフォルト値に__、list などの __ミュータブルなデータを記述__ すると、__意図しない処理__ が行われる __可能性がある__ ので、__避けたほうが良い__ でしょう。

その理由については複雑な話になるので、下記の説明の意味が分からない場合は __読み飛ばしてもらっても構いません__。ただし、読み飛ばす場合でも、理屈はわからなくても構わないので、__意図しない処理が行われないよう__ に、__デフォルト値__ には、__数値型__、__文字列型__、__論理型__、__None 型__ のいずれかのデータを記述するように気を付けて下さい。

:::note
より正確には、「その 11」の記事で説明した、__[疑似的な複製](https://qiita.com/ysgeso/items/cc9e67c16b0814c8bc2e#%E7%96%91%E4%BC%BC%E7%9A%84%E3%81%AA%E8%A4%87%E8%A3%BD)__ が行われるデータをデフォルト値にすれば、意図しない処理が行われることはありません。
:::

:::note
参考までに、このことに関する Python の公式ドキュメントへのリンクを挙げておきます。
:::

https://docs.python.org/ja/3/faq/programming.html#why-are-default-values-shared-between-objects

#### デフォルト値に関するよくある勘違い

デフォルト値は、__常に__ 関数の定義で記述した値であると __勘違いされがち__ ですが、実際には、__デフォルト値__ は関数呼び出しが行われると __変化する場合__ があります。

具体例を挙げて説明します。下記のプログラムの `append_list` は、仮引数 `alist` に代入された list の要素に、仮引数 `data` に代入されたデータを追加し、alist を返り値として返すという処理を行う関数です。

:::note
仮引数の名前を `alist` にしたのは、__`list` という名前の組み込み関数が存在する__ ためです。`alist` は、関数のブロックの中で要素を追加（append）する list であることから、そのような名前を付けました。わかりにくいと思った方は、__別の名前に変更してもかまいません__。
:::

デフォルト引数である `alist` の __デフォルト値__ には、__空の list が記述__ されているので、この関数で以下のような処理が行われると __勘違いする__ 人が多いのではないでしょうか？

:::note alert
下記の説明は __間違っている__ ので背景が赤いノートに記述しています。

* 仮引数 __`alist` に対応__ する実引数を __記述した場合__ は、`alist.append(data)` が実行され、__`alist` の要素に `data` が追加された list__ が __返り値として返る__
* 仮引数 __`alist` に対応__ する実引数を __省略した場合__ は、`alist` に デフォルト値である __空の list が代入__ される。`alist.append(data)` が実行され、`data` のみを要素とする __`[data]` が返り値として返る__
:::

```python
1  def append_list(data, alist=[]):
2      alist.append(data)
3      return alist
4
5  print(append_list(2, [0, 1]))         # alist に対応する実引数を記述した場合
6  print(append_list("c", ["a", "b"]))
7  print(append_list(1))                 # alist に対応する実引数を省略した場合
8  print(append_list(2))
```

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

```python
def append_list(data, alist=[]):
    alist.append(data)
    return alist

print(append_list(2, [0, 1]))         # alist に対応する実引数を記述した場合
print(append_list("c", ["a", "b"]))
print(append_list(1))                 # alist に対応する実引数を省略した場合
print(append_list(2))
```
</details>

実行結果
```
[0, 1, 2]
['a', 'b', 'c']
[1]
[1, 2]
```

上記のプログラムの 5 行目では、__`alist` に対応__ する実引数を __記述__ しており、__その実引数の `[0, 1]` の要素__ に、`data` に対応する実引数の `2` が追加されるので、実行結果は `[0, 1, 2]` となります。

6 行目でも、__`alist` に対応__ する実引数を __記述__ しており、同様の処理が行われます。このように、__`alist` に対応__ する実引数を __記述__ した場合の処理は、先ほどのノートで __説明した通りの処理__ が行われます。

7 行目では、__`alist` に対応__ する実引数を __省略__ しており、__空の list の要素__ に `data` に対応する実引数の `1` が追加されるので、実行結果は `[1]` となります。

8 行目でも、__`alist` に対応__ する実引数を __省略__ しており、空の list の要素に `data` に対応する実引数の `2` が追加されるので、実行結果は `[2]` となると思うかもしれませんが、__実際には `[1, 2]`__ になります。このことから、先ほどの __`alist` に対応__ する実引数を __省略__ した場合の __処理の説明__ は、__間違っている__ ことがわかります。

#### デフォルト引数が記述された関数の定義で行われる処理

デフォルト引数が記述された関数の定義を実行すると、以下のような処理が行われます。なお、この処理は、関数呼び出し __ではなく__、__関数の定義が実行__ された時に行われる処理である点に注意して下さい。

1. __関数の定義__ を管理する __オブジェクトが作成__ される
2. __デフォルト値__ を管理する __オブジェクトが作成__ される
3. __関数の定義__ を管理する __オブジェクトの中__ に、__デフォルト引数の名前__ から __デフォルト値__ を管理する __オブジェクトへの対応づけ__ が __記録__ される

上記の手順 3 から、関数の定義を実行すると、関数の定義を管理するオブジェクトの中に、__デフォルト引数名__ から __デフォルト値__ を管理するオブジェクトを __対応づける名前空間が記録__ されます。この名前空間は、関数呼び出しの際に、デフォルト引数に対応する __実引数が省略__ された __場合でのみ利用__ されます。

下図は、先程の `append_list` の定義を実行した際に作成されるオブジェクトです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/434b88b0-a867-f16d-3545-5e6d0d823c8a.png" width="350">

#### 7 行目の `append_list(1)` で行われる処理

関数呼び出しの際に、デフォルト引数に対応する __実引数を記述しなかった場合__ は、デフォルト引数は、その関数の定義の中で __デフォルト引数名に対応づけられたオブジェクトを参照__ します。

言葉の説明では意味が分かりづらいと思いますので、先ほどのプログラムの __7 行目の `append_list(1)`__ の関数呼び出しを行った __直後の状態__ を下図に示します。図のように、`append_list` の __ローカル変数 `alist`__ は、__`append_list` の定義__ を管理する __オブジェクトの中__ の、__`alist` に対応づけられた__ オブジェクトを __参照__ します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/71192f8e-fd68-30ed-4283-3ca7a41468d7.png" width="600">

この後で、`append_list` のブロック内の __`alist.append(data)`__ が実行されると、下図のような状態になります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/754fcc71-fbbf-e8db-4e1e-3fd69f18a907.png" width="600">

図からわかるように、__`append_list` の定義__ を管理する __オブジェクトの__ 中の、__`alist` に対応づけられた__ オブジェクトが管理する __データ__ は、空の list から、__`[1]` に変化__ します。

#### 8 行目の `append_list(2)` で行われる処理

この状態で、次の 8 行目の `append_list(2)` の関数呼び出しを実行すると、`append_list` の __ローカル変数 `alist`__ には、空の list __ではなく__、__`[1]` が代入__ されます。そのため、`append_list` のブロック内の `alist.append(data)` が実行されると、`alist` に代入されたデータは __`[1, 2]`__ になります。

このような現象は、デフォルト値がデータの中身を後から変更できる __ミュータブルなデータ__ の場合に発生します。従って、データの中身を変更することができない、__数値型__、__文字列型__、__論理型__、__None 型__ のデータなどの、__疑似的な複製__[^3]が行われるデータでは __発生しません__。

:::note
下記のプログラムを記述することで、上記の `append_list` を、「__`alist` に対応__ する実引数を __省略__ した場合は、__`data`__ に対応する __実引数のみ__ を要素として持つ __list を返す__」ように __修正する__ ことが出来ます。

下記のプログラムは、1 行目で、`alist` のデフォルト値を __`None`__ に変更しています。None は __疑似的な複製__ が行われるデータなので、`alist` に対応する実引数が __省略__ されて `append_list` が呼び出された場合は、`alist` には __常に `None` が代入__ されます。

2 行目の if 文で __`alist` が `None` であることを判定__ し、__その場合のみ__ 3 行目で __`alist` に空の list を代入__ しています。なお、これまでの記事で説明していませんでしたが、__データが `None` であるかどうかを判定__ する場合は、`==` 演算子ではなく、__`is` 演算子を記述する必要__ があります。`is` 演算子の詳細については、必要になった時点で説明します。

先程の場合と異なり、10 行目の実行結果が `[2]` になっていることを確認して下さい。

```python
 1  def append_list(data, alist=None):
 2      if alist is None:
 3         alist = []
 4      alist.append(data)
 5      return alist
 6
 7  print(append_list(2, [0, 1]))         # alist に対応する実引数を記述した場合
 8  print(append_list("c", ["a", "b"]))
 9  print(append_list(1))                 # alist に対応する実引数を省略した場合
10  print(append_list(2))
```

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

```python
def append_list(data, alist=None):
    if alist is None:
        alist = []
    alist.append(data)
    return alist

print(append_list(2, [0, 1]))         # alist に対応する実引数を記述した場合
print(append_list("c", ["a", "b"]))
print(append_list(1))                 # alist に対応する実引数を省略した場合
print(append_list(2))
```
</details>

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

```diff_python
- def append_list(data, alist=[]):
+ def append_list(data, alist=None):
+   if alist is None:
+       alist = []
    alist.append(data)
    return alist

print(append_list(2, [0, 1]))         # # alist に対応する実引数を記述した場合
print(append_list("c", ["a", "b"]))
print(append_list(1))                 # # alist に対応する実引数を省略した場合
print(append_list(2))
```
</details>

実行結果
```
[0, 1, 2]
['a', 'b', 'c']
[1]
[2]
```
:::

### `print` の仮引数 `end` の意味

`print` には、__`end`__ という、`print` で文字列を表示した後に __追加して表示する文字列__ を表す仮引数があります。__`end` はデフォルト引数__ になっており、その __デフォルト値__ は __改行を表す文字列__ になっています。そのため、`print` に対して関数呼び出しを行う際に、実引数に __キーワード引数で `end` を記述しない__ 場合は、文字列を表示した後で __改行が行われます__。

従って、`print` の実引数に __`end=""`__（空文字）を記述することで、文字列を表示した後で __何も表示が追加されなくなる__ ので、__改行が行われなくなります__。

もちろん、`end` には __他の文字列を指定__ することもできます。下記のプログラムでは、`print` の実引数に `end="."` を記述することで、最後に半角の `.` を表示しています。

```python
print("abc", end=".")
```
実行結果
```
abc.
```

### まとめ

:::note warn
__位置引数に関するまとめ__

* __位置引数__ とは、__仮引数に対応__ する __位置__ に記述する __実引数__ のことである

__キーワード引数に関するまとめ__

* __キーワード引数__ とは、__`仮引数名=実引数`__ のように記述する __実引数__ のことである
* キーワード引数は、__仮引数の位置__ とは __無関係な場所__ に記述できる
* キーワード引数は、__すべて__ の __位置引数より後に記述__ する必要がある

__デフォルト引数とデフォルト値（デフォルト引数値）に関するまとめ__

* __デフォルト引数__ とは、関数呼び出しの際に __対応する実引数を省略__ できる __仮引数__ のことである
* デフォルト引数は、関数の定義で __`仮引数名=デフォルト値`__ のように記述する
* 関数呼び出しの際に、デフォルト引数に対応する __実引数が省略__ された場合は、__仮引数にはデフォルト値が代入__ される
* デフォルト引数は、__すべて__ の __通常の引数__ より __後に記述__ する必要がある
* 関数呼び出しの際に、__途中の__ デフォルト引数に対応する実引数を __省略__ する場合は、__省略しない__ デフォルト引数に対応する実引数を __キーワード引数で記述する必要__ がある
* デフォルト引数の仕組みを正しく理解していない場合は、デフォルト値には __数値型__、__文字列型__、__論理型__、__None 型__ のいずれかのデータ型のみを記述したほうが良い
:::

## ゲーム盤の行ごとに改行する方法

先程のプログラムでは、マスを表示する `print` の実引数に `end=""` を記述したので、__`.` が横に 9 つ並んで表示__ が行われました。次は、__ゲーム盤の行ごとに改行__ するようにプログラムを修正します。

改行は、__各列のマスに対する繰り返し__ を行う __内側の for 文__ が __終了した直後__ で行えばよいので、下記のプログラムのように 10 行目に __`print()` を追加__ します。なお、文字列を表示せずに __改行だけを行いたい__ 場合は、__`print()`__ のように、__実引数に何も記述せず__ に `print` を呼び出します[^4]。

実行結果から、全てのマスが空の __3 x 3 のゲーム盤__ がうまく表示されているように見えます。

```python
 1  board = initialize_board()
 2
 3  # 各列に対する繰り返しの処理を行う
 4  for x in range(3):
 5      # x 列の各マスに対する繰り返しの処理を行う
 6      for y in range(3):
 7          # (x, y) のマスの要素を改行せずに表示する
 8          print(board[x][y], end="")
 9      # x 列の表示が終わったので、改行する
10      print()
```

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

```python
board = initialize_board()

# 各列に対する繰り返しの処理を行う
for x in range(3):
    # x 列の各マスに対する繰り返しの処理を行う
    for y in range(3):
        # (x, y) のマスの要素を改行せずに表示する
        print(board[x][y], end="")
    # x 列の表示が終わったので、改行する
    print()
```
</details>

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

```diff_python
board = initialize_board()

# 各列に対する繰り返しの処理を行う
for x in range(3):
    # x 列の各マスに対する繰り返しの処理を行う
    for y in range(3):
        # (x, y) のマスの要素を改行せずに表示する
        print(board[x][y], end="")
+   # x 列の表示が終わったので、改行する
+   print()
```
</details>

実行結果
```
...
...
...
```

# ゲーム盤の表示を行う関数の定義

ゲーム盤の表示は、今後の〇×ゲームを実装する際に、__様々な場面で行う可能性が高い処理__ なので、ゲーム盤の表示を行う __関数を定義__ することにします。

関数を定義するためには、関数の __名前__、__仮引数__、関数が行う __処理__、__返り値__ を決める必要があるので、それぞれについて、以下のように決めることにします

* __関数の名前__ はゲーム盤（board）を表示（display）するので、__`display_board`__ とする
* __仮引数__ は __表示するゲーム盤のデータ__ を代入する __`board`__ とする
* __関数の処理__ は、`board` の内容を表示する
* __返り値__ は必要がないので、返り値を返さない関数とする

関数が行う処理のプログラムは __先ほどのプログラムと同じ__ なので、`display_board` は下記のプログラムのように定義することが出来ます

```python
def display_board(board: list[list[str]]):
    # 各列に対する繰り返しの処理を行う
    for x in range(3):
        # x 列の各マスに対する繰り返しの処理を行う
        for y in range(3):
            # (x, y) のマスの要素を改行せずに表示する
            print(board[x][y], end="")
        # x 列の表示が終わったので、改行する
        print()
```

# `display_board` の処理の確認

`display_board` が __正しく動作するか__ を、ゲーム盤に __マークを配置__ して __確認__ することにします。

下記のプログラムは、__(0, 1) のマスに 〇 を配置__ してゲーム盤を表示するプログラムです。

```python
board = initialize_board()
place_mark(board, 0, 1, "〇")
display_board(board)
```
実行結果
```
.〇.
...
...
```

実行結果から __`display_board`__ には、以下のような __問題点__ がある事がわかります。

* __(0, 1)__ ではなく、__(1, 0) のマス__ に 〇 が表示される
* 〇 を表示した結果、__上下の行で表示位置がずれる__

## マークの表示位置がおかしい原因

プログラムは、__記述した内容通りの処理が必ず実行__ されます。従って、プログラムで __意図しない処理__、すなわち __バグが発生__ した場合は、必ず __何らかの原因__ があります。バグの原因を見つける __方法の一つ__ に、__意図しない処理__ が行われるプログラムを __複数回実行__ することで、意図しない処理が行われる __法則を見つけ出し__、その __法則を元に__ バグの __原因を推測する__ という方法があります。

そこで、どのような __法則__ でマークが表示される __位置がずれる__ かを調べるために、もう一つ __(1, 2) のマスに × のマークを配置__ してみることにします。

```python
board = initialize_board()
place_mark(board, 0, 1, "〇")
place_mark(board, 1, 2, "×")
display_board(board)
```
実行結果
```
.〇.
..×
...
```

今度は、__(1, 2)__ ではなく、__(2, 1)__ のマスに × が表示されることがわかります。

2 つの __位置のずれ__ を __比較する__ と、__x 座標と y 座標が逆__ になっていることがわかります。そのことを念頭に置いて、`display_board` のプログラムを見てみると、下記の部分が __おかしい__ ことがわかります。

```python
    # 各列に対する繰り返しの処理を行う
    for x in range(3):
        # x 列の各マスに対する繰り返しの処理を行う
        for y in range(3):
            # (x, y) のマスの要素を改行せずに表示する
```

:::note
今回は __運よく__、すぐに法則が見つかりましたが、なかなか法則が見つからない場合は、さらにいくつかの処理を実行する必要があります。また、この方法では __バグの原因を見つけられない場合もある__ ので、見つからない場合はあきらめて __別の方法__ でバグの原因を探す必要があります。
:::

上記のプログラムは、__外側の for 文__ で __各列__ の繰り返しの処理を行い、__内側の for 文__ で __その列の各マス__ に対する繰り返し処理を行っています。このことから、このプログラムで表示される文字は、__0 列、1 列、2 列__ の順番で表示が行われることがわかります。

実際には、__0 行、1 行、2 行__ の順番で表示を行いたいので、for 文の __入れ子の順番が間違っている__ ことがこのバグの原因です。下記は、for 文の __入れ子の順番を入れ替えた__ プログラムで、実行するとマークが正しい位置に表示されるようになります。

```python
def display_board(board: list[list[str]]):
    # 各行に対する繰り返しの処理を行う
    for y in range(3):
        # y 行の各マスに対する繰り返しの処理を行う
        for x in range(3):
            # (x, y) のマスの要素を改行せずに表示する
            print(board[x][y], end="")
        # y 行の表示が終わったので、改行する
        print()

board = initialize_board()
place_mark(board, 0, 1, "〇")
place_mark(board, 1, 2, "×")
display_board(board)
```

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

```diff_python
def display_board(board: list[list[str]]):
-   # 各列に対する繰り返しの処理を行う
+   # 各行に対する繰り返しの処理を行う
-   for x in range(3):
+   for y in range(3):
-       # x 列の各マスに対する繰り返しの処理を行う
+       # y 行の各マスに対する繰り返しの処理を行う
-       for y in range(3):
+       for x in range(3):
            # (x, y) のマスの要素を改行せずに表示する
            print(board[x][y], end="")
        # y 行の表示が終わったので、改行する
        print()

board = initialize_board()
place_mark(board, 0, 1, "〇")
place_mark(board, 1, 2, "×")
display_board(board)
```
</details>


実行結果
```
...
〇..
.×.
```

:::note warn
__2 次元配列を表す list__ に対して、__for 文を入れ子__ にして繰り返しの処理を行う場合は、__入れ子の順番が重要__ になる場合があるので、__その点に注意して__ プログラムを記述する必要がある。

入れ子の順番が __重要でない処理__ を行う場合は、__どの順番で for 文を記述してもかまわない__。
:::

### ゲーム盤の表示に `board` に対する繰り返し処理が使えない理由

今回の記事の前半で、__2 次元配列を表す list が代入__ された __`board` に対して for 文を記述__ する繰り返し処理について紹介しましたが、この方法で〇×ゲームのゲーム盤を __表示することはできません__。その理由は、`board` の __1 つめのインデックス__ が __参照する要素__ は、__各列__ のデータを表す要素だからです。下記のように記述すると、__外側の for 文__ は __列に対する繰り返し__ になるので、先ほどと同じ理由で、__行と列が入れ替わった状態__ でゲーム盤が __表示__ されてしまいます。

```python
board = initialize_board()
place_mark(board, 0, 1, "〇")
place_mark(board, 1, 2, "×")

# 繰り返しのたびに、board から列のデータを取り出して col に代入する
for col in board:
    # 繰り返しのたびに、col からマスのデータを取り出して cell に代入する
    for cell in col:
        # 取り出したマスのデータを改行せずに表示する
        print(cell, end="")
    # 列の表示が終わったので、改行する
    print()
``` 
実行結果
```
.〇.
..×
...
```

:::note
`board` の __1 つ目のインデックス__ がゲーム盤の __y 座標__ を表すようにすれば、__上記のプログラムで__ ゲーム盤を __正しく表示することが出来る__ ようになります。ただし、そのように変更すると、__数学の座標の記法と逆__ に、インデックスが前から順に y 座標、x 座標を表すという __欠点__ が生じます。
:::

### 2 種類の for 文の違いに関する補足

今回の記事で説明したように、__2 次元配列を表す list__ の __全ての要素__ に対して処理を行う __繰り返し処理__ は、__`range` を使う方法__ と、__2 次元配列を表す list そのものを使う方法__ があります。

__`range` を使う方法__ は、2 次元配列を表す list の __2 つのインデックス__ の __すべての組み合わせ__ に対して繰り返しの処理を行うという方法です。この方法には以下のような利点と欠点があります。

* __利点__
    * 繰り返しの処理の中で、__インデックスの番号が必要になる__ ような処理を記述することが出来る
    * 2 つの for 文が __どちらのインデックス__ に対して繰り返しを行うかを __選択する__ ことが出来る
* __欠点__
    * list の要素に対して、__2 つのインデックスを記述__ する必要があるのでプログラムが __少し長くなる__
    * __`range` の引数__ に __繰り返す数__ を表す __正しい数値__ を記述する必要がある

__2 次元配列を表す list__ そのものを使って行う繰り返し処理は、以下のような利点と欠点があります。

* __利点__
    * 繰り返しの処理の中で、__インデックスを使わず__ に list の要素に対する処理を __短く__ 記述することが出来る
    * __何回繰り返すかを表す数値__ を記述する __必要がない__
* __欠点__
    * 繰り返しの処理の中で、__インデックスの番号が必要になる__ ような処理を __記述することが出来ない__
    * 2 つの for 文が __どちらのインデックス__ に対して繰り返しを行うかを __選択することができない__

:::note warn
それぞれの利点と欠点を __比べてみる__ とわかるように、それぞれの __利点と欠点__ は __お互いに対応__ しています。そのため、どちらの方法を使うかは、これらの __利点と欠点を考慮__ して、記述するプログラムで __行う処理__ に合わせて __自分で選択する__ 必要があります。
:::

以下に、それぞれの利点について、__ゲーム盤の表示を行うプログラムで比較__ しながら説明します。

#### インデックスの番号が必要になる処理

__`range` を使った繰り返し処理__ では、繰り返しの中で __インデックスの番号__ が __`x` と `y` に代入__ されるので、それを使って下記のプログラムのように、繰り返しの中で __マスの座標を表示__ することが出来ます。

```python
board = initialize_board()

# 各行に対する繰り返しの処理を行う
for y in range(3):
    # y 行の各マスに対する繰り返しの処理を行う
    for x in range(3):
        # (x, y) のマスの要素を改行せずに表示する
        print("(", x, ",", y, ")", board[x][y])
```
実行結果
```
( 0 , 0 ) .
( 1 , 0 ) .
( 2 , 0 ) .
( 0 , 1 ) .
( 1 , 1 ) .
( 2 , 1 ) .
( 0 , 2 ) .
( 1 , 2 ) .
( 2 , 2 ) .
```

一方、__2 次元配列を表す list__ そのものを使って行う繰り返し処理では、繰り返しの中で `col` と `cell` に代入されるのは、__list の要素__ なので、繰り返しの中でインデックスの番号を利用する、上記のようなプログラムを __記述することはできません__。

:::note
細かい話になりますが、実際には、本記事でまだ紹介していない組み込み関数 __`enumerate`__ を使えば、__2 次元配列を表す list__ そのものを使って行う繰り返し処理の中で、インデックスの番号を利用することが出来ます。`enumerate` に関しては、必要になった時点で紹介します。
:::

#### インデックスに対応する繰り返し処理の順番の選択

__`range` を使った繰り返し処理__ の場合は、__入れ子__ になった __それぞれの for 文__ が、__どちらのインデックスに対応__ する処理を行うかを __選択__ することが出来ます。

具体的には、__2 次元配列の list__ が代入された `board` の __全ての要素__ に対して処理を行うプログラムは、以下の __2 通りの方法で記述__ することが出来ます。

ゲーム盤の表示のように、for 文の __入れ子の順番が重要__ になる場合は、__正しい順番で記述する__ 必要があります。

```python
for x in range(3):
    for y in range(3):
        (x, y) の要素に対する処理
```

```python
for y in range(3):
    for x in range(3):
        (x, y) の要素に対する処理
```

一方、__2 次元配列を表す list__ そのものを使って行う繰り返し処理では、__入れ子になった for 文の順番__ は、__必ず__ list の __インデックスの順番に対応__ することになります。

`board` の場合は、list の 2 つのインデックスは __順に「x 座標」__、__「y 座標」__ を表すことに __決めた__ ので、入れ子になった for 文は __外側から順に__、__「x 座標」に対する__ 繰り返し、__「y 座標」に対する__ 繰り返し __しか行うことが出来ません__。

従って、__その逆の順番__ で for 文を記述する必要があるゲーム盤の表示処理を、__2 次元配列を表す list__ そのものを使って行う繰り返し処理で __記述することはできません__。

#### 繰り返しの中でのインデックスの必要性

__2 次元配列を表す list__ そのものを使って行う繰り返し処理では、下記のプログラムのように、繰り返しの中で __インデックスを記述する必要__ が __ない__ ので、プログラムを __短く記述__ できます。

```python
for col in board:
    for cell in col:
        print(cell)
```

一方、__`range`__ を使って行う繰り返し処理では、下記のプログラムのように、繰り返しの中で __2 つのインデックスを記述する必要__ が __ある__ ので、プログラムが __長く__ なります。

```python
for y in range(3):
    for x in range(3):
        print(board[x][y])
```

#### 何回繰り返すかを表す数値の記述

__2 次元配列を表す list__ そのものを使って行う繰り返し処理では、__list の要素の数__ だけ繰り返しが行われるので、下記のプログラムのように、__何回繰り返すかを表す数値__ を記述する必要は __ありません__。

```python
for col in board:
    for cell in col:
        print(cell)
```

一方、`range` を使って行う繰り返し処理では、下記のプログラムのように、__繰り返しの数__ を表す `3` という数値を記述する __必要があり__、この数値を間違えるとプログラムが正しく動作しなくなります。

```python
for y in range(3):
    for x in range(3):
        print(board[x][y])
```

:::note
list の __要素の数__ を返り値として返す __`len`__ という組み込み関数を使うことで、下記のプログラムのように、`range` を使って行う繰り返し処理でも、__繰り返しの数を記述しなくても済む__ ようになります。

下記のプログラムでは、2 行目の外側の for 文では __行に対する繰り返し__ を行います。行の数は、0 列 のデータを表す `board[0]` の要素の数に等しいので、`range(len(board[0]))` のように記述しています。なお、〇×ゲームのゲーム盤の __行の数__ は __どの列でも同じ__ なので、この部分は、`range(len(board[1]))` や、`range(len(board[2]))` のように記述しても構いません。

```python
# ゲーム盤の行の数は、board[0] の要素の数に等しい
for y in range(len(board[0])):
    # ゲーム盤の x 列のマスの数は、board の要素の数に等しい
    for x in range(len(board)):
        print(board[x][y])
```

この方法には、「`len` を記述する分だけ __プログラムが長くなる__」、「`len` の実引数を __正しく記述する__ 必要がある」という欠点があります。
:::

## 文字のずれを解消する方法

__上下の行で文字がずれる__ 原因は、__半角__ の `.` と __全角__ の `〇` と `×` を __混在して__ 表示しているからです。一般的に __全角文字__ は、__半角文字__ よりも __幅が広く表示__ されるので上下で表示がずれてしまいます。

また、__文字の種類__ によって __横幅が異なるフォント__[^5]で文字が表示される場合は、__全ての文字列を全角文字で表示__ した場合でも、__上下の行で文字がずれる__ ことが良くあります[^6]。

下記のプログラムは、ゲーム盤の __0 行に 〇__ を、__1 行に ×__ を並べて配置したものを表示するプログラムです。実行結果から、全角文字の 〇 と × の __表示幅が異なる__ ため __上下の行でずれる__ ことがわかります。

```python
board = initialize_board()
place_mark(board, 0, 0, "〇")
place_mark(board, 1, 0, "〇")
place_mark(board, 2, 0, "〇")
place_mark(board, 0, 1, "×")
place_mark(board, 1, 1, "×")
place_mark(board, 2, 1, "×")
display_board(board)
```
実行結果
```
〇〇〇
×××
...
```

Word などの __ワープロ__ では、文章をきれいに表示するために、一般的に __半角文字__ は文字によって __横幅が異なるフォントで表示__ されます。一方、__プログラムを扱うソフト__ で __半角文字__ を __記述__ したり __表示__ したりする場合は、一般的に __すべての文字が同じ幅__ で表示される __フォント__[^7]が使われます。

実際に、本記事のプログラムを表示する背景が黒い部分でも、VSCode でプログラムを記述したり、`print` で文字を表示したりした場合でも、__半角文字__ が上下の行で __ずれて表示される__ ことは __ありません__。

そのため、__CUI__ のような、__文字だけで出力__ を行う際に、__上下の行で文字の位置を揃えたい__ 場合は、__すべての文字を半角文字で表示__ するのが一般的です。

そこで、以降は __〇 を表す文字__ を __半角の小文字の o（オー）__ に、__× を表す文字__ を __半角の小文字の x（エックス）__ に変更することにします。

下記は、そのように変更した場合に、ゲーム盤の 0 行に 〇 を、1 行に × を並べて配置したものを表示するプログラムです。実行結果から、__上下の文字がずれなくなった__ ことを確認することが出来ます。


```python
board = initialize_board()
place_mark(board, 0, 0, "o")
place_mark(board, 1, 0, "o")
place_mark(board, 2, 0, "o")
place_mark(board, 0, 1, "x")
place_mark(board, 1, 1, "x")
place_mark(board, 2, 1, "x")
display_board(board)
```
実行結果
```
ooo
xxx
...
```

:::note warn
CUI で __上下の行で文字を揃えて表示__ したい場合は、__全ての文字を半角文字__ で表示すると良い。
:::

# 今回の記事でのゲーム盤のデータ構造の変更点

:::note warn
今回の記事で、ゲーム盤の __データ構造__ を以下のように変更しました

* __空白のマス__ を表す文字を半角の空白から、__半角の `.`__（ピリオド）に変更した
* __〇 のマス__ を表す文字を全角の 〇 から、__半角の小文字の `o`__（オー）に変更した
* __× のマス__ を表す文字を全角の × から、__半角の小文字の `x`__（エックス）に変更した

変更後の __〇×ゲームのデータ構造__ は以下のようになります。なお、変更した部分は __6 番目の文字列型のデータの部分__ のみです。

* 〇×ゲームのゲーム盤のマスの座標を、左右方向を x 座標、上下方向を y 座標とする __2 次元の座標__ で表現する
* x 座標はゲーム盤の左端の列を 0 とし、右に 1 つ列がずれるたびに x 座標が 1 増える
* y 座標はゲーム盤の上端の行を 0 とし、下に 1 つ行がずれるたびに y 座標が 1 増える
* ゲーム盤の各マスを表すデータを __2 次元配列__ を表す __list__ で表現する
* 2 次元配列の list の 1 つ目のインデックスを x 座標、2 つ目のインデックスを y 座標に対応させる
* 空白、〇、× が配置されたマスを表すデータを、それぞれ半角の __`" "`__、__`"o"`__（小文字のオー）、__`"x"`__（小文字のエックス） という __文字列型のデータ__ で表現する
* ゲーム盤の各マスを表す list の要素に、そのマスに配置されたマークに対応する文字列を代入する
::: 

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

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

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

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

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

# 次回の記事

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

# 更新履歴

| 更新日時 | 更新内容 |
|:--|:--|
| 2023/10/14 | デフォルト引数に関する公式の Python のドキュメントへのリンクを追加しました |

[^1]:列を表す column の略です
[^2]:default は、既定（あらかじめ決まっているという意味）を表す英単語です
[^3]:疑似的な複製については、「__[その 11](https://qiita.com/ysgeso/items/cc9e67c16b0814c8bc2e#%E7%96%91%E4%BC%BC%E7%9A%84%E3%81%AA%E8%A4%87%E8%A3%BD)__」の記事を参照して下さい
[^4]:`print("")` のように記述しても構いません
[^5]:文字の種類によって __表示幅が異なる__ フォントの事を __プロポーショナルフォント__ と呼びます
[^6]:表示される文字のフォントは、設定によって変えることが出来まるので、必ずずれるとは限りません
[^7]:どの文字も __同じ幅で表示__ するフォントの事を __等幅フォント__ と呼びます