Pythonで〇×ゲームのAIを一から作成する　その48　デバッグ表示と、可変長引数と実引数の展開

# 目次と前回の記事

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

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

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

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

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

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

## これまでに作成した AI

これまでに作成した AI の アルゴリズム は以下の通りです。

| 関数名 | アルゴリズム |
|:-:|:--|
| __[`ai1`](https://qiita.com/ysgeso/items/bdbcd4c1ab1d8731df58#ai1-%E3%81%AE%E5%AE%9A%E7%BE%A9)__  | __左上から順__ に __空いているマス__ を探し、__最初に見つかったマス__ に __着手__ する |
| __[`ai2`](https://qiita.com/ysgeso/items/bdbcd4c1ab1d8731df58#ai2-%E3%81%AE%E5%AE%9A%E7%BE%A9)__<br>__[`ai2s`](https://qiita.com/ysgeso/items/8f3d4b3d2c02ef51a70e#%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E9%96%A2%E6%95%B0%E3%82%92%E5%88%A9%E7%94%A8%E3%81%97%E3%81%9F%E3%83%AB%E3%83%BC%E3%83%AB-2-%E3%81%A7%E7%9D%80%E6%89%8B%E3%82%92%E8%A1%8C%E3%81%86-ai-%E3%81%AE%E5%AE%9A%E7%BE%A9)__  | __ランダム__ なマスに __着手__ する |
| __[`ai3`](https://qiita.com/ysgeso/items/1e8732db6cd1b0104276#ai3-%E3%81%AE%E5%AE%9F%E8%A3%85)__<br>__[`ai3s`](https://qiita.com/ysgeso/items/8f3d4b3d2c02ef51a70e#%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E9%96%A2%E6%95%B0)__  | __真ん中__ のマスに __優先的__ に __着手__ する<br>既に __埋まっていた場合__ は __ランダム__ なマスに __着手__ する | |
| __[`ai4`](https://qiita.com/ysgeso/items/1e8732db6cd1b0104276#%E3%83%AB%E3%83%BC%E3%83%AB-4%E5%9B%9B%E9%9A%85%E3%81%AE%E3%83%9E%E3%82%B9%E3%81%AB%E5%84%AA%E5%85%88%E3%81%97%E3%81%A6%E7%9D%80%E6%89%8B%E3%81%99%E3%82%8B)__  | __真ん中__、__隅__ のマスの __順__ で __優先的__ に __着手__ する<br>既に __埋まっていた場合__ は __ランダム__ なマスに __着手__ する | あり |
| __[`ai5`](https://qiita.com/ysgeso/items/ff28a95dff50322e6b16#%E3%83%AB%E3%83%BC%E3%83%AB%E3%81%9D%E3%81%AE-5%E5%8B%9D%E3%81%A6%E3%82%8B%E5%A0%B4%E5%90%88%E3%81%AB%E5%8B%9D%E3%81%A4)__  | __勝てる場合__ に __勝つ__<br>そうでない場合は __ランダム__ なマスに __着手__ する | |
| __[`ai6`](https://qiita.com/ysgeso/items/e08c41f93109496fb9f9#%E3%83%AB%E3%83%BC%E3%83%AB%E3%81%9D%E3%81%AE-6%E7%9B%B8%E6%89%8B%E3%81%AE%E5%8B%9D%E5%88%A9%E3%82%92%E9%98%BB%E6%AD%A2%E3%81%99%E3%82%8B)__  | __勝てる場合__ に __勝つ__<br>そうでない場合は __相手の勝利__ を __阻止__ する<br>そうでない場合は __ランダム__ なマスに __着手__ する |
| __[`ai7`](https://qiita.com/ysgeso/items/e08c41f93109496fb9f9#%E3%83%AB%E3%83%BC%E3%83%AB%E3%81%9D%E3%81%AE-7%E3%83%AB%E3%83%BC%E3%83%AB3-%E3%81%A8-%E3%83%AB%E3%83%BC%E3%83%AB-6-%E3%81%AE%E4%BD%B5%E7%94%A8)__  | __真ん中__ のマスに __優先的__ に __着手__ する<br>そうでない場合は __勝てる場合__ に __勝つ__<br>そうでない場合は __相手の勝利__ を __阻止__ する<br>そうでない場合は __ランダム__ なマスに __着手__ する |

基準となる __`ai2`__ との __対戦結果__（単位は %）は以下の通りです。__太字__ は __`ai2 VS ai2` よりも__ __成績が良い__ 数値を表します。__欠陥__ の列は、__アルゴリズム__ に __欠陥__ があるため、__`ai2`__ との __対戦成績__ が __良くても強い__ とは __限らない__ ことを表します。欠陥の詳細については、関数名のリンク先の説明を見て下さい。

| 関数名 | o 勝 | o 負 | o 分 | x 勝 | x 負 | x 分 | 勝 | 負 | 分 | 欠陥 |
|:-:|--:|--:|--:|--:|--:|--:|--:|--:|--:|:-:|
| __[`ai1`](https://qiita.com/ysgeso/items/bdbcd4c1ab1d8731df58#ai1-%E3%81%AE%E5%AE%9A%E7%BE%A9)__  | __78.1__ | __17.5__ | 4.4 | __44.7__ | __51.6__ | 3.8 | __61.4__ | __34.5__ | 4.1 | あり |
| __[`ai2`](https://qiita.com/ysgeso/items/bdbcd4c1ab1d8731df58#ai2-%E3%81%AE%E5%AE%9A%E7%BE%A9)__  | 58.7 | 28.8 | 12.6 | 29.1 | 58.6 | 12.3 | 43.9 | 43.7 | 12.5 | |
| __[`ai3`](https://qiita.com/ysgeso/items/1e8732db6cd1b0104276#ai3-%E3%81%AE%E5%AE%9F%E8%A3%85)__  | __69.3__ | __19.2__ | 11.5 | __38.9__ | __47.6__ | 13.5 | __54.1__ | __33.4__ | 12.5 | |
| __[`ai4`](https://qiita.com/ysgeso/items/1e8732db6cd1b0104276#%E3%83%AB%E3%83%BC%E3%83%AB-4%E5%9B%9B%E9%9A%85%E3%81%AE%E3%83%9E%E3%82%B9%E3%81%AB%E5%84%AA%E5%85%88%E3%81%97%E3%81%A6%E7%9D%80%E6%89%8B%E3%81%99%E3%82%8B)__  | __83.0__ | __9.5__ | 7.4 | __57.2__ | __33.0__ | 9.7 | __70.1__ | __21.3__ | 8.6 | あり |
| __[`ai5`](https://qiita.com/ysgeso/items/ff28a95dff50322e6b16#%E3%83%AB%E3%83%BC%E3%83%AB%E3%81%9D%E3%81%AE-5%E5%8B%9D%E3%81%A6%E3%82%8B%E5%A0%B4%E5%90%88%E3%81%AB%E5%8B%9D%E3%81%A4)__  | __81.2__ | __12.3__ | 6.5 | __51.8__ | __39.8__ | 8.4 | __66.5__ | __26.0__ | 7.4 | |
| __[`ai6`](https://qiita.com/ysgeso/items/e08c41f93109496fb9f9#%E3%83%AB%E3%83%BC%E3%83%AB%E3%81%9D%E3%81%AE-6%E7%9B%B8%E6%89%8B%E3%81%AE%E5%8B%9D%E5%88%A9%E3%82%92%E9%98%BB%E6%AD%A2%E3%81%99%E3%82%8B)__  | __88.9__ | __2.2__ | 8.9 | __70.3__ | __6.2__ | 23.5 | __79.6__ | __4.2__ | 16.2 |
| __[`ai7`](https://qiita.com/ysgeso/items/e08c41f93109496fb9f9#%E3%83%AB%E3%83%BC%E3%83%AB%E3%81%9D%E3%81%AE-7%E3%83%AB%E3%83%BC%E3%83%AB3-%E3%81%A8-%E3%83%AB%E3%83%BC%E3%83%AB-6-%E3%81%AE%E4%BD%B5%E7%94%A8)__  | __95.8__ | __0.2__ | 4.0 | __82.3__ | __2.4__ | 15.3 | __89.0__ | __1.3__ | 9.7 |

# デバッグ表示の実装

評価値を利用したアルゴリズムに限った話ではありませんが、__実装__ した __プログラム__ が __意図通り__ に __動作しない__ ことは __良くある事__ です。ある程度以上の規模の __プログラム__ を、__バグ__ を一度も __発生させず__ に __完成する__ のは、__ほぼ不可能__ であると言っても良いでしょう。そのような場合に、__バグの修正__ に __役立つ__ のが、__処理の経過__ を __表示__ する __[デバッグ表示](https://qiita.com/ysgeso/items/ff28a95dff50322e6b16#%E3%83%90%E3%82%B0%E3%81%AE%E5%8E%9F%E5%9B%A0%E3%81%AE%E8%AA%BF%E6%9F%BB)__ です。

そこで、今後のことを考えて、評価値を利用したアルゴリズムのひな形となる __`ai_by_score`__ を、__デバッグ表示__ の __機能__ を持つように、プログラムを __改良__ することにします。

__デバッグ表示__ の機能の __実装__ は、[以前の記事](https://qiita.com/ysgeso/items/24a604d82249f61e199d#test_judge-%E3%81%AE%E6%94%B9%E8%89%AF-%E3%81%9D%E3%81%AE-1%E3%82%B2%E3%83%BC%E3%83%A0%E7%9B%A4%E3%81%AE%E8%A1%A8%E7%A4%BA%E3%81%AE%E6%9C%89%E7%84%A1%E3%81%AE%E9%81%B8%E6%8A%9E) で実装した __`test_judge`__ と __同じ方法__ で、下記のプログラムのように行うことができます。

* __仮引数 `debug`__ によって __デバッグ表示__ を行うかどうかを __選択できる__ ようにする
* __if 文__ を使って、__`debug`__ の値が __`True` の場合__ に、__デバッグ表示__ を行う

```python
def test_judge(testcases, debug=False):
略
    if debug:
        print(mb)
略
```

## デバッグ表示を行う関数の定義

上記のプログラムでは、__`if debug:`__ によって __デバッグ表示__ を行うかどうかを __判定__ する __行__ と、__`print`__ を使って __デバッグ表示__ を行う __行__ を __記述__ していますが、毎回 __`if debug:`__ を記述するのは __面倒__ です。そこで、この __2 行の処理__ を行う __関数を定義__ することにします。

その __関数__ の __処理__、__名前__、__入力__、__出力__ は、以下のように決めることにします。

__処理__：__実引数__ に記述した __値によって__、__デバッグ表示__ を行うかどうかを __判断__ して __表示__ する
__関数の名前__：__デバッグ__（debug）の __表示__（print）を行うので、__debug__ の __頭文字__ をとって __`dprint`__[^1] という __名前__ にする
__入力__：__デバッグ表示__ を __行うか__ どうかを __表す値__ を __仮引数 `debug`__ に、__`print`__ で __デバッグ表示__ を行う __データ__ を __仮引数 `data`__ に __代入__ する
__出力__：__画面への表示__ という形での __出力を行う__ が、__返り値__ という形の __出力は行わない__

具体的には、下記のような関数を定義します。

* __1 行目__：__デバッグ表示__ を __行うか__ どうかを __表すデータ__ を __仮引数 `debug`__ に、__デバッグ表示__ で __表示するデータ__ を __仮引数 `data`__ に __代入__ するように __`dprint`__ を __定義__ する
* __2、3 行目__：__`debug`__ が __`True` の場合__ に、__`print`__ で __`data` を表示__ する

```python
def dprint(debug, data):
    if debug:
        print(data)
```

下記のプログラムは、__`dprint`__ の __利用例__ です。__1 行目__ では、__`dprint`__ の __1 つ目__ の __実引数__ に __`True` を記述__ しているので、__2 つ目__ の __実引数の値__ である __debug = True__ が __表示__ されますが、__2 行目__ では、__1 つ目__ の __実引数__ に __`False` を記述__ しているので __何も表示されません__。

```python
dprint(True, "debug = True")
dprint(False, "debug = False")
```

実行結果

```
debug = True
```

## 定義した関数の問題点

先程定義した __`dprint`__ にはいくつかの __問題点__ があります。その一つは、下記のプログラムのように、__複数のデータ__ を __`dprint`__ で __表示__ しようとした場合に __エラーが発生__ する点です。

```python
dprint(True, 1, 2)
```

実行結果

```
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[3], line 1
----> 1 dprint(True, 1, 2)

TypeError: dprint() takes 2 positional arguments but 3 were given
```

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

* __TypeError__

データ型（Type）に関するエラー

* __dprint() takes 2 positional arguments but 3 were given__

dprint は 2 つの位置引数（positional arguments）を受け取る（takes）が、3 つの位置引数が与えられている（were given）
:::

エラーの __原因__ は、__`dprint`__ の __仮引数__ が __`debug`__ と __`data`__ の __2 つしかない__ にもかかわらず、`True`、`1`、`2` の __3 つ__ の __実引数__ を __記述__ して __`dprint`__ を __呼び出した__ 点にあります。

この __問題__ は、__`dprint` の中__ で利用している組み込み関数 __`print`__ が、__任意の数__ の __実引数__ を __記述できる__ のに対して、__`dprint`__ には __2__ つの __実引数__ を __記述する必要__ がある点にあります。

この __問題を解決__ するためには、__`dprint`__ に対して、__任意の数__ の __実引数__ を __記述__ して呼び出すことが __できるようにする__ 必要がありますが、__これまで__ に本記事で紹介してきた __方法__ では、__任意の数__ の __実引数__ を記述するような __関数を定義__ する事は __できません__。そこで、今回の記事では、__任意の数__ の __実引数__ を __記述できる__ ような __関数を定義__ する __方法__ を紹介します。

# 可変長引数と実引数の展開

__任意の数__ の __実引数__ を __記述__ して呼び出すことができる __関数__ の __仮引数__ のことを、__可変長引数__ と呼びます。__可変長引数__ には、__[位置引数](https://qiita.com/ysgeso/items/c5dbe62b329f7936181a#%E4%BD%8D%E7%BD%AE%E5%BC%95%E6%95%B0%E3%81%A8%E3%82%AD%E3%83%BC%E3%83%AF%E3%83%BC%E3%83%89%E5%BC%95%E6%95%B0)__ に __対する__ ものと、__[キーワード引数](https://qiita.com/ysgeso/items/c5dbe62b329f7936181a#%E4%BD%8D%E7%BD%AE%E5%BC%95%E6%95%B0%E3%81%A8%E3%82%AD%E3%83%BC%E3%83%AF%E3%83%BC%E3%83%89%E5%BC%95%E6%95%B0)__ に __対する__ ものの __2 種類__ があります。

## 位置引数に対する可変長引数

まず、__位置引数__ に対する __可変長引数__ について説明します。__位置引数__ に対する __可変長引数__ は、下記のプログラムのように、__仮引数の名前__ の __先頭__ に __半角の `*`__ を __記述__ します。

```python
def a(*args):
    print(args)
```

__Python__ では、__位置引数__ に対する __可変長引数__ の __名前__ を __`args`__（__実引数__ を表す __argument__ の __複数形__ の略）にする __慣習__[^2] があるので、上記のプログラムでも __名前__ を __`args`__ としました。 

なお、初心者が __勘違いしやすい点__ として、__先頭__ の __`*`__ を __含んだ `*args`__ が __名前__ であるというものがあります。__先頭__ の __`*`__ は、その __仮引数__ が __位置引数__ に対する __可変長引数__ であることを __示すための記号__ なので、上記の __可変長引数__ の __名前__ は __`args`__ である点に __注意__ して下さい。

なお、「__位置引数に対する可変長引数__」という表記は長いので、以後は、__位置引数に対する可変長引数__ として __一般的に記述__ される「__`*args`__」と記述することにします。

__仮引数__ に __`*args`__ が __記述__ されている __関数__ に対して、__任意の数__ の __位置引数__ を __記述__ して呼び出すと、下記のプログラムのように、__記述__ した __位置引数__ の __値__ を、__記述した順番__ で __要素__ とする __tuple__ が、__`args` に代入__[^3] されます。__1 行目__ や __3 行目__ のように、__実引数の数__ が __1__ や __0__ の __場合__ でも、__必ず tuple__  が __代入__ される点に __注意__ して下さい。

```python
a(1)       # 実引数の数が 1 つしかない場合は、1 ではなく、要素を 1 つ持つ tuple が代入される
a(1, 2, 3)
a()        # 実引数が記述されていない場合は、None ではなく、空の tuple が代入される
```

実行結果
```
(1,)       
(1, 2, 3)
()         
```

## 引数に関する用語と分類の整理

この後の説明を行う前に、__引数__ に関する一部の __用語__ は、__仮引数__ を表す用語であるか、__実引数__ を表す用語であるかが __分かりづらい__ ので、__表__ にまとめて __整理__ することにします。

なお、__通常の仮引数__ という __用語__ はおそらく __存在しない__ と思いますが、本記事では、[デフォルト引数](https://qiita.com/ysgeso/items/c5dbe62b329f7936181a#%E3%83%87%E3%83%95%E3%82%A9%E3%83%AB%E3%83%88%E5%BC%95%E6%95%B0%E3%81%A8%E3%83%87%E3%83%95%E3%82%A9%E3%83%AB%E3%83%88%E5%BC%95%E6%95%B0%E5%80%A4)、可変長引数 __以外__ の __仮引数__ を、__区別__ して「__通常の仮引数__」と __表記__ することにします。まだ __説明していない__ 表の __項目__ については、__後で詳しく説明__ します。

| 用語 | 分類 | 記述例 | 
|:--|:--|:--|
| __位置引数__  | 実引数  | `a(1)` |
| __キーワード引数__  | 実引数  | `a(x=1)` |
| __反復可能オブジェクトの展開__  | 実引数  | `a(*x)` |
| __マッピング型の展開__  | 実引数  | `a(**x)` |
| __通常の仮引数__ | 仮引数 | `def a(x):` |
| __デフォルト引数__  | 仮引数  | `def a(x=1):` |
| __位置引数に対する可変長引数__  | 仮引数  | `def a(*args):` |
| __キーワード引数に対する可変長引数__  | 仮引数  | `def a(**kwargs):` |

関数の仮引数と、実引数に関する詳細は、下記のリンク先を参照して下さい。

https://docs.python.org/ja/3/reference/compound_stmts.html#function

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

## `*args` と他の仮引数の併用

__`*args`__ は、__通常の仮引数__ や __デフォルト引数__ と __併用__ することができます。その場合は、__`*args`__ は、__すべて__ の __通常の仮引数__ より __後__ に __記述__ する __必要__ があります。なお、__`*args`__ と __デフォルト引数__ の記述の順番については、__後で説明__ します。

__`*args`__ と、__通常の仮引数__ や __デフォルト引数__ を __併用__ した場合は、__位置引数__ として __記述__ された __実引数__ は、以下の仮引数に代入されます。

* __対応__ する __通常の仮引数__ や __デフォルト引数__ が __存在すれば__、__その仮引数__ に代入される
* それらが __存在しなければ__、__位置引数__ の __値__ を __記述__ された __順番__ で __要素__ とする __tuple__ が、__`args` に代入__ される

別の言葉で説明すると、「__余った位置引数__ の __値__ を __要素__ として持つ __tuple__ が __`args` に代入__ される」という処理が行われます。

従って、__`dprint`__ を、下記のプログラムのように修正することで、__2 つ目以降__ の __実引数__ に、__任意の数__ の __位置引数を記述__ することができるようになります。なお、__元__ の __`dprint`__ では、__2 つ目__ の __仮引数__ の __名前__ を __`data`__ としていましたが、Python の __慣習__ に従って、__2 つ目__ の位置引数に対応する可変長引数の __名前__ を __`args`__ に __修正__ しました。

```python
def dprint(debug, *args):
    if debug:
        print(args)
```

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

```diff_python
-def dprint(debug, data):
+def dprint(debug, *args):
    if debug:
-       print(data)
+       print(args)
```
</details>


下記のプログラムを実行すると、__1 つ目__ の __実引数__ の値が、__`dprint`__ の __仮引数 `debug`__ に __代入__ され、__2 つ目以降__ の __位置引数__ の __値__ が、__tuple に変換__ されて __`args`__ に __代入__ されます。

従って、__1 ~ 3 行目__ の __実行結果__ は、__先程のプログラム__ の実行結果と __同じ__ になります。また、__4 行目__ を __実行__ しても何も __表示__ は __行われません__。

```python
dprint(True, 1)
dprint(True, 1, 2, 3)
dprint(True)
dprint(False, 1, 2, 3)
```

実行結果

```
(1,)
(1, 2, 3)
()
```

## 反復可能オブジェクトの展開

先程の __`dprint`__ には __問題__ があります。__`dprint`__ は、__1 つ目__ の __実引数__ が __`True`__ の場合に、__2 つ目以降__ の __実引数__ を使って __`print`__ で __表示を行う__ 関数として __定義したつもり__ ですが、__実際__ には、下記のプログラムのように、__`dprint`__ で __表示__ される内容と、__`print`__ で __表示__ される内容は __異なります__。なお、__6 行目__ では、__空行__（何も表示されない行）が __表示__ されています。

```python
dprint(True, 1)
print(1)
dprint(True, 1, 2, 3)
print(1, 2, 3)
dprint(True)
print()
```

実行結果
```
(1,)
1
(1, 2, 3)
1 2 3
()

```

この問題は、__反復可能オブジェクト__ の __各要素__ を、__順番__ に __位置引数__ として __記述__ したと __みなして__、__関数呼び出しを行う__ という、__反復可能オブジェクトの展開__ という __記述__ を行うことで __解決__ できます。__反復可能オブジェクトの展開__ は、__実引数__ を __記述__ する際に、__反復可能オブジェクト__ の __先頭__ に __半角の `*`__ を記述します。

わかりづらいと思いますので、具体例を示します。下記のプログラムは、__1 行目__ で __`data`__ に、__3 つの要素__ を持つ、__反復可能オブジェクト__ である __tuple__ を __代入__ しています。

```python
data = (1, 2, 3)
print(data)
print(*data)
print(1, 2, 3)
```

実行結果

```
(1, 2, 3)
1 2 3
1 2 3
```

上記のプログラムの 2 ~ 4 行目では下記の処理が行われます。

* __2 行目__：__`data` そのもの__ を __`print`__ で表示しているので、__`data` に代入__ された、__(1, 2, 3)__ という __tuple が表示__ される
* __3 行目__：`data` の前に `*` が記述されているので、__反復可能オブジェクトの展開__ が行われる。その結果、__`data` の要素__ が __順番__ に __取り出され__、__`*data`__ の __部分__ に __それぞれ__ の __要素の値__ が __位置引数__ として記述された __`print(1, 2, 3)`__ が __実行__ される。その結果、__3 行目__ と、__4 行目__ では、__同じ表示__ が行われる

なお、__反復可能オブジェクトの展開__ は、__tuple__ だけでなく、下記のプログラムのように、__list__ や __dict__ に対しても行うことができます。なお、__dict__ の場合は、__キー__ が取り出されて __展開__ されます。__dict__ の __キー__ と __値__ を __展開__ する方法については __後述__ します。

```python
data1 = [1, 2, 3]
data2 = { "a": 1, "b": 2 }
print(*data1)
print(*data2)
```

実行結果

```
1 2 3
a b
```

## 反復可能オブジェクトの展開を利用した `dprint` の修正

先程の __`dprint`__ の問題は、下記のプログラムの __3 行目__ のように、__反復可能オブジェクトの展開__ を `print` の実引数に記述することで修正できます。

```python
def dprint(debug, *args):
    if debug:
        print(*args)
```

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

```diff_python
def dprint(debug, *args):
    if debug:
-       print(args)
+       print(*args)
```
</details>

__反復可能オブジェクトの展開__ を __利用__ することで、下記のプログラムのように、__`dprint`__ と __`print`__ で __同じ表示__ が行われるようになります。なお、5、6 行目は、__空行が表示__ されます。

```python
dprint(True, 1)
print(1)
dprint(True, 1, 2, 3)
print(1, 2, 3)
dprint(True)
print()
```

実行結果
```
1
1
1 2 3
1 2 3


```

## `*args` と反復可能オブジェクトの展開の関係

__`*args`__ と、__反復可能オブジェクトの展開__ は、どちらも __先頭__ に __同じ `*`__ という __記号__ を __記述__ するので __紛らわしい__ ですが、その __意味__ は __異なる__ 点に __注意__ して下さい。

__見た目__ は __紛らわしい__ ですが、__記述する場所__ などが下記のように __異なる__ ので、__見分ける__ ことは __難しくない__ と思います。

* __`*args`__ は __関数の定義__ の __仮引数__ の部分に __記述__ される。__名前__ は __一般的__ に __`args`__ である
* __反復可能オブジェクトの展開__ は、__関数呼び出し__ の __実引数__ の部分に __記述__ される。__名前__ は特に __決まっておらず__、下記のプログラムのように、__変数に代入されていない__ 反復可能オブジェクトに __対して__ も __反復可能オブジェクトの展開__ を __記述__ することが __できる__

```python
print(*(1, 2, 3))
```

実行結果

```
1 2 3
```

__両者__ で __同じ記号__ が __使われる__ のは、おそらく __それぞれ__ で行われる __処理__ が、下記のように __対応している__ からではないかと思います。

* __`*args`__ の処理は、「__位置引数__ → __反復可能オブジェクト（tuple）__」
* __反復可能オブジェクトの展開__ の処理は、「__反復可能オブジェクト__ → __位置引数__」

## キーワード引数に対する可変長引数

先程で定義した、__`dprint`__ には、__まだ問題__ があります。

その理由は、__`*args`__ は、__キーワード引数__ に __対応していない__ からです。そのため、下記のプログラムのように、__仮引数__ に __`*args` のみ__ が __記述__ された関数に対して、__キーワード引数__ を __実引数__ に __記述__ して呼び出すと __エラーが発生__ します。

```python
def a(*args):
    print(args)

a(1, x=1)
```

実行結果

```
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[14], line 4
      1 def a(*args):
      2     print(args)
----> 4 a(1, x=1)

TypeError: a() got an unexpected keyword argument 'x'
```

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

* __TypeError__

データ型（Type）に関するエラー

* __a() got an unexpected keyword argument 'x'__

a は、予期しない（unexpected）x というキーワード引数（keyword argument）を得た（got
:::

そのため、__`dprint`__ で __改行を行わない__ 表示を行うために、下記のプログラムのように、__`print`__ と __同様__ に __キーワード引数__ に __`end=""`__ を __記述__ して呼び出すと __エラーが発生__ します。

```python
dprint(True, 1, 2, 3, end="")
```

実行結果
```
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[125], line 1
----> 1 dprint(True, 1, 2, 3, end="")

TypeError: dprint() got an unexpected keyword argument 'end'
```

__キーワード引数__ に対する __可変長引数__ は、__仮引数__ の __名前__ の __先頭__ に __半角の `**`__ を記述します。__キーワード引数__ に対する __可変長引数__ の __名前__ には、__`kwargs`__（keyword arguments の略）をつけるという __慣習__ があります。以後は、`*args` の場合と同様に、__キーワード引数__ に対する __可変長引数__ を __`**kwargs`__ のように __表記__ することにします。

__`**kwargs`__ は、対象が __位置引数__ であるか、__キーワード引数__ であるか、代入する値が __tuple__ であるか、__dict__ であるかの __違いを除けば__、__`*args`__ と __類似__ する __性質__ を持ちます。

具体的には、__`**kwargs`__ と、__通常の仮引数__ や __デフォルト引数__ を __併用__ した場合は、__キーワード引数__ として __記述__ された __実引数__ は、以下の __仮引数__ に代入されます。

* __対応__ する __通常の仮引数__ や __デフォルト引数__ が __存在すれば__、__その仮引数__ に代入される
* それらが __存在しなければ__、__キーワード引数__ の __キーワード__ と __値__ を、__キー__ と __その値__ とする __dict__ が、__`kwargs` に代入__ される

別の言葉で説明すると、「__余ったキーワード引数__ のデータを表す __dict__ が __`kwargs` に代入__ される」という処理が行われます。


下記は、__`*args`__ と __`**kwargs`__ の __違い__ を __表にまとめた__ ものです。

|  | `*args` | `**kwargs` |
|:--|:--|:--|
| __対応する実引数__  | 位置引数  |  キーワード引数 |
| __代入されるデータ型__  | tuple  | dict  |

下記のプログラムは、__仮引数__ に __`*args`__ と __`**kwargs`__ を記述した関数を __呼び出した__ 例です。

* __5 行目__：__位置引数__ である __`1`__、__`2`__、__`3`__ の __値__ が __tuple に変換__ されて __`args` に代入__ され、__キーワード引数__ である __`x=4`__ と __`y=5`__ が __dict に変換__ されて __`kwargs` に代入__ される
* __6 行目__：__キーワード引数__ を __記述せず__ に呼び出した場合は、__`kwargs`__ には、__空の dict__ が __代入__ される

```python
1  def a(*args, **kwargs):
2      print(args)
3      print(kwargs)
4
5  a(1, 2, 3, x=4, y=5)
6  a(6, 7, 8)
```

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

```python
def a(*args, **kwargs):
    print(args)
    print(kwargs)

a(1, 2, 3, x=4, y=5)
a(6, 7, 8)
```
</details>


実行結果

```
(1, 2, 3)
{'x': 4, 'y': 5}
(6, 7, 8)
{}
```

## マッピング型の展開

__`**kwargs`__ を使って、__`dprint`__ を下記のプログラムのように修正すれば良いと思うかもしれませんが、下記のプログラムは __意図通り__ の __処理__ を __行いません__。

* __1 行目__：仮引数に __`**kwargs`__ を追加した
* __3 行目__：`print` の実引数に __`kwargs`__ を追加した

```python
def dprint(debug, *args, **kwargs):
    if debug:
        print(*args, kwargs)
```        

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

```diff_python
-def dprint(debug, *args):
+def dprint(debug, *args, **kwargs):
    if debug:
-       print(*args)
+       print(*args, kwargs)
```
</details>

下記のプログラムは、__`dprint`__ を使って、__`"abc"`__ の __後__ で __改行せず__ に __`"def"`__ を __表示しようと__ していますが、__実行結果__ から、__それぞれの行__ で、__`"abc"`__ や __`"def"`__ の後に、__`kwargs`__ に代入された __dict が表示__ されてしまいます。

```python
dprint(True, "abc", end="")
dprint(True, "def")
```

実行結果
```
abc {'end': ''}
def {}
```

この問題は、[以前の記事](https://qiita.com/ysgeso/items/d457f37df8671e930603#%E5%AE%9F%E5%BC%95%E6%95%B0%E3%81%AB%E8%A8%98%E8%BF%B0%E3%81%97%E3%81%9F-dict-%E3%81%AE%E4%BB%AE%E5%BC%95%E6%95%B0%E3%81%B8%E3%81%AE%E5%B1%95%E9%96%8B)で説明した、__マッピング型の展開__ を利用することで __解決__ できます。以前の記事では __dict の展開__ と表記しましたが、__マッピング型の展開__ のほうが __正確__ なので、以後はそちらの表記を用います。なお、__マッピング型__ とは、__dict__ や __[defaultdict](https://qiita.com/ysgeso/items/5725504c97a4490fbadb#defaultdict-%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%9F%E9%9B%86%E8%A8%88)__ など、__キー__ と __キーの値__ によって __複数__ の __オブジェクト__ を __管理__ する __データ型__ のことです。

__マッピング型の展開__ とは、__マッピング型__ のデータの __キー__ と __キーの値__ を、__キーワード引数に変換__ したものと __みなして__、__関数呼び出しを行う__ という処理です。__マッピング型の展開__ は、__実引数__ を __記述__ する際に、__マッピング型__ のデータの __先頭__ に __半角の `**`__ を記述します。

__`dprint`__ で、__`**kwargs`__ に代入された __dict__ の __キー__ と __キーの値__ を、__キーワード引数__ として __`print`__ の __実引数__ で __利用する__ 場合は、下記のプログラムの 3 行目のように、__マッピング型の展開__ を表す __`**kwargs`__ を __`print`__ の __実引数__ に __記述__ します。

```python
def dprint(debug, *args, **kwargs):
    if debug:
        print(*args, **kwargs)
```

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

```diff_python
def dprint(debug, *args, **kwargs):
    if debug:
-       print(*args, kwargs)
+       print(*args, **kwargs)
```
</details>

__修正__ した __`dprint`__ に対して、下記の __先ほどと同じ__ プログラムを __実行__ すると、__1 行目__ の処理によって __`"abc"`__ が __改行せず__ に __表示__ されるため、__意図通り__ の __表示__ が行われます。

```python
dprint(True, "abc", end="")
dprint(True, "def")
```

実行結果
```
abcdef
```

__`**kwargs`__ と、__マッピング型の展開__ の __関係__ は、__`*args`__ と、__反復可能オブジェクトの展開__ の関係と __同様__ なので、その説明は省略します。

これで、デバッグ表示を行うための __`dprint`__ は __完成__ です。

## 可変長引数と実引数の展開に関する補足

以後は、__反復可能オブジェクトの展開__ と、__マッピング型の展開__ の __総称__ を、__実引数の展開__ と表記することにします。

以下、__可変長引数__ と、__実引数の展開__ に関するいくつかの __補足説明__ を行います。


### 可変長引数と実引数の展開の関係のまとめ

__可変長引数__ と __実引数の展開__ の __関係__ を __まとめる__ と、以下のようになります。

|  | 記述場所 | 行う処理 |
|:--|:-:|:--|
| __`*args`__  | 仮引数 | __位置引数__ → __反復可能オブジェクト（tuple）__  |
| __反復可能オブジェクトの展開__  | 実引数 |  __反復可能オブジェクト__ → __位置引数__ |
| __`**kwargs`__  | 仮引数 | __キーワード引数__ → __マッピング型（dict）__  |
| __マッピング型の展開__  | 実引数 |  __マッピング型__ → __キーワード引数__ |

上記の 4 つは、下記の関係にあります。

* __可変長引数__ と、__展開__ は __逆の処理__ を行う
* 「__`*args`__ と __`**kwargs`__」、「__反復可能オブジェクトの展開__ と __マッピング型の展開__」は、前者が __位置引数__ を、後者が __キーワード引数__ を扱うことを __除く__ と __同様の処理__ を行う

### `*args`、`**kwargs`、通常の仮引数、デフォルト引数の併用

__`*args`__、__`**kwargs`__、__通常の仮引数__、__デフォルト引数__ は、すべて併用することができます。

__併用__ した場合は、__通常の仮引数__ と __デフォルト引数__ に __対応__ する __実引数の値__ は、__`*args`__ にも __`**kwargs`__ にも __代入されません__。具体例を挙げて説明します。下記のプログラムは、__通常の仮引数 `x`__、__デフォルト引数 `y`__ と __`z`__、__`*args`__、__`**kwargs`__ を __仮引数__ として持ち、それぞれの __仮引数の値__ を __`print` で表示__ する __関数を定義__ しています。

```python
def a(x, y=2, z=3, *args, **kwargs):
    print(x, y, z)
    print(args)
    print(kwargs)
```

この関数に対して下記のプログラムを実行すると、下記の処理が行われます。

* __位置引数 `1`__ が __対応__ する __通常の仮引数 `x`__ に __代入__ される
* __キーワード引数 `z=5`__ によって、__対応__ する __デフォルト引数 `z`__ に __`5`__ が __代入__ される。
* __キーワード引数 `b=5`__ に __対応__ する通常の仮引数もデフォルト引数も __存在しない__ ので、__`{"b": 5}`__ という __dict__ が __`kwargs`__ に __代入__ される
* __デフォルト引数 `y`__ に __対応__ する実引数が __存在しない__ ので、__`y`__ には __デフォルト値__ である __`2`__ が __代入__ される
* __すべて__ の __位置引数__ に __対応__ する __仮引数__ が __存在__ するので、__`args`__ には、__空の tuple__ が __代入__ される

```python
a(1, z=5, b=3)
```

実行結果
```
1 2 5
()
{'b': 3}
```

上記をまとめると、以下のようになります

:::note warn
__`*args`__、__`**kwargs`__、__通常の仮引数__、__デフォルト引数__ を __併用__ した場合は、__`*args`__ と __`**kwargs`__ には、__通常の仮引数__ と __デフォルト引数__ に __代入__ された __値__ は __入らない__。
:::

### 仮引数の記述に関するルール

__`*args`__、__`**kwargs`__、__通常の仮引数__、__デフォルト引数__ を __併用__ する場合は、下記の __ルール__ があります。__ルール 1、2、3__ を __破る__ と __エラーが発生__ します。__ルール 4、5__ は少し __細かい話__ になりますが、__思わぬバグの原因__ になる __可能性__ があるので __注意__ が必要です。

1. __通常の仮引数__ は、__それ以外__ の仮引数よりも __前に記述__ する必要がある
2. __`*args`__ と __`**kwargs`__ は、__いずれ__ も __2 つ以上記述__ することは __できない__
3. __`*args`__ は __`**kwargs`__ よりも __前に記述__ する必要がある
4. __デフォルト引数__ は、__通常の仮引数より後__ であれば、__任意の場所__ に __記述__ できる
5. __デフォルト引数__ を __`*args`__ や __`**kwargs` より後__ に __記述__ した場合は、その __デフォルト引数__ の __値__ を、__位置引数__ に __記述__ して __代入__ することは __できなくなる__

上記の __ルール 4、5__ が __分かりづらい__ と思いますので、具体例を挙げて説明します。

下記の __`a`__ と __`b`__ は、いずれも __同じ仮引数__ を持つ __関数__ ですが、__デフォルト引数 `y=2`__ と、__`*args`__ の __記述の順番__ が __異なり__ ます。


```python
def a(x, y=3, *args, **kwargs):
    print(x, y)
    print(args)
    print(kwargs)

def b(x, *args, y=3, **kwargs):
    print(x, y)
    print(args)
    print(kwargs)
```

下記のプログラムのように、__`a`__ と __`b`__ に対して __`1`__、__`y=3`__ の __順番__ で __実引数__ を __記述__ して呼び出した場合は、__`a`__ と __`b`__ は __同じ内容__ を __表示__ します。その __理由__ は以下の通りです。

* __`a`__ も __`b`__ も __最初__ に __記述__ した __位置引数 `1`__ に __対応__ する __通常の仮引数 `x`__ が __存在する__ ので、__`x`__  には __どちらも `1`__ が __代入__ される
* __`a`__ も __`b`__ も、__2 つ目__ に __記述__ した __キーワード引数 `y=5`__ に __対応__ する、__デフォルト引数 `y`__  が __存在__ するので、__デフォルト引数 `y`__ には __どちらも `2`__ が __代入__ される
* __`*args`__ と __`**args`__ に __対応__ する __実引数__ はどちらも __記述されていない__ ので、__どちらも `args`__ には __空の tuple__ が、__`kwargs`__ には __空の dict__ が __代入__ される

```python
a(1, y=5)
b(1, y=5)
```

実行結果
```
1 5
()
{}
1 5
()
{}
```

一方、下記のプログラムのように、__`a`__ と __`b`__ に対して、__`1`__、__`2`__ の __位置引数__ のみを __記述__ した場合は、下記のように __異なる表示__ が行われます。その __理由__ は以下の通りです。

* __`a`__ も __`b`__ も __最初__ に __記述__ した __位置引数 `1`__ に __対応__ する __通常の仮引数 `x`__ が __存在する__ ので、__`x`__  には __どちらも `1`__ が __代入__ される
* __`a`__ には、__2 つ目__ に __記述__ された __位置引数 `2`__ に __対応__ する __デフォルト引数 `y`__ が __存在__ するので、__`a`__ の __`y`__ には __`2`__ が __代入__ される
* __`a`__ の __`*args`__ に __対応__ する __実引数__ は __記述されていない__ ので、__`a`__ の __`args`__ には __空の tuple__ が __代入__ される
* __`b`__ には、__2 つ目__ に __記述__ された __位置引数 `2`__ に __対応__ する、__通常の仮引数__ または __デフォルト引数__ が __存在しない__ ので、__`b`__ の __`args`__ の __tuple__ の __0 番__ の __要素__ に __`2`__ が __代入__ される
* __`b`__ の __デフォルト引数 `y`__ に __対応__ する __実引数__ が __記述されていない__ ので、__`b`__ の __`y`__ には __デフォルト値__ である __`3`__ が __代入__ される
* __`**args`__ に __対応__ する __実引数__ は __どちらも記述されていない__ ので、__どちらも `kwargs`__ には __空の dict__ が __代入__ される

```python
a(1, 2)
b(1, 2)
```

実行結果
```
1 2
()
{}
1 3
(2,)
{}
```

上記からわかるように、__デフォルト引数__ を、__`*args`__ や __`**kwargs`__ より __後に記述__ した場合は、その __デフォルト引数__ の __値__ を、__位置引数__ に __記述__ して __代入できなくなります__。

### 展開に関する補足

__`*args`__ と __`**kwargs`__ に関する __ルール__ と __勘違いする__ 人がいるかもしれませんが、__反復可能オブジェクトの展開__ と、__マッピング型の展開__ は、__展開した後__ の、__すべて__ の __位置引数__ が __キーワード引数__ よりも __前に記述__ されていれば、下記のプログラムの 5 行目のように、__任意の場所__ に、__いくつでも記述__ することが __できます__。なお、下記のプログラムの 6 行目は、5 行目と同じ処理を行うプログラムです。

```python
1  def a(*args, **kwargs):
2      print(args)
3      print(kwargs)
4
5  a(1, *(2, 3), *(4, 5, 6), **{"a": 7, "b": 8}, **{"c": 9})
6  a(1, 2, 3, 4, 5, 6, a=7, b=8, c=9)
```    

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

```python
def a(*args, **kwargs):
    print(args)
    print(kwargs)

a(1, *(2, 3), *(4, 5, 6), **{"a": 7, "b": 8}, **{"c": 9})
a(1, 2, 3, 4, 5, 6, a=7, b=8, c=9)
```
</details>

実行結果
```
(1, 2, 3, 4, 5, 6)
{'a': 7, 'b': 8, 'c': 9}
(1, 2, 3, 4, 5, 6)
{'a': 7, 'b': 8, 'c': 9}
```

下記のプログラムは、展開した後で、キーワード引数が位置引数よりも前に記述されるのでエラーが発生します。なお、下記の 1 行目と 2 行目は同じ処理を行うプログラムです。

```python
a(*(1, 2), **{"a": 3}, *(4, 5, 6))
a(1, 2, a=3, 4, 5, 6)
```

実行結果
```
  Cell In[27], line 1
    a(*(1, 2), **{"a": 3}, *(4, 5, 6))
      ^
SyntaxError: iterable argument unpacking follows keyword argument unpacking
```

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

* __SyntaxError__
構文（文法のこと）（syntax）に関するエラー
* __iterable argument unpacking follows keyword argument unpacking__
反復可能（iterable）な実引数（argument）の展開（unpacking）が、キーワード引数（keyword argument）の展開（unpacking）（マッピング型の展開のこと）の後（follows）に記述されている
:::

# `ai_by_score` の改良（デバッグ表示の機能の追加）

__デバッグ表示__ を行う __`dprint`__ を使って __`ai_by_score`__ に __デバッグ表示__ の __機能を追加__ します。まず、下記のプログラムの __1 行目__ のように、[以前の記事](https://qiita.com/ysgeso/items/24a604d82249f61e199d#test_judge-%E3%81%AE%E6%94%B9%E8%89%AF-%E3%81%9D%E3%81%AE-1%E3%82%B2%E3%83%BC%E3%83%A0%E7%9B%A4%E3%81%AE%E8%A1%A8%E7%A4%BA%E3%81%AE%E6%9C%89%E7%84%A1%E3%81%AE%E9%81%B8%E6%8A%9E) で実装した __`test_judge`__ と __同様の方法__ で、__デバッグ表示__ を __行うか__ どうかを __選択__ するための、__デフォルト引数 `debug`__ を、__デフォルト値__ に __`False`__ を __設定__ して __追加__ します。

```python
 1  from copy import deepcopy
 2  from random import choice
 3
 4  def ai_by_score(mb_orig, eval_func, debug=False):
 5      dprint(debug, "Start ai_by_score")
 6      dprint(debug, mb_orig)
 7      legal_moves = mb_orig.calc_legal_moves()
 8      dprint(debug, "legal_moves", legal_moves)
 9      best_score = float("-inf")
10      best_moves = []
11      for move in legal_moves:
12          dprint(debug, "=" * 20)
13          dprint(debug, "move", move)
14          mb = deepcopy(mb_orig)
15          x, y = move
16          mb.move(x, y)
17          dprint(debug, mb)
18        
19          score = eval_func(mb)
20          dprint(debug, "score", score, "best score", best_score)
21        
22          if best_score < score:
23              best_score = score
24              best_moves = [move]
25              dprint(debug, "UPDATE")
26              dprint(debug, "  best score", best_score)
27              dprint(debug, "  best moves", best_moves)
28          elif best_score == score:
29              best_moves.append(move)
30              dprint(debug, "APPEND")
31              dprint(debug, "  best moves", best_moves)
32
33      dprint(debug, "=" * 20)
34      dprint(debug, "Finished")
35      dprint(debug, "best score", best_score)
36      dprint(debug, "best moves", best_moves)
37      return choice(best_moves)
```

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

```python
from copy import deepcopy
from random import choice

def ai_by_score(mb_orig, eval_func, debug=False):
    dprint(debug, "Start ai_by_score")
    dprint(debug, mb_orig)
    legal_moves = mb_orig.calc_legal_moves()
    dprint(debug, "legal_moves", legal_moves)
    best_score = float("-inf")
    best_moves = []
    for move in legal_moves:
        dprint(debug, "=" * 20)
        dprint(debug, "move", move)
        mb = deepcopy(mb_orig)
        x, y = move
        mb.move(x, y)
        dprint(debug, mb)
        
        score = eval_func(mb)
        dprint(debug, "score", score, "best score", best_score)
        
        if best_score < score:
            best_score = score
            best_moves = [move]
            dprint(debug, "UPDATE")
            dprint(debug, "  best score", best_score)
            dprint(debug, "  best moves", best_moves)
        elif best_score == score:
            best_moves.append(move)
            dprint(debug, "APPEND")
            dprint(debug, "  best moves", best_moves)

    dprint(debug, "=" * 20)
    dprint(debug, "Finished")
    dprint(debug, "best score", best_score)
    dprint(debug, "best moves", best_moves)
    return choice(best_moves)
```
</details>

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

```diff_python
from copy import deepcopy
from random import choice

-def ai_by_score(mb_orig, eval_func):
+def ai_by_score(mb_orig, eval_func, debug=False):
+   dprint(debug, "Start ai_by_score")
+   dprint(debug, mb_orig)
    legal_moves = mb_orig.calc_legal_moves()
+   dprint(debug, "legal_moves", legal_moves)
    best_score = float("-inf")
    best_moves = []
    for move in legal_moves:
+       dprint(debug, "=" * 20)
+       dprint(debug, "move", move)
        mb = deepcopy(mb_orig)
        x, y = move
        mb.move(x, y)
+       dprint(debug, mb)
        
        score = eval_func(mb)
+       dprint(debug, "score", score, "best score", best_score)
        
        if best_score < score:
            best_score = score
            best_moves = [move]
+           dprint(debug, "UPDATE")
+           dprint(debug, "  best score", best_score)
+           dprint(debug, "  best moves", best_moves)
        elif best_score == score:
            best_moves.append(move)
+           dprint(debug, "APPEND")
+           dprint(debug, "  best moves", best_moves)

+   dprint(debug, "=" * 20)
+   dprint(debug, "Finished")
+   dprint(debug, "best score", best_score)
+   dprint(debug, "best moves", best_moves)
    return choice(best_moves)
```
</details>

__デバッグ表示__ では、__`ai_by_score`__ で行われた __処理の経過__ を `dprint` を使って、以下のように __表示__ します。なお、__デバッグ表示__ で __何を表示するか__ は、__どのような処理の経過__ を __知りたいか__ によって __変わります__。下記のプログラムで表示する以外のデバッグ表示を行いたい人は、__自由にデバッグ表示__ を __追加__ して下さい。また、下記の __表示の仕方__ が __分かりづらい__ と __思った方__ は __自由に変更__ して下さい。

* __5 行目__：__`ai_by_score`__ の __処理を開始__ したことを __表示__ する
* __6 行目__：__処理__ を __開始した時点__ の __ゲーム盤__ を __表示__ する
* __8 行目__：__合法手の一覧__ を __表__ 示する
* __11 行目__：__新しい着手__ を __行う__ ことを __明確にする__ ために、__`=`__ を __20 個並べて表示__ する
* __12 行目__：__着手__ する __合法手__ を __表示__ する
* __17 行目__：__着手後__ の __ゲーム盤__ を __表示__ する
* __20 行目__：計算した __評価値__ と、__それまでの評価値__ の __最大値__ を __表示__ する
* __25 ~ 27 行目__：__評価値__ が、__それまでの評価値__ の __最大値__ を __超えた__ ことによって、__`best_score`__ と __`best_moves`__ が __更新__（update）したことと、__更新後__ の __値__ を __表示__ する
* __30 ~ 31 行目__：__評価値__ が、__それまでの評価値__ の __最大値__ と __同じ__ であったので、__`best_moves`__ に __着手__ を __追加__（append）te）したことと、__追加後__ の __値__ を __表示__ する
* __35 ~ 38 行目__：__`ai_by_score`__ の __処理が完了__ したことと、__評価値の最大値__ と、計算された __最善手の一覧__ を __表示__ する

## `ai2s` と `ai3s` の修正

次に、__`ai2s`__ に __デバッグ表示__ の __機能__ を __追加__ するために、下記のプログラムのように修正します。修正箇所は、__デフォルト引数 `debug`__ を __追加__ した点と、__`ai_by_score`__ に __キーワード引数 `debug=debug`__ を __追加__ した点です。

```python
def ai2s(mb, debug=False):
    def eval_func(mb):
        return 0

    return ai_by_score(mb, eval_func, debug=debug)
```

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

```diff_python
-def ai2s(mb):
+def ai2s(mb, debug=False):
    def eval_func(mb):
        return 0

-   return ai_by_score(mb, eval_func)
+   return ai_by_score(mb, eval_func, debug=debug)
```
</details>

:::note
__5 行目__ を、__`ai_by_score(mb, eval_func, debug)`__ のように、キーワード引数ではなく、__位置引数__ で __記述__ することも __できます__ が、__デバッグ表示__ を __行うか__ どうかを表す __実引数__ は、その __意味を明確__ に __するため__ に、__キーワード引数__ で __記述することが多い__ ような気がするので、__キーワード引数で記述__ しました。
:::

__`ai3s`__ も __同様の方法__ で、下記のプログラムのように修正します。

```python
def ai3s(mb, debug=False):
    def eval_func(mb):
        if mb.last_move == (1, 1):
            return 1
        else:
            return 0

    return ai_by_score(mb, eval_func, debug=debug)
```

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

```diff_python
-def ai3s(mb):
+def ai3s(mb, debug=False):
    def eval_func(mb):
        if mb.last_move == (1, 1):
            return 1
        else:
            return 0

-   return ai_by_score(mb, eval_func)
+   return ai_by_score(mb, eval_func, debug=debug)
```
</details>

## 動作の確認

修正した __`ai2s`__ が __正しく動作__ するかどうかを __確認__ するために、__`ai2`__ と __対戦__ を行います。修正した __`ai2s`__ の __デフォルト引数 `debug`__ の __デフォルト値__ は、__`False`__ であり、__`ai_match`__ の __ブロックの中__ では、__AI の関数__ を __呼び出す際__ に、__`debug`__ に関する __実引数__ を __記述していない__ ので、__`ai_match`__ を __実行__ しても __デバッグ表示__ は __行われません__。そのため、下記のプログラムのように、__これまでと同様__ の __記述__ で __`ai_match`__ による __対戦__ を、__デバッグ表示なし__ で __行う__ ことができます。__実行結果__ から、__`ai2s`__ を __正しく実装__ できていることが __確認__ できます。

```python
from ai import ai_match, ai2, ai3

ai_match(ai=[ai2s, ai2])
```

実行結果（実行結果はランダムなので下記とは異なる場合があります）

```
ai2s VS ai2
count     win    lose    draw
o        5921    2868    1211
x        2923    5830    1247
total    8844    8698    2458

ratio     win    lose    draw
o       59.2%   28.7%   12.1%
x       29.2%   58.3%   12.5%
total   44.2%   43.5%   12.3%
```

`ai3s` も、下記のプログラムのように __同様の方法__ で __正しく動作__ することが __確認__ できます。

```python
ai_match(ai=[ai3s, ai3])
```

実行結果（実行結果はランダムなので下記とは異なる場合があります）

```
ai3s VS ai3
count     win    lose    draw
o        6848    1945    1207
x        1849    7095    1056
total    8697    9040    2263

ratio     win    lose    draw
o       68.5%   19.4%   12.1%
x       18.5%   71.0%   10.6%
total   43.5%   45.2%   11.3%
```

## デバッグ表示を使った `ai3s` の処理の流れの確認

__デバッグ表示__ は、__バグを修正__ する __だけでなく__、__処理の流れ__ を __確認したい__ 場合でも __利用できます__。せっかくなので、下記のプログラムを実行することで __`ai3s`__ の __デバッグ表示__ を行い、__評価値を利用__ した __アルゴリズム__ の __処理の流れ__ を __確認__ することにします。

```python
from marubatsu import Marubatsu

mb = Marubatsu()
ai3s(mb, debug=True)
```

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

legal_moves [(0, 0), (1, 0), (2, 0), (0, 1), (1, 1), (2, 1), (0, 2), (1, 2), (2, 2)]
====================
move (0, 0)
Turn x
O..
...
...

score 0 best score -inf
UPDATE
  best score 0
  best moves [(0, 0)]
====================
move (1, 0)
Turn x
.O.
...
...

score 0 best score 0
APPEND
  best moves [(0, 0), (1, 0)]
====================
move (2, 0)
Turn x
..O
...
...

score 0 best score 0
APPEND
  best moves [(0, 0), (1, 0), (2, 0)]
====================
move (0, 1)
Turn x
...
O..
...

score 0 best score 0
APPEND
  best moves [(0, 0), (1, 0), (2, 0), (0, 1)]
====================
move (1, 1)
Turn x
...
.O.
...

score 1 best score 0
UPDATE
  best score 1
  best moves [(1, 1)]
====================
move (2, 1)
Turn x
...
..O
...

score 0 best score 1
====================
move (0, 2)
Turn x
...
...
O..

score 0 best score 1
====================
move (1, 2)
Turn x
...
...
.O.

score 0 best score 1
====================
move (2, 2)
Turn x
...
...
..O

score 0 best score 1
====================
Finished
best moves [(1, 1)]
```

__実行結果__ の __表示__ を __まとめる__ と以下の __表__ のようになります。

表から、__`score`__ が __`best_score` より大きい__、__(0, 0)__ と __(1, 1)__ の __着手__ で __更新の処理__ が行われ、__`score`__ が __`best_score`__ と __等しい__、__(1, 0)__、__(2, 0)__、__(0, 1)__ の __着手__ で __`best_moves`__ に __着手__ を __追加__ する __処理__ が __行われる__ ことが分かります。また、__(1, 1)__ の __着手以降__ では、__評価値__ が __`1` 以上__ に __なることは無い__ ので、__最善手の一覧__ が __(1, 1)__ のみになることが分かります。

| 着手 | `score` | `best_score` | `best_moves` | 処理 |
|:-:|--:|--:|:--| :-: |
| __開始時__  |   | 負の無限大 | `[]`  |
| __(0, 0)__  |  `0` | __`0`__  | __`[(0,0)]`__  | 更新 |
| __(1, 0)__  |  `0` | `0`  | __`[(0,0),(1,0)]`__  | 追加 |
| __(2, 0)__  |  `0` | `0`  | __`[(0,0),(1,0),(2,0)]`__  | 追加 |
| __(0, 1)__  |  `0` | `0`  | __`[(0,0),(1,0),(2,0),(1,0)]`__  | 追加 |
| __(1, 1)__  |  `1` | __`1`__  | __`[(1,1)]`__  | 更新 |
| __(2, 1)__  |  `0` | `1`  | `[(1,1)]`  |  |
| __(0, 2)__  |  `0` | `1`  | `[(1,1)]`  |  |
| __(1, 2)__  |  `0` | `1`  | `[(1,1)]`  |  |
| __(2, 2)__  |  `0` | `1`  | `[(1,1)]`  |  |

評価値によるアルゴリズムで行われる処理の流れが良くわからなかった人でも、__デバッグ表示__ を見ることで、__処理の流れ__ が __理解できる__ ようになったのではないでしょうか？

# 今回の記事のまとめ

今回の記事では、__評価値を利用__ した __アルゴリズム__ の __AI__ で、__デバッグ表示__ を行うことができるように __`ai_by_score`__ を __改良__ しました。また、その際に必要となる知識として、__可変長引数__ と、__実引数の展開__ について説明しました。

次回の記事では、__評価値を利用__ した __アルゴリズム__ で、__ルール 1__、__4__ を __実装__ する方法について説明します。

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

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

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

今回の記事では、marubatsu.py は修正していないので、marubatsu_new.py はありません。

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

https://github.com/ysgeso/marubatsu/blob/master/048/ai_new.py

# 次回の記事
https://qiita.com/ysgeso/items/3afe5630d7e615a28ebf
[^1]:__さらに省略したい__ 場合は、__`dp`__ という名前を付けると良いでしょう
[^2]:慣習なので __別の名前__ にしても __かまいません__ が、__他の人__ がプログラムを見た時に __分かりづらくなる__ ので __避けたほうが良い__ でしょう
[^3]:すぐ上で説明したように、__仮引数の名前__ は `*args` ではなく、__`args`__ です