Pythonで〇×ゲームのAIを一から作成する　その57　敗因の検証と改善方法

# 目次と前回の記事

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

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

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

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

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

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

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

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

| ルール | アルゴリズム |
|:-:|:--|
| __[ルール1](https://qiita.com/ysgeso/items/bdbcd4c1ab1d8731df58#%E6%9C%80%E5%88%9D%E3%81%AB%E8%A6%8B%E3%81%A4%E3%81%8B%E3%81%A3%E3%81%9F%E7%A9%BA%E3%81%84%E3%81%A6%E3%81%84%E3%82%8B%E3%83%9E%E3%82%B9%E3%81%AB%E7%9D%80%E6%89%8B%E3%82%92%E8%A1%8C%E3%81%86-ai)__  | __左上から順__ に __空いているマス__ を探し、__最初に見つかったマス__ に __着手__ する |
| __[ルール2](https://qiita.com/ysgeso/items/bdbcd4c1ab1d8731df58#%E3%83%A9%E3%83%B3%E3%83%80%E3%83%A0%E3%81%AA%E3%83%9E%E3%82%B9%E3%81%AB%E7%9D%80%E6%89%8B%E3%82%92%E8%A1%8C%E3%81%86-ai)__  | __ランダム__ なマスに __着手__ する |
| __[ルール3](https://qiita.com/ysgeso/items/1e8732db6cd1b0104276#%E3%83%AB%E3%83%BC%E3%83%AB-3-%E7%9C%9F%E3%82%93%E4%B8%AD%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>既に __埋まっていた場合__ は __ランダム__ なマスに __着手__ する | |
| __[ルール4](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>既に __埋まっていた場合__ は __ランダム__ なマスに __着手__ する | あり |
| __[ルール5](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>そうでない場合は __ランダム__ なマスに __着手__ する | |
| __[ルール6](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>そうでない場合は __ランダム__ なマスに __着手__ する |
| __[ルール6改](https://qiita.com/ysgeso/items/6c0d73cc9ef4efd62d48#%E3%83%AB%E3%83%BC%E3%83%AB-6-%E6%94%B9%E3%81%AE%E5%AE%9A%E7%BE%A9)__  | __勝てる場合__ に __勝つ__<br>そうでない場合は __相手__ が __勝利できる__ 着手を __行わない__<br>そうでない場合は __ランダム__ なマスに __着手__ する |
| __[ルール7](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>そうでない場合は __ランダム__ なマスに __着手__ する |
| __[ルール7改](https://qiita.com/ysgeso/items/6c0d73cc9ef4efd62d48#%E8%A9%95%E4%BE%A1%E5%80%A4%E3%82%92%E5%88%A9%E7%94%A8%E3%81%97%E3%81%9F-%E3%83%AB%E3%83%BC%E3%83%AB-7-%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)__  | __真ん中__ のマスに __優先的__ に __着手__ する<br>そうでない場合は __勝てる場合__ に __勝つ__<br>そうでない場合は __相手__ が __勝利できる__ 着手を __行わない__<br>そうでない場合は __ランダム__ なマスに __着手__ する |
| __[ルール8](https://qiita.com/ysgeso/items/58754ba98c286f414ea4#%E3%83%AB%E3%83%BC%E3%83%AB-8-%E3%81%AE%E5%AE%9A%E7%BE%A9)__  | __真ん中__ のマスに __優先的__ に __着手__ する<br>そうでない場合は __勝てる場合__ に __勝つ__<br>そうでない場合は __相手__ が __勝利できる__ 着手を __行わない__<br>そうでない場合は、__次__ の __自分の手番__ で __勝利できる__ ように、「__自 2 敵 0 空 1__」が 1 つ以上 __存在する__ 局面になる着手を行う<br>そうでない場合は __ランダム__ なマスに __着手__ する |
| __[ルール9](https://qiita.com/ysgeso/items/c1285c93cbfbdbdc7b77#%E3%83%AB%E3%83%BC%E3%83%AB-9-%E3%81%AE%E5%AE%9A%E7%BE%A9)__  | __真ん中__ のマスに __優先的__ に __着手__ する<br>そうでない場合は __勝てる場合__ に __勝つ__<br>そうでない場合は __相手__ が __勝利できる__ 着手を __行わない__<br>そうでない場合は、__次__ の __自分の手番__ で __必ず勝利できる__ ように、「__自 2 敵 0 空 1__」が __2 つ以上存在する__ 局面になる着手を行う<br>そうでない場合は、__次__ の __自分の手番__ で __勝利できる__ ように、「__自 2 敵 0 空 1__」が __1 つ存在する__ 局面になる着手を行う<br>そうでない場合は __ランダム__ なマスに __着手__ する |
| __[ルール10](https://qiita.com/ysgeso/items/c1285c93cbfbdbdc7b77#%E3%83%AB%E3%83%BC%E3%83%AB-10)__  | __真ん中__ のマスに __優先的__ に __着手__ する<br>そうでない場合は __勝てる場合__ に __勝つ__<br>そうでない場合は __相手__ が __勝利できる__ 着手を __行わない__<br>そうでない場合は、__次__ の __自分の手番__ で __必ず勝利できる__ ように、「__自 2 敵 0 空 1__」が __2 つ以上存在する__ 局面になる着手を行う<br>そうでない場合は、__以下__ の 2 つを __総合的に判断__ して着手を行う<ul><li>__次__ の __自分の手番__ で __勝利できる__ ように、「__自 2 敵 0 空 1__」が __1 つ存在する__ 局面になる着手を行う</li><li>__自分__ が __有利になる__ ように、「__自 1 敵 0 空 2__」が __最も多い__ 着手を行う</li></ul>そうでない場合は __ランダム__ なマスに __着手__ する |
| __[ルール11](https://qiita.com/ysgeso/items/c1285c93cbfbdbdc7b77#%E3%83%AB%E3%83%BC%E3%83%AB-11)__  | __真ん中__ のマスに __優先的__ に __着手__ する<br>そうでない場合は __勝てる場合__ に __勝つ__<br>そうでない場合は __相手__ が __勝利できる__ 着手を __行わない__<br>そうでない場合は、__次__ の __自分の手番__ で __必ず勝利できる__ ように、「__自 2 敵 0 空 1__」が __2 つ以上存在する__ 局面になる着手を行う<br>そうでない場合は、__以下__ の 3 つを __総合的に判断__ して着手を行う<ul><li>__次__ の __自分の手番__ で __勝利できる__ ように、「__自 2 敵 0 空 1__」が __1 つ存在する__ 局面になる着手を行う</li><li>__自分__ が __有利になる__ ように、「__自 1 敵 0 空 2__」が __最も多い__ 着手を行う</li><li>__相手__ が __不利になる__ ように、「__自 0 敵 1 空 2__」が __最も少ない__ 着手を行う</li></ul>そうでない場合は __ランダム__ なマスに __着手__ する |

基準となる __`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)__<br>__[`ai1s`](https://qiita.com/ysgeso/items/3afe5630d7e615a28ebf#ai1s-%E3%81%AE%E4%BF%AE%E6%AD%A3)__  | __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)__<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)__  | 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)__<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)__  | __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)__<br>__[`ai4s`](https://qiita.com/ysgeso/items/3afe5630d7e615a28ebf#ai4s-%E3%81%AE%E4%BF%AE%E6%AD%A3%E3%81%A8%E5%8B%95%E4%BD%9C%E3%81%AE%E7%A2%BA%E8%AA%8D)__  | __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)__<br>__[`ai5s`](https://qiita.com/ysgeso/items/6c0d73cc9ef4efd62d48#ai5s-%E3%81%AE%E5%AE%9F%E8%A3%85)__  | __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 |
| __[`ai6s`](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.6__ | __1.9__ | 9.5 | __69.4__ | __9.1__ | 21.5 | __79.0__ | __5.5__ | 15.5 |
| __[`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>__[`ai7s`](https://qiita.com/ysgeso/items/6c0d73cc9ef4efd62d48#ai7s-%E3%81%AE%E5%AE%9A%E7%BE%A9)__ | __95.8__ | __0.2__ | 4.0 | __82.3__ | __2.4__ | 15.3 | __89.0__ | __1.3__ | 9.7 |
| __[`ai8s`](https://qiita.com/ysgeso/items/085fd15fbd91d5cdf770#ai8s-%E3%81%AE%E4%BF%AE%E6%AD%A3)__  | __98.2__ | __0.1__ | 1.6 | __89.4__ | __2.5__ | 8.1 | __93.8__ | __1.3__ | 4.9 |
| __[`ai9s`](https://qiita.com/ysgeso/items/c1285c93cbfbdbdc7b77#ai9s-%E3%81%AE%E5%AE%9A%E7%BE%A9)__  | __98.7__ | __0.1__ | 1.2 | __89.6__ | __2.4__ | 8.0 | __94.1__ | __1.3__ | 4.6 |
| __[`ai10s`](https://qiita.com/ysgeso/items/c1285c93cbfbdbdc7b77#ai10s-%E3%81%AE%E5%AE%9A%E7%BE%A9)__  | __97.4__ | __0.0__ | 2.6 | __85.6__ | __2.6__ | 11.7 | __91.5__ | __1.3__ | 7.2 |
| __[`ai11s`](https://qiita.com/ysgeso/items/c1285c93cbfbdbdc7b77#ai11s-%E3%81%AE%E5%AE%9A%E7%BE%A9%E3%81%A8ai2ai10s-%E3%81%A8%E3%81%AE%E5%AF%BE%E6%88%A6)__  | __98.1__ | __0.0__ | 1.9 | __82.5__ | __1.9__ | 15.6 | __90.3__ | __1.0__ | 8.7 | あり |

# 前回の記事のおさらい

前回の記事では、__`ai10s` VS `ai11s`__ で、__`ai11s`__ が __負ける試合__ で行われる __着手__ の __選択__ を __検証__ し、__下図__ のような __着手__ が行われることがわかりました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/5861a470-a3b7-08c6-2c9a-8f84a055516f.png" width="600">

# 4 手目で (0, 2) に着手を行った場合の検証

前回の記事では、__4 手目__ で __`ai11s`__ が上図の __(0, 2)__ の着手を __選択__ した場合の __検証__ を __行っていない__ ので、最初に __その検証__ を __行います__。なお、__実際__ には __4 手目__ で、__(2, 0)__ に __着手__ する __場合__ がありますが、__その場合__ の __局面__ は __(0, 2)__ に着手した場合の局面と __[同一局面](https://qiita.com/ysgeso/items/e78980b366e123ca57f1#%E5%90%8C%E4%B8%80%E5%B1%80%E9%9D%A2%E3%81%AE%E5%AE%9A%E7%BE%A9%E3%81%A8%E6%89%B1%E3%81%84)__ なので、__(0, 2)__ に __着手__ した場合の __検証__ で __まとめて説明__ することにします。

## 5 手目の検証

下図は、__`ai11s`__ が、__4 手目__ で __(0, 2)__ に __着手__ を行った場合の図です。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/0062069c-96aa-ad7b-03d0-136fdf9fbd30.png" width="150">

__上記__ の __局面__ は、__「自 0 敵 2 空 1」__ が __1 つ存在__ する __局面__ です。__ルール 10__ には、__相手の勝利を阻止する__ という __条件__ があるので、__`ai10s`__ は __必ず (1, 0)__ を __選択するはず__ です。

そのことを、下記のプログラムを実行することで __検証__ して __確認__ することにします。

* __4 ~ 8 行目__：上図の __4 手目まで__ の __着手__ を __行う__

```python
1  from marubatsu import Marubatsu
2  from ai import ai_match, ai10s, ai11s
3
4  mb = Marubatsu()
5  mb.move(1, 1)
6  mb.move(0, 0)
7  mb.move(2, 2)
8  mb.move(0, 2)
9  ai10s(mb, debug=True)
```

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

```python
from marubatsu import Marubatsu
from ai import ai_match, ai10s, ai11s

mb = Marubatsu()
mb.move(1, 1)
mb.move(0, 0)
mb.move(2, 2)
mb.move(0, 2)
ai10s(mb, debug=True)
```
</details>

<details><summary><strong>実行結果（長いのでクリックして開いてください）</strong></summary>

```
Start ai_by_score
Turn o
x..
.o.
X.o

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

defaultdict(<class 'int'>,
            {Markpat(last_turn=0, turn=2, empty=1): 1,
             Markpat(last_turn=1, turn=0, empty=2): 2,
             Markpat(last_turn=1, turn=1, empty=1): 3,
             Markpat(last_turn=2, turn=0, empty=1): 1,
             Markpat(last_turn=2, turn=1, empty=0): 1})
score -100 best score -inf
UPDATE
  best score -100
  best moves [(1, 0)]
====================
move (2, 0)
Turn x
x.O
.o.
x.o

defaultdict(<class 'int'>,
            {Markpat(last_turn=0, turn=2, empty=1): 1,
             Markpat(last_turn=1, turn=0, empty=2): 2,
             Markpat(last_turn=1, turn=1, empty=1): 2,
             Markpat(last_turn=2, turn=0, empty=1): 1,
             Markpat(last_turn=2, turn=1, empty=0): 2})
score -100 best score -100
APPEND
  best moves [(1, 0), (2, 0)]
====================
move (0, 1)
Turn x
x..
Oo.
x.o

defaultdict(<class 'int'>,
            {Markpat(last_turn=0, turn=1, empty=2): 1,
             Markpat(last_turn=1, turn=0, empty=2): 2,
             Markpat(last_turn=1, turn=1, empty=1): 2,
             Markpat(last_turn=1, turn=2, empty=0): 1,
             Markpat(last_turn=2, turn=0, empty=1): 1,
             Markpat(last_turn=2, turn=1, empty=0): 1})
score 3 best score -100
UPDATE
  best score 3
  best moves [(0, 1)]
====================
move (2, 1)
Turn x
x..
.oO
x.o

defaultdict(<class 'int'>,
            {Markpat(last_turn=0, turn=1, empty=2): 1,
             Markpat(last_turn=0, turn=2, empty=1): 1,
             Markpat(last_turn=1, turn=0, empty=2): 1,
             Markpat(last_turn=1, turn=1, empty=1): 2,
             Markpat(last_turn=2, turn=0, empty=1): 2,
             Markpat(last_turn=2, turn=1, empty=0): 1})
score -100 best score 3
====================
move (1, 2)
Turn x
x..
.o.
xOo

defaultdict(<class 'int'>,
            {Markpat(last_turn=0, turn=1, empty=2): 1,
             Markpat(last_turn=0, turn=2, empty=1): 1,
             Markpat(last_turn=1, turn=0, empty=2): 2,
             Markpat(last_turn=1, turn=1, empty=1): 1,
             Markpat(last_turn=2, turn=0, empty=1): 1,
             Markpat(last_turn=2, turn=1, empty=0): 2})
score -100 best score 3
====================
Finished
best score 3
best moves [(0, 1)]
```
</details>

下記は、__上記の結果__ を __まとめた表__ です。

| |  | 「201」 | 「021」 | 「102」 | 評価値 |
|:-:|:-:|:-:|:-:|:-:|:-:|
| | __評価値__ | 1：`+1`<br>2~：`100` | `-100` | 1 つで<br>`+1` | |
| 1 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/807233c1-d7b7-0eb5-ed40-1a36bcb88f16.png" width="65">  | 1 | 1 | 2 | -100 |
| 2 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/8601ea9e-954d-918c-eafb-cf9c10f25be0.png" width="65">  | 1 | 1 | 2 | -100 |
| 3 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/6362f1f4-4487-543a-264e-388939c58c87.png" width="65">  | 1 | | 2 | 3 |
| 4 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/09a3a912-2537-3307-b6bd-4972cf88f932.png" width="65">  | 2 | 1 | 1 | -100 |
| 5 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/f21eaf0e-e6ca-7da7-3ba9-19c55893db65.png" width="65">  | 1 | 1 | 2 | -100 |

下記は __ルール 10__ の __条件__ の __優先順位__ です。

1. 「__自 0 敵 2 他 1__」が __存在__ する場合は __-100__
2. 「__自 2 敵 0 他 1__」が __2 つ以上存在__ する場合は __100__
3. 下記の __評価値__ の __合計__
    1.「__自 2 敵 0 他 1__」が __1 つ__ の場合は __1__
    2.「__自 1 敵 0 他 2__」が __1 つにつき +1__

__上記の表__ から、__実際__ に __`ai10s`__ が __上記__ の __優先順位__ で __評価値__ の __計算__ を __行っている__ ことが __確認__ できます。例えば、__合法手 4__ では、『「__自 0 敵 2 他 1__」が __存在__ する』と『__自 2 敵 0 他 1__」が __2 つ以上存在__ する』の __2 つの条件__ が __同時__ に __満たされます__ が、__前者__ の条件のほうが __優先順位__ が __高い__ ので、__評価値__ は __-100__ に __なっています__。

上記の __表__ から、__`ai10s`__ は、__最も評価値__ が __高い 3__ になる、__合法手 3__ の __(0, 1)__ を __必ず選択する__ ことがわかります。そのことは、下記の __実行結果__ の __最後の 2 行__ からも __確認__ できます。

```
best score 3
best moves [(0, 1)]
```

__5 手目__ で __`ai10s`__ が __必ず (0, 1)__ に __着手を行う__ ことが __分かった__ ので、__その着手__ が行われた __局面__ に対して __6 手目__ の __検証__ を __行う__ ことにします。

## 6 手目の検証

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/7e65e06a-95d1-0917-00a2-1c7d87f7b6f1.png" width="150">

__上記の局面__ も、「__自 0 敵 2 空 1__」が __1 つ存在__ する __局面__ で、__ルール 11__ にも、__相手の勝利を阻止する__ という __条件__ があるので、__`ai11s`__ は __必ず (2, 1)__ を __選択するはず__ です。

そのことは、下記のプログラムで、__(2, 1)__ に __着手__ した場合の __評価値__ が __0__、__それ以外__ の場合の __評価値__ が __-100__ になることから __確認__ できます。

```python
mb.move(0, 1)
ai11s(mb, debug=True)
```

<details><summary><strong>実行結果（長いのでクリックして開いてください）</strong></summary>

```
Start ai_by_score
Turn x
x..
Oo.
x.o

legal_moves [(1, 0), (2, 0), (2, 1), (1, 2)]
====================
move (1, 0)
Turn o
xX.
oo.
x.o

defaultdict(<class 'int'>,
            {Markpat(last_turn=0, turn=1, empty=2): 1,
             Markpat(last_turn=0, turn=2, empty=1): 1,
             Markpat(last_turn=1, turn=1, empty=1): 3,
             Markpat(last_turn=1, turn=2, empty=0): 1,
             Markpat(last_turn=2, turn=0, empty=1): 1,
             Markpat(last_turn=2, turn=1, empty=0): 1})
score -100 best score -inf
UPDATE
  best score -100
  best moves [(1, 0)]
====================
move (2, 0)
Turn o
x.X
oo.
x.o

defaultdict(<class 'int'>,
            {Markpat(last_turn=0, turn=1, empty=2): 1,
             Markpat(last_turn=0, turn=2, empty=1): 1,
             Markpat(last_turn=1, turn=1, empty=1): 2,
             Markpat(last_turn=1, turn=2, empty=0): 1,
             Markpat(last_turn=2, turn=0, empty=1): 1,
             Markpat(last_turn=2, turn=1, empty=0): 2})
score -100 best score -100
APPEND
  best moves [(1, 0), (2, 0)]
====================
move (2, 1)
Turn o
x..
ooX
x.o

defaultdict(<class 'int'>,
            {Markpat(last_turn=0, turn=1, empty=2): 1,
             Markpat(last_turn=1, turn=0, empty=2): 1,
             Markpat(last_turn=1, turn=1, empty=1): 3,
             Markpat(last_turn=1, turn=2, empty=0): 2,
             Markpat(last_turn=2, turn=1, empty=0): 1})
score 0 best score -100
UPDATE
  best score 0
  best moves [(2, 1)]
====================
move (1, 2)
Turn o
x..
oo.
xXo

defaultdict(<class 'int'>,
            {Markpat(last_turn=0, turn=1, empty=2): 1,
             Markpat(last_turn=0, turn=2, empty=1): 1,
             Markpat(last_turn=1, turn=0, empty=2): 1,
             Markpat(last_turn=1, turn=1, empty=1): 2,
             Markpat(last_turn=1, turn=2, empty=0): 1,
             Markpat(last_turn=2, turn=1, empty=0): 2})
score -100 best score 0
====================
Finished
best score 0
best moves [(2, 1)]
```
</details>

また、そのことは、下記の __実行結果__ の __最後の 2 行__ からも __確認__ できます。なお、__実行結果__ を __まとめた表__ は、__5 手目__ の場合と __ほとんと変わらない__ ので __省略__ します。

```
best score 0
best moves [(2, 1)]
```

__6 手目__ で __`ai11s`__ が __必ず (2, 1)__ に __着手を行う__ ことが __分かった__ ので、__その着手__ が行われた __局面__ に対して __7 手目__ の __検証__ を __行う__ ことにします。

## 7 手目の検証

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/a3195c3f-1ea9-5fde-7419-ceee333a56a3.png" width="150">

__これまで__ と __同様__ に、下記のプログラムで __`ai10s`__ がそれぞれの __合法手__ に対して __どのように__ 評価値を __計算__ するかを __表示__ し、その __結果__ を __表にまとめます__。

```python
mb.move(2, 1)
ai10s(mb, debug=True)
```

<details><summary><strong>実行結果（長いのでクリックして開いてください）</strong></summary>

```
Start ai_by_score
Turn o
x..
ooX
x.o

legal_moves [(1, 0), (2, 0), (1, 2)]
====================
move (1, 0)
Turn x
xO.
oox
x.o

defaultdict(<class 'int'>,
            {Markpat(last_turn=1, turn=1, empty=1): 4,
             Markpat(last_turn=1, turn=2, empty=0): 1,
             Markpat(last_turn=2, turn=0, empty=1): 1,
             Markpat(last_turn=2, turn=1, empty=0): 2})
score 1 best score -inf
UPDATE
  best score 1
  best moves [(1, 0)]
====================
move (2, 0)
Turn x
x.O
oox
x.o

defaultdict(<class 'int'>,
            {Markpat(last_turn=1, turn=0, empty=2): 1,
             Markpat(last_turn=1, turn=1, empty=1): 2,
             Markpat(last_turn=1, turn=2, empty=0): 1,
             Markpat(last_turn=2, turn=1, empty=0): 4})
score 1 best score 1
APPEND
  best moves [(1, 0), (2, 0)]
====================
move (1, 2)
Turn x
x..
oox
xOo

defaultdict(<class 'int'>,
            {Markpat(last_turn=0, turn=1, empty=2): 1,
             Markpat(last_turn=1, turn=1, empty=1): 2,
             Markpat(last_turn=1, turn=2, empty=0): 1,
             Markpat(last_turn=2, turn=0, empty=1): 1,
             Markpat(last_turn=2, turn=1, empty=0): 3})
score 1 best score 1
APPEND
  best moves [(1, 0), (2, 0), (1, 2)]
====================
Finished
best score 1
best moves [(1, 0), (2, 0), (1, 2)]
```
</details>

下記は、__上記の結果__ を __まとめた表__ です。

| |  | 「201」 | 「021」 | 「102」 | 評価値 |
|:-:|:-:|:-:|:-:|:-:|:-:|
| | __評価値__ | 1：`+1`<br>2~：`100` | `-100` | 1 つで<br>`+1` | |
| 1 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/a81b362e-095d-6d60-0761-75706d6366f8.png" width="65">  | 1 | |  | 1 |
| 2 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/8a8991a9-13ae-d0bc-672c-dbce78bf3772.png" width="65">  |  | | 1 | 1 |
| 3 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/f90df855-726d-dd13-5556-02e4c774276f.png" width="65">  | 1 | |  | 1 |

__上記の表__ から、__どの合法手__ でも、「__自 2 敵 0 他 1__」と「__自 1 敵 0 他 2__」の __どちらか__ が __1 つ__ であるため、__評価値__ が __同じ 1__ になります。従って、__3 つの合法手__ である __(1, 0)__、__(2, 0)__、__(1, 2)__ がそれぞれ 1 / 3 = __約 33 %__ の __確率__ で __選択される__ ことが __わかります__。そのことは、下記の __実行結果__ の __最後の 2 行__ からも __確認__ できます。

```
best score 1
best moves [(1, 0), (2, 0), (1, 2)]
```

## 8 手目の検証

__7 手目__ の __局面__ は、下記の __3 通り__ が __あります__。__それぞれの局面__ に対して、__これまでと同様__ に、__`ai11s`__ を実行し、__実行結果__ から __`ai11s`__ が __選択__ する __合法手__ を __検証__ することが __できますが__、3 つの局面に対して、それぞれ __プログラムを実行__ してその __結果を記述__ して __説明__ すると、__非常に長く__ なります。__8 手目__ を __着手__ する __局面__ では、__残り__ のマスが __2 マスしかない__ ので、プログラムに頼らなくても __頭の中だけ__ で __簡単__ に __`ai11`__ が __行う着手__ を __知ることが可能__ なので、__その方法__ で __検証する__ ことにします。興味と余裕がある方は、これまで通りの方法で `ai11s` を実行して検証してみて下さい。

### 7 手目で (1, 0) に着手した場合

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/e0e1435e-c85d-ad4c-830c-282e861cfed1.png" width="150">

__この場合__ は、__次__ に __相手__ が __(1, 2)__ に __着手__ すると __負けてしまう__ ので、明らかに __`ai11s`__ は __(1, 2)__ に __着手__ を行います。__その結果__、__残りのマス__ が __1 マスになる__ ので、__9 手目__ で __`ai10s`__ は __(2, 0)__ に __着手__ を行い、下図のように __引き分け__ になります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/efced884-ea43-cc78-e3e5-ecf1c1be1048.png" width="230">

### 7 手目で (1, 2) に着手した場合

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/5af53eb0-ffc3-8ac7-4315-5a142694c33c.png" width="150">

__この場合__ も __上記と同様__ に、__次__ に __相手__ が __(1, 0)__ に __着手__ すると __負けてしまう__ ので、明らかに __`ai11s`__ は __(1, 0)__ に __着手__ を行います。__その結果__、__残りのマス__ が __1 マスになる__ ので、__9 手目__ で __`ai10s`__ は __(2, 0)__ に __着手__ を行い、下図のように __引き分け__ になります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/6f248d92-b260-ccce-80e9-891e19a20250.png" width="230">

### 7 手目で (2, 0) に着手した場合

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/57408deb-ba06-9f9b-2297-356b9a81717c.png" width="150">

__この場合__ は、少し考えればすぐにわかると思いますが、__残りのマス__ に __×__ と __〇__ を __どのような順番__ で __配置__ しても __引き分け__ になります。従って、__どのような AI どうしの対戦__ であっても、__この局面__ になった時点で __引き分け__ になることが __確定__ します。

### 8 手目の検証のまとめ

__上記__ から、__7 手目__ で 3 つの __どの着手__ が __選択された場合でも__、__必ず引き分け__ になります。

## 4 手目で (0, 2) に着手を行った場合の検証からわかること

__上記の検証__ から、__下記__ の事が __わかります__。

* __4 手目__ で __(0, 2)__ に __着手__ が行われた場合は、__`ai10s`__ VS __`ai11s`__ は __7 手目まで__ は __必ず同一局面__ になる着手を行う
* __7 手目__ で __選択__ される __着手__ は __3 種類__ あるが、__どの着手__ が行われても __引き分け__ になる

__上記__ から、__4 手目__ で __(0, 2)__ に __着手__ が行われた場合は、__`ai10s` VS `ai11s`__ は __必ず引き分けになる__ ことが __分かりました__。

# `ai10s` VS `ai11s` で行われる着手のまとめ

下図は、__`ai10s` VS `ai11s`__ で __行われる着手__ を __まとめた__ ものです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/f2e03b8d-74a2-004b-51cc-4017cb1308e4.png" width="700">

__この図__ から __以下の事__ が __分かります__。

* __4 手目__ で __`ai11s`__ が __50 %__ の __確率__ で __(1, 2)__ に __着手__ すると、__`ai11s` が負ける__
* __4 手目__ で __`ai11s`__ が __50 %__ の __確率__ で __(0, 2)__ に __着手__ すると、__引き分け__ になる

このことは、[前回の記事](https://qiita.com/ysgeso/items/e78980b366e123ca57f1#ai11s-%E3%81%AE%E5%95%8F%E9%A1%8C%E7%82%B9)で実行した、下記のプログラムによる __`ai11s` VS `ai10s`__ の __対戦結果__ で、__`ai11s`__ が __× を担当__ した場合の __敗率 約 50 %__、__引き分け 約 50%__ と __一致__ します。従って、これまでに行ってきた __`ai10s`__ VS __`ai11s`__ で __行われる着手__ の __検証結果__ に __ほぼ間違いがなさそう__ であることが __確認__ できました。 

:::note
__人間が行う検証__ には、__間違いがある可能性__ があります。そのため、上記では「__ほぼ間違いがなさそう__」であるという __表現__ にしています。
:::

```python
ai_match(ai=[ai11s, ai10s])
```

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

```
ai11s VS ai10s
count     win    lose    draw
o        2223       0    7777
x           0    5053    4947
total    2223    5053   12724

ratio     win    lose    draw
o       22.2%    0.0%   77.8%
x        0.0%   50.5%   49.5%
total   11.1%   25.3%   63.6%
```

# `ai11s` の敗因の検証と改善

__`ai10s` VS `ai11s`__ で行われる __着手の検証__ から、__以下の事__ が __判明__ しました。

* __`ai10s`__ と __`ai11s`__ が、__ルール通り__ の __処理__ を行っている __可能性が高い__ こと
* __`ai10s`__ VS __`ai11s`__ で __`ai11s`__ が __50 %__ の __確率__ で __負ける理由__

上記の __2 つ__ は __どちらも重要__ です。__AI__ が __ルール通り__ の __処理を行っている__ ということは、AI の __プログラムの実装__ には __問題がない__ ということを表すからです。従って、__問題を修正__ するためには、__ルールの不備__ を __見つける__ という __作業__ を __行えば良い__ ことが __分かります__。

__負ける理由__ が __判明した__ ということは、その __理由を分析__ することで、__負けないよう__ に __ルールを改善__ することができる __可能性が生じる__ ということを意味するからです。

なお、__負ける理由__ のことを __敗因__ と呼ぶので、以後は __敗因__ と __表記__ することにします。

:::note
「__可能性が生じる__」という __表現__ にしたのは、残念ながら __原因が分かっても__ 問題を __解決する方法__ が __ない場合__ や、__分からない場合__ が __あるから__ です。
:::

## `ai11s` の敗因の検証と改善方法の検討

そこで、__`ai11s`__ の __敗因__ を __検証__ し、__`ai11s`__ を __改善する方法__ を __検討する__ ことにします。

先ほどの __検証__ から、__`ai10s`__ VS __`ai11s`__ の __対戦__ では、__`ai11s`__ が __4 手目__ で __(1, 2)__ と __(0, 2)__ の __どちらに着手__ を __行うか__ で、__敗北__ と __引き分け__ が __決まること__ が __分かりました__。このことから、__`ai10s` VS `ai11s`__ で、__`ai11s`__ が __敗北__ した場合の __敗因__ は、__4 手目__ で __(0, 2)__ に __着手__ したことであることが __わかります__。このような、__負けにつながる着手__ の事を __敗着__ と呼びます。

次に、この __敗着__ を __避ける方法__ について __検討__ することにします。__4 手目__ では、__引き分け__ に __つながる (1, 2)__ という __合法手__ が __存在__ するので、__`ai11s`__ が __4 手目__ で、__必ず (1, 2)__ を __選択する__ ように __ルールを改善__ できれば、__`ai10s`__ VS __`ai11s`__ の __対戦__ の結果は __必ず引き分け__ になり、その結果、__`ai11s`__ が __負けること__ が __なくなります__。

上記の __改善を行う__ ためには、__`ai11s`__ の __評価値__ の __計算方法__ を __修正__ する __必要__ があります。どのように修正すれば良いかについて少し考えてみて下さい。

:::note
ここまでで行ったことは、__現実の世界__ でも __実際__ に __良く行われる__ ことです。例えば、スポーツの試合で __負けた場合__ に、__負けた試合__ を __細かく検証__ することで、__敗因を発見__ し、その __敗因を分析__ することで __次は負けないよう__ に __改善します__。
:::

## 評価値の計算方法の修正

下記は、[前回の記事](https://qiita.com/ysgeso/items/e78980b366e123ca57f1#4-%E6%89%8B%E7%9B%AE%E3%81%AE%E6%A4%9C%E8%A8%BC)で __4 手目__ の __検証__ を行った際に __作成した表__ です。

| |  | 「201」 | 「021」 | 「102」 | 「012」 |評価値 |
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| | __評価値__ | 1：`+1`<br>2~：`100` | `-100` | 1 つで<br>`+1` | 1 つで<br>`-1` | |
| 1 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/3d1fdd6a-2a1d-06e2-1ff7-f9bb9aae2216.png" width="140"> | 1 | | 1 | 4 | -2 |
| 2 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/2a606113-076e-2b87-8268-a76e4bfd89b7.png" width="140"> | 1 | | 1 | 3 | -1 |
| 3 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/5f16999c-609b-1748-91af-2ff41108eefe.png" width="140"> |  | | 2 | 3  | -1 |

__上記の表__ で、__敗着__ となる __合法手 3__ が __選択__ される __可能性__ がある __理由__ は、__その評価値__ が __合法手 2__ の __評価値__ と __並んで__、__最も高いから__ です。先程の、__引き分けになる__ ことが分かっている __合法手 2 のみ__ を __選択__ するという __改善方法__ を __実現するため__ には、__合法手 2__ の __評価値のみ__ が __最も高くなる__ ようにする __必要__ があります。そのために、__ルール 11__ の __評価値__ の __計算方法__ を __どのように修正すれば良いか__ について少し考えてみて下さい。

### 評価値の計算のパラメータ

__4 手目__ の局面では、__ルール 11__ の『「__自 2 敵 0 空 1__」が __2 つ以上存在__ する 局面になる __着手__ を行う』__以上__ の __優先順位__ の __条件__ を満たす __合法手__ は __存在しない__ ので、__`ai11s`__ は、それぞれの __合法手__ の __評価値__ を、__下記の表__ で __設定__ される __評価値__ の __合計__ で __計算__ します。

| 項目 | 評価値 |
|:-:|:-:|
| 「__自 2 敵 0 空 1__」が __1 つ__ の場合  | 1  |
| 「__自 1 敵 0 空 2__」が __1 つあたり__ | 1  |
| 「__自 0 敵 1 空 2__」が __1 つあたり__ | -1  |

この表の __評価値__ の __数字__ は、[以前の記事](https://qiita.com/ysgeso/items/c1285c93cbfbdbdc7b77#%E4%BF%AE%E6%AD%A3%E3%81%97%E3%81%9F-ai10s-%E3%81%A8-ai2ai9s-%E3%81%A8%E3%81%AE%E5%AF%BE%E6%88%A6)で __試行錯誤__ して決めたものであったり、__[とりあえずこの値に設定する](https://qiita.com/ysgeso/items/c1285c93cbfbdbdc7b77#%E6%95%B0%E3%82%92%E8%80%83%E6%85%AE%E3%81%99%E3%82%8B%E8%A9%95%E4%BE%A1%E5%80%A4%E3%81%AE%E8%A8%AD%E5%AE%9A%E6%96%B9%E6%B3%95)__ という方法で決めたものであるため、__それぞれの値__ が __AI を強くする__ ために __ベストな数字であるか__ どうかは __わかっていません__。実際に、[以前の記事](https://qiita.com/ysgeso/items/c1285c93cbfbdbdc7b77#%E4%BF%AE%E6%AD%A3%E3%81%97%E3%81%9F-ai10s-%E3%81%A8-ai2ai9s-%E3%81%A8%E3%81%AE%E5%AF%BE%E6%88%A6)で、『「__自 2 敵 0 空 1__」が __1 つ__ の場合』の __評価値__ を __変えて `ai9s`__ と __対戦__ した __結果__、__対戦成績__ が __変化__ したことからわかるように、__これらの値__ を __変える__ ことで、__AI の強さ__ が __変わります__。

このような、__何かを計算__ する際に __利用__ する、__変更可能__ な __値__ を __パラーメータ__ と呼びます。__4 手目__ の __局面__ で、__合法手 2__ の __評価値のみ__ が __最も高くなる__ ような __パラメータ__ を __見つける__ ことで、__目的__ を __達成__ することが __できます__。

:::note
__パラメータ__（__parameter__）は、__日本語__ では __変数__ と __訳され__、__プログラム用語__ での __任意の値__ を __代入__ できる __`a`__ のような __変数__ も __パラメータの一種__ です。ただし、__プログラム用語__ の __変数__ は、__一般的__ に __英語__ では __variable__ と __表記__ し、__parameter__ は __仮引数__ という意味で、__変数__ と __区別__ して __使われます__[^1]。

__プログラム用語__ の __変数__ を、英語で __variable__ と __表記__ する のは、おそらく「__変化__（vary）することが __できる__（__可変__）」という __意味__ を __強調したい__ からではないかと思います。一方、__仮引数__ は、__関数__ で行う __何らかの計算__ を __行うため__ の __変更可能__ な __値__ と __考えることができる__ ので、__parameter__ と __表記__ するのではないかと思います。

__パラメータ__ は、__ゲーム__ などでは、キャラクターやアイテムの __能力値など__ を __表す用語__ として使われます。それらの __パラメータ__ は、__個々__ のキャラクターやアイテムによって __値が変化__ し、__戦闘__ などで行われる __計算__ で __利用__ されます。
:::

### パラメータの修正方法の考え方

__それぞれの合法手__ の __現時点__ の __評価値__ と、__目標__ となる __評価値__ の __関係__ は __以下__ の通りです。

* __現状__：合法手 2 の評価値 = 合法手 3 の評価値 > 合法手 1 の評価値
* __目標__：合法手 2 の評価値 > 合法手 1 の評価値、合法手 2 の評価値 > 合法手 3 の評価値

__目標__ の __関係__ のうち、__前者__ の「__合法手 2 の評価値 > 合法手 1 の評価値__」という __関係__ は __達成済__ なので、__後者__ の「__合法手 2 の評価値 > 合法手 3 の評価値__」という __関係__ を __達成__ するように __パラメータ__ を __変更すればよい__ ことが分かります。そこで、__評価値__ を __計算__ する際に __利用__ する __項目__ の、__合法手 2__ と __合法手 3__ の __性質__ を __調べる__ ことにします。

下記は、__合法手 2__ と __合法手 3__ のそれらの __項目__ を __調べた結果__ を __まとめた表__ です。

| 項目 | 合法手 2 | 合法手 3 | 合法手 2 と 3 の比較 |
|:-:|:-:|:-:|:--|
| 「__自 2 敵 0 空 1__」が __1 つ__  | あり | なし | 合法手 2 のみあり | 
| 「__自 1 敵 0 空 2__」の数 |  1 | 2 | 合法手 3 の方が多い |
| 「__自 0 敵 1 空 2__」の数 | 3 | 3 | 数は同じ |

上記から、__以下__ の事が __分かります__。

* 『「__自 2 敵 0 空 1__」が __1 つ__ の場合』の __評価値__（パラメータ）を __増やす__ と、__合法手 2__ の __評価値__ は __増える__ が、__合法手 3__ の __評価値__ は __増えない__
* 『「__自 1 敵 0 空 2__」が __1 つあたり__』の __評価値__ を __増やす__ と、__合法手 3__ の __評価値__ のほうが、__合法手 2__ の __評価値__ より __多く増える__
* 『「__自 0 敵 1 空 2__」が __1 つあたり__』の __評価値__ を __変えた場合__、__合法手 2__ と __合法手 3__ の __評価値__ は、__同じ大きさ__ だけ __増減__ する

従って、__合法手 2__ の __評価値__ が __合法手 3__ の __評価値より__ も __大きくなる__ ようにするためには、以下の __2 種類__ の __方法がある__ 事が分かります。

* 『「__自 2 敵 0 空 1__」が __1 つ__ の場合』の __評価値__ を __増やす__
* 『「__自 1 敵 0 空 2__」が __1 つあたり__ 』の __評価値__ を __減らす__

これで __パラメータ__ の __修正方法__ が __決まりました__ が、__この__ ような __修正方法__ で __本当に大丈夫でしょうか__？何か __忘れていることはないか__、少し考えてみて下さい。

### 目標に複数の条件がある場合の修正方法

__上記__ の __修正方法__ では、__以下__ のような __考え方__ で __パラメータの修正__ を行いました。この __考え方__ には __重大な間違い__ があります。__何が間違っているか__ を少し __考えてみて下さい__。

::: note alert
* __目標__ のうち、「__合法手 2 の評価値 > 合法手 1 の評価値__」は __満たされている__
* __目標__ のうち、「__合法手 2 の評価値 > 合法手 3 の評価値__」は __満たされていない__
* 従って、「__合法手 2 の評価値 > 合法手 3 の評価値__」が __満たされるよう__ に __パラメータ__ を __修正すればよい__
:::

__上記__ の考え方の __間違い__ は、__上記__ の __考え方__ に従って __パラメータ__ を __修正__ した __結果__ 、__現状__ で __満たされている__「__合法手 2 の評価値 > 合法手 1 の評価値__」という __条件__ が __満たされなくなる__ 可能性を __考慮__ に __入れていない__ 点です。

ピンとこない人がいるかもしれないので、__上記__ に __類似する__ 簡単な __具体例__ を挙げます。

__テレビの音__ が __小さすぎて__ よく __聞こえない__ ので、__適切な音量__ に __調整__ する場合のことを考えてみて下さい。この例では、__テレビの音量__ を __0 ~ 50__ までの __整数__ で表現し、__数字__ が __大きい程__、__音量__ が __大きくなる__ ものとします。この場合の __パラメータ__ は、__テレビの音量__ です。

まず、__目標__ を __設定__ するために、__適切な音量__ を __定義__ する __必要__ があります。ここでは、__音__ が __良く聞こえる音量__ を __20 以上__ とすることにします。また、__音量__ が __大きすぎる__ と __うるさい__ ので、__うるさい__ と感じられる __音量__ を __30 以上__ とすることにします。

__現状__ の __音量__ を __10__ とした場合、__現状__ と __目標__ は以下のようになります。

* __現状__：音量 = 10
* __目標__：音量 >= 20、音量 < 30

__先程__ と __同じ考え方__ で __パラメータ__ を __修正する方法__ を __考える__ と以下のようになります。

* __現状__ の __音量 10__ は、「__音量 >= 20__」は __満たしていない__ が、「<strong>音量 < 30</strong>」は <strong>満たす</strong>
* 従って、「<strong>音量 >= 20</strong>」を __満たす__ ように __パラメータ__ である __音量__ を __修正__ すれば良い
* 従って、__修正方法__ は、__パラメータ__ である、__音量__ を __10 以上増やす__ ことである

__音量__ を __20 以上増やす__ という __パラメータ__ の __修正__ は、__上記__ の __修正方法__ の __条件__ を __満たします__ が、__音量__ が __30 以上__ になり、「<strong>音量 < 30</strong>」が __満たされなくなる__ ため、__目的__ を __達成__ することが __できません__。従って、__上記__ の __修正方法__ は __間違っています__。

上記の事から、下記の事がわかります。

:::note warn
__目標__ に __複数の条件__ がある場合は、__現状__ で __条件__ のうちのいくつかが __満たされているか__ どうかに __関係なく__、__すべての条件__ を __満たす__ ような __パラメータ__ の __修正方法__ を __考える必要__ がある。
:::

### 複数の条件を満たすパラメータを見つける手順

__上記__ の事から、下記の __両方の条件__ を満たす __パラメータ__ を __見つける__ ことで、__目的__ を __達成できる__ ことがわかりました。このうち、「__合法手 2 の評価値 > 合法手 3 の評価値__」を __達成__ する __パラメータ__ の __修正方法__ は __先程見つけた__ ので、__同時__ に __もう一つ__ の「__合法手 2 の評価値 > 合法手 1 の評価値__」を __達成__ する __パラメータ__ の __条件__ を __見つける必要__ があります。

* 合法手 2 の評価値 > 合法手 1 の評価値
* 合法手 2 の評価値 > 合法手 3 の評価値

__その際__ に、先程「__合法手 2 の評価値 > 合法手 3 の評価値__」を満たす __パラメータ__ を __みつけた手順__ と __同じ方法__ で __見つける必要__ があると __思う人__ がいるかもしれませんが、その __条件を見つける__ のは __大変__ です。__もっと簡単な方法__ があるので、それが何かを考えてみて下さい。

__目的__ を __達成するため__ の __複数の条件__ を __すべて満たす__ ような __パラメータの条件__ は、__下記の手順__ で __求める__ ことができます。

:::note warn
1. 最初は、__パラメータの条件__ を「__条件なし__」と設定する
2. __目標__ を __達成するため__ の __複数の条件__ のうち __検討していない条件__ を __1 つ選択__ する
3. __パラメータの条件__ の __範囲__ で、__選択__ した __条件__ を満たす __条件__ を求め、__求めた条件__ で、__パラメータの条件__ を __更新__ する
4. __検討していない条件__ が __残っている場合__ は __手順 2__ に __戻る__
5. __パラメータの条件__ が __求める条件__ である
:::

分かりづらいと思いますので、具体例と図で説明します。

__目標__ を __達成するため__ の __条件__ として、「__条件 1__」、「__条件 2__」、「__条件 3__」があるとき、__すべての条件__ を満たす __パラメータの条件__ は __下記の手順__ で __求める__ ことが __できます__。

1. __パラメータの条件__ を「__条件なし__」と設定する（一番上の図）
2. __その条件の範囲__ で、__条件 1__ を満たす __条件__ を __計算__ する
3. __パラメータの条件__ を __計算した条件__ で __更新__ する（2 番目の図）
4. __その条件の範囲__ で、__条件 2__ を満たす __条件__ を __計算__ する
5. __パラメータの条件__ を __計算した条件__ で __更新__ する（3 番目の図）
6. __その条件の範囲__ で、__条件 3__ を満たす __条件__ を __計算__ する
7. __パラメータの条件__ を __計算した条件__ で __更新__ する（4 番目の図）。これが、__すべての条件__ を満たす __パラメータの条件__ である

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/68188ad7-9a17-39d3-04ff-66ea632bf7b6.png" width="350">

__上記の手順__ の方が、__簡単である__ ことが __ピンとこない__ 人が __いるかもしれない__ ので、別の __具体例__ を挙げて __説明__ します。たとえば、__100 人の生徒__ の中から、__以下__ の __条件__ を __すべて満たす生徒__ を __見つける__ という __問題__ を考えてみて下さい。

* __国語__ の点数が __50 点以上__
* __英語__ の点数が __50 点以上__
* __数学__ の点数が __50 点以上__

__個別の条件__ を それぞれ __満たす生徒__ を __探し__、__探した生徒__ の __中から__、__すべての条件__ を満たす __生徒を探す__ という __方法__ でこの問 __題を解く__ 場合は、__以下__ の __手順__ で __作業__ を __行います__。

__この手順__ では、__100 人__ の __中から__ 特定の __条件を満たす生徒__ を __探す__ という __作業__ を __3 回行う必要__ があります。また、__その後__ で、__その中から__ すべての条件を満たす __生徒を探す__ という __作業__ を __行う必要__ があります。

1. __100 人__ の __中から__ 国語の点数が 50 点以上の __生徒を探す__
2. __100 人__ の __中から__ 英語の点数が 50 点以上の __生徒を探す__
3. __100 人__ の __中から__ 数学の点数が 50 点以上の __生徒を探す__
4. 手順 1、2、3 で探した中で、__すべてに含まれる生徒__ を __探す__

一方、__先程__ の __手順__ で __探す__ 場合は、__以下の手順__ で __作業__ を __行います__。__こちらの手順__ では、__作業__ を __行うたび__ に、特定の条件を満たす __生徒を探す際__ の、__生徒の数__ が __減っていく__ ため、__作業__ が __やりやすく__ なります。また、__最後__ に __上記の手順 4__ を __行う必要__ は __ありません__。

1. __100 人__ の __中から__ 国語の点数が 50 点以上の __生徒を探す__
2. 手順 1 で X 人が見つかった場合、__X 人__ の __中から__ 英語の点数が 50 点以上の __生徒を探す__
3. 手順 2 で Y 人が見つかった場合、__Y 人__ の __中から__ 数学の点数が 50 点以上の __生徒を探す__

__同様__ の __問題を解く際__ には、おそらく、__多くの方__ が、無意識のうちに __後者の手順__ で __作業を行っている__ のではないでしょうか。

### すべての条件を満たすパラメータの計算

__先程の手順__ に従って、「__合法手 2 の評価値 > 合法手 3 の評価値__」と「__合法手 2 の評価値 > 合法手 1 の評価値__」の両方の条件を満たす __パラメータ__ の __修正方法__ を __求めます__。

下記は、__先程検討__ した「__合法手 2 の評価値 > 合法手 3 の評価値__」を満たす、パラメータの __2 種類__ の __修正方法__ です。

1. 『「__自 2 敵 0 空 1__」が __1 つ__ の場合』の __評価値__ を __増やす__
2. 『「__自 1 敵 0 空 2__」が __1 つあたり__ 』の __評価値__ を __減らす__

__先程の手順__ から、この __条件__ を __満たした上__ で、「__合法手 2 の評価値 > 合法手 1 の評価値__」を満たす __パラメータ__ の __修正方法__ を考える必要があるので、__この条件__ を __満たした場合__ の、__合法手 2 の評価値__ と __合法手 1 の評価値__ の __関係__ を __調べます__。

この __2 つ__ の __修正方法__ に関して、__合法手 1__ と __2__ の __関係__ は以下のようになっています。

* __合法手 1__ と __合法手 2__ は、__どちらも__「__自 2 敵 0 空 1__」が __1 つ__ である
* __合法手 1__ と __合法手 2__ は、__どちらも__「__自 1 敵 0 空 2__」の __数が同じ__ である

従って、__上記__ の __どちらの修正方法__ で __修正__ を行っても、__合法手 1__ と __合法手 2__ の __評価値__ は __同じだけ増減する__ ことが __分かります__。__修正前__ の __合法手 1__ の評価値は __`-2`__ で、__修正前__ の __合法手 2__ の評価値である __`-1`__ より __小さい__ ので、__どちらの修正方法__ で __修正__ を行っても、__合法手 2__ の評価値の方が、__合法手 1__ の評価値より __常に大きくなる__ ことが __分かりました__。

__上記__ から、__先程__ 求めた「__合法手 2 の評価値 > 合法手 3 の評価値__」の __条件__ を __満たすため__ の __修正方法__ によって、「__合法手 2 の評価値 > 合法手 1 の評価値__」の __条件__ も __同時に満たされる__ ことが分かります。従って、__目的__ を __満たす__ ための __修正方法__ は __以下のいずれか__ です。

1. 『「__自 2 敵 0 空 1__」が __1 つ__ の場合』の __評価値__ を __増やす__
2. 『「__自 1 敵 0 空 2__」が __1 つあたり__ 』の __評価値__ を __減らす__

### 2 つ目の条件に対するパラメータの条件を求める場合の具体例

__上記__ では、__複数の条件__ のうちの、__一つ目__ の __条件__ を __満たすための修正__ によって、__残りの条件__ が __満たされました__ が、__一般的__ には __そのようなこと__ は __おきません__。そこで、__そのようなことが起きない__ 場合の __具体例__ を __紹介__ することにします。

なお、__下記__ は、__説明のため__ に __作った__、__実際__ の __〇×ゲーム__ では __おきない__、__架空の例__ です。具体的には、 __下記の表__ のように、__4 手目__ の局面で、__合法手 1__ を着手した場合の「__自 1 敵 0 他 2__」の __数__ が 1 ではなく、__3__ だった場合に、__先程__ と __同様__ の、__下記__ の __条件__ を __満たす__ ような __パラメータ__ の __修正方法__ を __求める手順__ を説明します。

* 合法手 2 の評価値 > 合法手 1 の評価値
* 合法手 2 の評価値 > 合法手 3 の評価値

| |  | 「201」 | 「021」 | 「102」 | 「012」 |評価値 |
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| | __評価値__ | 1：`+1`<br>2~：`100` | `-100` | 1 つで<br>`+1` | 1 つで<br>`-1` | |
| 1 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/3d1fdd6a-2a1d-06e2-1ff7-f9bb9aae2216.png" width="140"> | 1 | | 1 → __3__ | 4 | -2 → __0__ |
| 2 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/2a606113-076e-2b87-8268-a76e4bfd89b7.png" width="140"> | 1 | | 1 | 3 | -1 |
| 3 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/5f16999c-609b-1748-91af-2ff41108eefe.png" width="140"> |  | | 2 | 3  | -1 |

#### 「__合法手 2 の評価値 > 合法手 3 の評価値__」を満たす修正方法

__上記の表__ では、__合法手 2__ と __合法手 3__ の __部分__ は __変わっていない__ ので、「__合法手 2 の評価値 > 合法手 3 の評価値__」の __条件__ を満たす __パラメータ__ の __修正方法__ は、__先程と同様__ に、__下記__ の __いずれか__ になります。

1. 『「__自 2 敵 0 空 1__」が __1 つ__ の場合』の __評価値__ を __増やす__
2. 『「__自 1 敵 0 空 2__」が __1 つあたり__ 』の __評価値__ を __減らす__

#### 「__合法手 2 の評価値 > 合法手 1 の評価値__」を満たす修正方法

次に、上記の __条件__ を __満たした上__ で、「__合法手 2 の評価値 > 合法手 1 の評価値__」を満たす __パラメータ__ の __修正方法__ を考える必要があります。

まず、上記の __1 つ目__ の __修正方法__ によって、__合法手 1__ と __合法手 2__ の __評価値__ が __どのように変化__ するかを __考察__ します。__以下の点__ から、上記の __1 つ目__ の __修正方法__ では、「__合法手 2 の評価値 > 合法手 1 の評価値__」の __条件__ を __満たす__ ことは __できない__ ことが __分かります__。

* __合法手 1__ と __2__ では、__どちらも__「__自 2 敵 0 空 1__」の数が __1 つ__ である
* 従って、__上記__ の __1 つ目__ の __修正方法__ を __適用__ しても、__合法手 1__ と __2__ の __評価値__ は __同じだけ増減__ する
* __現状__ では、__合法手 1__ の __評価値__ は __0__ であり、__合法手 2__ の __評価__ 値である __-1__ よりも __大きい__

次に、__もう一つ__ の __修正方法__ について __考察__ します。__合法手 1__ のほうが、__合法手 2__ よりも「__自 1 敵 0 空 2__」の __数が多い__ ので、「__自 1 敵 0 空 2__」が __1 つあたり__ の __評価値__ を __減らす__ ことで、__合法手 1__ の __評価値__ の方が、__合法手 2__ の __評価値より__ も __多く減る__ ことになります。

しかし、「__自 1 敵 0 空 2__」が __1 つあたり__ の __評価値__ を __減らしたから__ と言って、__必ずしも__「__合法手 2 の評価値 > 合法手 1 の評価値__」の __条件を満たす__ ことが __できるわけ__ では __ありません__。そのため、__その条件__ を __満たすため__ の __パラメータの条件__ を __調べる必要__ があります。

下図は、「__自 1 敵 0 空 2__」が __1 つあたり__ の __評価値__ と、__合法手 1__ と __2__ の __評価値__ の __関係__ を表す __グラフ__ です。__グラフ__ から、「__合法手 2 の評価値 > 合法手 1 の評価値__」の __条件__ を __満たすため__ には、__評価値__ の __パラメータ__ を __0.5 未満__ にする __必要がある__ ことが __わかりました__。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/b98eb42f-f253-ec1d-9bc1-48c2b6366215.png" width="300">

従って、__下記の条件__ を __満たす__ ための __パラメータ__ の __修正方法__ は、『「__自 1 敵 0 空 2__」が __1 つあたり__ の __評価値__ を __0.5 未__ 満にする』ということが __わかりました__。

* 合法手 2 の評価値 > 合法手 1 の評価値
* 合法手 2 の評価値 > 合法手 3 の評価値

### パラメータの調整のさらなる注意点 その 1

話を、__4 手目__ で、__下記の条件__ を満たす __パラメータ__ の __修正方法__ に __戻します__。

* 合法手 2 の評価値 > 合法手 1 の評価値
* 合法手 2 の評価値 > 合法手 3 の評価値

先程、__この条件__ を __満たす__ ための __パラメータ__ の __修正方法__ として、__下記__ の __いずれか__ の __修正__ を __行えば良い__ と示しましたが、__この条件だけ__ で __本当に十分でしょうか__？__何か忘れている__ ことはないか、少し考えてみて下さい。

1. 『「__自 2 敵 0 空 1__」が __1 つ__ の場合』の __評価値__ を __増やす__
2. 『「__自 1 敵 0 空 2__」が __1 つあたり__ 』の __評価値__ を __減らす__

__ルール 11__ の「__自 1 敵 0 空 2__」に関する __下記の説明__ を思い出してください。

「__自分__ が __有利になる__ ように、「__自 1 敵 0 空 2__」が __最も多い__ 着手を行う」

__この条件__ を __満たす__ ような __着手__ を __選択する__ ためには、『「__自 1 敵 0 空 2__」が __1 つあたり__ 』の __評価値__ を __正の値__ にする __必要__ があります。しかし、上記の __2 つ目__ の __修正方法__ は、__そのこと__ に __言及していない__ ので、『「__自 1 敵 0 空 2__」が __1 つあたり__ 』の __評価値__ を __負の値__ にしても __かまわない__ ことになります。__実際__ に、この __パラメータ__ を __負の値__ に __修正__ することで、__合法手 2__ の __評価値のみ__ が __最も高くなる__ という __目的__ は __達成できます__ が、__ルール__ の __条件__ の __意図__ に __反する__ ような __修正__ を __行ってはいけない__ 点に __注意が必要__ です。

なお、__1 つ目__ の __修正方法__ にも __同じこと__ が __言えます__ が、__1__ を __増やした場合__ は、__必ず正の値になる__ ので、__そのまま__ でも __問題__ は __ありません__。

従って、__正しい修正方法__ は __以下__ のようになります。なお、__1 つ目__ の __元の修正方法__ の「__増やす__」という __表現__ は、__あいまい__ なので、下記のように __具体的な表現__ に __修正__ しました。

1. 『「__自 2 敵 0 空 1__」が __1 つ__ の場合』の __評価値__ を __1 より大きな値__ にする
2. 『「__自 1 敵 0 空 2__」が __1 つあたり__ 』の __評価値__ を __1 未満__ の __正の値__ にする

実は、上記の修正を行っても、まだ __問題__ が __残っている__ ので、少し考えてみて下さい。

### パラメータの調整のさらなる注意点 その 2

__上記の条件__ の __問題点__ は、__ルール 11__ の、より __優先順位__ が __高い条件__ を __考慮していない__ という点です。例えば、『「__自 2 敵 0 空 1__」が __1 つ__ の場合』の __評価値__ を __1__ から __10000__ に __変更__ するという __修正方法__ は、__上記__ の __1 つ目の修正方法__ の __条件__ を __満たします__。下記は、__そのように修正__ した場合の __表__ です

| |  | 「201」 | 「021」 | 「102」 | 「012」 |評価値 |
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| | __評価値__ | 1：`+10000`<br>2~：`100` | `-100` | 1 つで<br>`+1` | 1 つで<br>`-1` | |
| 1 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/3d1fdd6a-2a1d-06e2-1ff7-f9bb9aae2216.png" width="140"> | 1 | | 1 | 4 | 9997 |
| 2 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/2a606113-076e-2b87-8268-a76e4bfd89b7.png" width="140"> | 1 | | 1 | 3 | 9998 |
| 3 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/5f16999c-609b-1748-91af-2ff41108eefe.png" width="140"> |  | | 2 | 3  | -1 |

表から、確かに __合法手 2__ の __評価値のみ__ が __最も大きい 9998__ になることが分かりますが、この __評価値__ は、より __優先順位__ が __高い__ はずの、__自分が勝利した局面__ の __評価値__ である __200__ や、__次__ の __自分の手番__ で __必ず__ 自分が __勝利できる局面__ の __評価値__ である __100__  よりも __大きく__ なってしまいます。4 手目の局面には、そのような合法手は存在しませんが、__他の局面__ で __そのような合法手__ が __存在__ した場合は、__優先順位__ が __低い条件__ を満たす __合法手__ が、__優先順位__ が __高い条件__ の __合法手より__ も __優先__ して __選択される__ ことになってしまいます。

__このようなこと__ が __起きないようにする__ ためには、__評価値__ の __計算結果__ が、__優先順位__ が __高い条件__ の __合法手より__ も __小さくなる__ ように __パラメータ__ を __修正する必要__ があります。

__問題__ が __発生しない__ ことを __確認__ するためには、[以前の記事](https://qiita.com/ysgeso/items/c1285c93cbfbdbdc7b77#%E8%A9%95%E4%BE%A1%E5%80%A4%E3%81%AE%E8%A8%AD%E5%AE%9A-1)で作成した、__優先順位__ ごとの __評価値__ の __表__ を __作成すると良い__ でしょう。下記は、『「__自 2 敵 0 空 1__」が __1 つ__ の場合』の __評価値__ を __1__ から __2__ に __修正__ した場合の __表__ で、__問題__ が __発生しない__ ことが __確認__ できます。

| 順位 | 局面の状況 | 個別 | 評価値 | 
|:-:|:--|:-:|:-:|
| 1 | __真ん中のマスに着手している__ | | `300` |
| 2 | __自分が勝利している__  | | `200` |
| 4 | __「自 2 敵 0 空 1」が 2 つ以上存在する__  | | `100` |
| 5 | __「自 2 敵 0 空 1」が 1 つ存在する__<br>__「自 1 敵 0 空 2」が x 個存在する__<br>__「自 0 敵 1 空 2」が y 個存在する__| `2` (`0～2`)<br>`x` (`0～8`)<br>`-y` (`-8~0`) | `-8～10`|
| 3 | __相手が勝利できる__  | | `-100` |

__念のため__ に、__上記__ の __修正__ を行った __場合__ のそれぞれの __合法手__ の __表__ も __作ってみる__ ことにします。__下記の表__ から、__合法手 2__ の __評価値のみ__ が、__最も高くなる__ ことが __確認__ できます。

| |  | 「201」 | 「021」 | 「102」 | 「012」 |評価値 |
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| | __評価値__ | 1：`+2`<br>2~：`100` | `-100` | 1 つで<br>`+1` | 1 つで<br>`-1` | |
| 1 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/3d1fdd6a-2a1d-06e2-1ff7-f9bb9aae2216.png" width="140"> | 1 | | 1 | 4 | -1 |
| 2 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/2a606113-076e-2b87-8268-a76e4bfd89b7.png" width="140"> | 1 | | 1 | 3 | 0 |
| 3 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/5f16999c-609b-1748-91af-2ff41108eefe.png" width="140"> |  | | 2 | 3  | -1 |

:::note
__パラメータ__ の __修正を行う際__ に、__元の値__ から __大きく変更する__ ことはあまり __お勧めできません__。その理由は、__大きく変更__ すると、上記の例のように、__別の所__ で __何らか__ の __悪影響__ を __及ぼす可能性__ が __高くなる__ からです。

__現実の場合__ でも、__テレビの音__ が __小さい場合__ に、__少しずつ__ 音量を __上げていく__ のが __一般的__ ではないでしょうか。__一気__ に音量を __上げてしまう__ と音が __大きくなりすぎて__、周りに迷惑がかかるなどの __別の問題__ が __発生する可能性__ が __高くなります__。
:::

この時点で、かなり __うんざりした人__ が __多いかもしれません__ が、__残念ながら__ このような __細かい点__ を __考慮せず__ に __パラメータ__ を __修正してしまう__ と、__AI__ が __意図通り__ に __着手__ を __行わなくなってしまう__ ので、__避けて通る__ ことは __できません__。これまでの記事で、__何度も__ 何度も __表を書いて確認__ してきたのはそのような __間違いを避けるため__ です。

:::note warn
__ある局面__ に対して、__何らかの目的__ を __達成__ するために __パラメータを修正__ する場合は、__修正したパラメータ__ が、__その局面__ における、__ルール__ の __すべての条件__ を __満たす__ ことを __確認する必要__ がある。
:::

## 目的を達成できない場合

__目的__ によっては、どのように __パラメータ__ を __修正しても__ 、その __目的__ を __達成__ することが __できない場合__ が __あります__。そのような場合は、パラメータではなく、__ルール__ の __条件__ の方を __修正__ する __必要__ が __あります__。具体例については今後の記事で紹介します。

また __ルール__ の __条件を修正__ しても __目的__ が __達成できない__ ような __場合__ もあります。そのような場合は、例えば、絶対に勝てない局面で勝利を目指すというような、__目的__ の __設定__ そのものに __無理がある__ 可能性が高く、__目的__ のほうを __修正__ する __必要__ が __あります__。

# `ai11s` の評価値の計算方法の修正

先程、__問題がない__ ことを __確認__ した、『「__自 2 敵 0 空 1__」が __1 つ__ の場合』の __評価値__ を __1__ から __2__ に __修正__ するという方法で __`ai11s`__ を __修正__ することにします。そのように __パラメータ__ を __修正する__ ためには、[以前の記事](https://qiita.com/ysgeso/items/c1285c93cbfbdbdc7b77#%E8%A9%95%E4%BE%A1%E5%80%A4%E3%81%AE%E8%AA%BF%E6%95%B4)で __評価値の調整__ を行った際と __同様__ に、__`ai11s`__ の下記のプログラムの __3 行目__ の __`score += 1`__ で、__評価値__ に __加算する数値__ を __`1`__ から __`2`__  に __変更すればよい__ のですが、今後、このように __パラメータ__ を __変更する必要__ が生じた場合に、__`ai11s`__ のプログラムを __毎回修正する__ のは __大変__ です。

```python
        # 次の自分の手番で自分が勝利できる場合は評価値に 1 を加算する
        if markpats[Markpat(last_turn=2, turn=0, empty=1)] == 1:
            score += 1
```

そこで、__`ai11s`__ の __仮引数__ に、__評価値__ を __計算__ する際に __利用__ する __パラメータ__ を __代入する__ ように __修正__ することで、__`ai11s`__ の __プログラム__ を __修正しなくても__、__実引数__ に __記述__ する __パラメータ__ を __変えるだけ__ で、__評価値__ の __計算方法__ を __変える__ ことが __できるように__ します。

これらの __パラメータ__ を、__仮引数__ に __代入する__ ためには、それぞれの __仮引数__ の __名前__ を __考える必要__ があるので、__仮引数__ の __名前__ を、__下記の表__ のように __名づける__ ことにします。


|  | 仮引数の名前 |
|:-:|:-:|
| 「__自 2 敵 0 空 1__」が __1 つ__ の場合  | `score_201`  |
| 「__自 1 敵 0 空 2__」が __1 つあたり__ | `score_102`  |
| 「__自 0 敵 1 空 2__」が __1 つあたり__ | `score_012`  |

なお、他の __`100` など__ の __評価値__ も __パラメータ__ の __一種__ なので、__それら__ を __仮引数__ に __代入__ して __変更できるよう__ にすることもできます。ただし、__仮引数__ を __多くする__ と __分かりづらくなる__ ので、__今回の記事__ では __上記__ の __3 つ__ の __パラメータのみ__ を __変更できる__ ようにします。

## `ai11s` の修正

下記は、__上記__ の __仮引数__ を __追加__ し、その __仮引数__ を使って __評価値を計算する__ ように __`ai11s`__ を __修正__ したプログラムです。__デフォルト引数__ は、__通常の仮引数より__ も __後に記述__ する __必要がある__ ので、__新しい仮引数__ は __`debug=False` より__ も __前に記述__ する __必要__ があります。

* __5 行目__：__パラメータ__ を __代入__ する __仮引数__ を __追加__ する
* __11 行目__：「__自 2 敵 0 空 1__」が __1 つ__ の場合に、__評価値__ に __`score_201`__ を __加算する__ ように __修正__ する
* __13 行目__：__評価値__ に『「__自 1 敵 0 空 2__」の数 × __`score_102`__』を __加算__ するように __修正__
* __15 行目__：__評価値__ に『「__自 0 敵 1 空 2__」の数 × __`score_012`__』を __加算__ するように __修正__。なお、__元__ は __`score -= 1`__ であったが、__`score_102`__ には __負の値__ を __代入__ することを __想定している__ ので、__修正後__ は __`score += 式`__ に __変更__ する __必要__ がある点に __注意__ すること


```python
 1  from pprint import pprint
 2  from ai import ai_by_score
 3  from marubatsu import Markpat
 4
 5  def ai11s(mb, score_201, score_102, score_012, debug=False):
 6      def eval_func(mb):      
元と同じなので省略
 7          # 評価値の合計を計算する変数を 0 で初期化する
 8          score = 0        
 9          # 次の自分の手番で自分が勝利できる場合は評価値に score_201 を加算する
10          if markpats[Markpat(last_turn=2, turn=0, empty=1)] == 1:
11              score += score_201
12          # 「自 1 敵 0 空 2」1 つあたり score_102 だけ、評価値を加算する
13          score += markpats[Markpat(last_turn=1, turn=0, empty=2)] * score_102
14          # 「自 0 敵 1 空 2」1 つあたり score_201 だけ、評価値を減算する
15          score += markpats[Markpat(last_turn=0, turn=1, empty=2)] * score_012
16        
17          # 計算した評価値を返す
18          return score
19
20      return ai_by_score(mb, eval_func, debug=debug)
```

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

```python
from pprint import pprint
from ai import ai_by_score
from marubatsu import Markpat

def ai11s(mb, score_201, score_102, score_012, debug=False):
    def eval_func(mb):      
        # 真ん中のマスに着手している場合は、評価値として 300 を返す
        if mb.last_move == (1, 1):
            return 300
    
        # 自分が勝利している場合は、評価値として 200 を返す
        if mb.status == mb.last_turn:
            return 200

        markpats = mb.count_markpats()
        if debug:
            pprint(markpats)
        # 相手が勝利できる場合は評価値として -100 を返す
        if markpats[Markpat(last_turn=0, turn=2, empty=1)] > 0:
            return -100
        # 次の自分の手番で自分が必ず勝利できる場合は評価値として 100 を返す
        elif markpats[Markpat(last_turn=2, turn=0, empty=1)] >= 2:
            return 100

        # 評価値の合計を計算する変数を 0 で初期化する
        score = 0        
        # 次の自分の手番で自分が勝利できる場合は評価値に score_201 を加算する
        if markpats[Markpat(last_turn=2, turn=0, empty=1)] == 1:
            score += score_201
        # 「自 1 敵 0 空 2」1 つあたり score_102 だけ、評価値を加算する
        score += markpats[Markpat(last_turn=1, turn=0, empty=2)] * score_102
        # 「自 0 敵 1 空 2」1 つあたり score_201 だけ、評価値を減算する
        score += markpats[Markpat(last_turn=0, turn=1, empty=2)] * score_012
        
        # 計算した評価値を返す
        return score

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

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

```diff_python
from pprint import pprint
from ai import ai_by_score
from marubatsu import Markpat

-def ai11s(mb, debug=False):
+def ai11s(mb, score_201, score_102, score_012, debug=False):
    def eval_func(mb):      
元と同じなので省略
        # 評価値の合計を計算する変数を 0 で初期化する
        score = 0        
        # 次の自分の手番で自分が勝利できる場合は評価値に score_201 を加算する
        if markpats[Markpat(last_turn=2, turn=0, empty=1)] == 1:
-           score += 1
+           score += score_201
        # 「自 1 敵 0 空 2」1 つあたり score_102 だけ、評価値を加算する
-       score += markpats[Markpat(last_turn=1, turn=0, empty=2)]
+       score += markpats[Markpat(last_turn=1, turn=0, empty=2)] * score_102
        # 「自 0 敵 1 空 2」1 つあたり score_201 だけ、評価値を減算する
-       score -= markpats[Markpat(last_turn=0, turn=1, empty=2)]
+       score += markpats[Markpat(last_turn=0, turn=1, empty=2)] * score_012
        
        # 計算した評価値を返す
        return score

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

## 動作の確認

__修正__ した __`ai11s`__ が __正しく動作__ するかを __確認__ します。

まず、下記のプログラムのように、__元__ の __計算方法__ で __評価値__ を __計算する__ ように、__実引数__ に __パラメータ__ を __記述__ し、__`ai10s`__ VS __`ai11s`__ の __4 手目__ の __局面__ で __`ai11s`__ が __どのように着手__ を __選択__ するかを __表示__ します。なお、__3 つ__ の __パラメータ__ を __位置引数__ で __記述__ すると、それぞれの __意味がわかりづらくなる__ ので、__キーワード引数__ で __記述__ しました。

```python
mb = Marubatsu()
mb.move(1, 1)
mb.move(0, 0)
mb.move(2, 2)
ai11s(mb, score_201=1, score_102=1, score_012=-1, debug=True)
```

<details><summary><strong>実行結果（長いのでクリックして開いてください）</strong></summary>

```
Start ai_by_score
Turn x
x..
.o.
..O

legal_moves [(1, 0), (2, 0), (0, 1), (2, 1), (0, 2), (1, 2)]
====================
move (1, 0)
Turn o
xX.
.o.
..o

defaultdict(<class 'int'>,
            {Markpat(last_turn=0, turn=1, empty=2): 4,
             Markpat(last_turn=1, turn=0, empty=2): 1,
             Markpat(last_turn=1, turn=1, empty=1): 1,
             Markpat(last_turn=1, turn=2, empty=0): 1,
             Markpat(last_turn=2, turn=0, empty=1): 1})
score -2 best score -inf
UPDATE
  best score -2
  best moves [(1, 0)]
====================
move (2, 0)
Turn o
x.X
.o.
..o

defaultdict(<class 'int'>,
            {Markpat(last_turn=0, turn=1, empty=2): 3,
             Markpat(last_turn=1, turn=0, empty=2): 1,
             Markpat(last_turn=1, turn=1, empty=1): 2,
             Markpat(last_turn=1, turn=2, empty=0): 1,
             Markpat(last_turn=2, turn=0, empty=1): 1})
score -1 best score -2
UPDATE
  best score -1
  best moves [(2, 0)]
====================
move (0, 1)
Turn o
x..
Xo.
..o

defaultdict(<class 'int'>,
            {Markpat(last_turn=0, turn=1, empty=2): 4,
             Markpat(last_turn=1, turn=0, empty=2): 1,
             Markpat(last_turn=1, turn=1, empty=1): 1,
             Markpat(last_turn=1, turn=2, empty=0): 1,
             Markpat(last_turn=2, turn=0, empty=1): 1})
score -2 best score -1
====================
move (2, 1)
Turn o
x..
.oX
..o

defaultdict(<class 'int'>,
            {Markpat(last_turn=0, turn=1, empty=2): 3,
             Markpat(last_turn=1, turn=0, empty=2): 2,
             Markpat(last_turn=1, turn=1, empty=1): 2,
             Markpat(last_turn=1, turn=2, empty=0): 1})
score -1 best score -1
APPEND
  best moves [(2, 0), (2, 1)]
====================
move (0, 2)
Turn o
x..
.o.
X.o

defaultdict(<class 'int'>,
            {Markpat(last_turn=0, turn=1, empty=2): 3,
             Markpat(last_turn=1, turn=0, empty=2): 1,
             Markpat(last_turn=1, turn=1, empty=1): 2,
             Markpat(last_turn=1, turn=2, empty=0): 1,
             Markpat(last_turn=2, turn=0, empty=1): 1})
score -1 best score -1
APPEND
  best moves [(2, 0), (2, 1), (0, 2)]
====================
move (1, 2)
Turn o
x..
.o.
.Xo

defaultdict(<class 'int'>,
            {Markpat(last_turn=0, turn=1, empty=2): 3,
             Markpat(last_turn=1, turn=0, empty=2): 2,
             Markpat(last_turn=1, turn=1, empty=1): 2,
             Markpat(last_turn=1, turn=2, empty=0): 1})
score -1 best score -1
APPEND
  best moves [(2, 0), (2, 1), (0, 2), (1, 2)]
====================
Finished
best score -1
best moves [(2, 0), (2, 1), (0, 2), (1, 2)]
```
</details>

__実行結果__ には、__修正前__ の __`ai11s`__ と __同じ内容__ が __表示__ されます。また、下記の __実行結果__ の __最後の 2 行__ から __修正前__ と __同様__ に __合法手 2__ と __3__ の __評価値__ として __-1__ が __計算__ され、__合法手 2__ と __3__ の __中から選択__ が __行われる__ ことが __確認__ できます。

```
best score -1
best moves [(2, 0), (2, 1), (0, 2), (1, 2)]
```

次に、下記のプログラムで、『「__自 2 敵 0 空 1__」が __1 つ__ の場合』の __評価値__ の __パラメータ__ を __2__ に __変更__ した場合に __`ai11s`__ が __どのよう__ に __着手を選択__ するかを __表示__ します。

* __1 行目__：__`score_201=1`__ を __`score_201=2`__ に __修正__ する

```python
ai11s(mb, score_201=2, score_102=1, score_012=-1, debug=True)
```

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

```diff_python
-ai11s(mb, score_201=1, score_102=1, score_012=-1, debug=True)
+ai11s(mb, score_201=2, score_102=1, score_012=-1, debug=True)
```
</details>

<details><summary><strong>実行結果（長いのでクリックして開いてください）</strong></summary>

```
Start ai_by_score
Turn x
x..
.o.
..O

legal_moves [(1, 0), (2, 0), (0, 1), (2, 1), (0, 2), (1, 2)]
====================
move (1, 0)
Turn o
xX.
.o.
..o

defaultdict(<class 'int'>,
            {Markpat(last_turn=0, turn=1, empty=2): 4,
             Markpat(last_turn=1, turn=0, empty=2): 1,
             Markpat(last_turn=1, turn=1, empty=1): 1,
             Markpat(last_turn=1, turn=2, empty=0): 1,
             Markpat(last_turn=2, turn=0, empty=1): 1})
score -1 best score -inf
UPDATE
  best score -1
  best moves [(1, 0)]
====================
move (2, 0)
Turn o
x.X
.o.
..o

defaultdict(<class 'int'>,
            {Markpat(last_turn=0, turn=1, empty=2): 3,
             Markpat(last_turn=1, turn=0, empty=2): 1,
             Markpat(last_turn=1, turn=1, empty=1): 2,
             Markpat(last_turn=1, turn=2, empty=0): 1,
             Markpat(last_turn=2, turn=0, empty=1): 1})
score 0 best score -1
UPDATE
  best score 0
  best moves [(2, 0)]
====================
move (0, 1)
Turn o
x..
Xo.
..o

defaultdict(<class 'int'>,
            {Markpat(last_turn=0, turn=1, empty=2): 4,
             Markpat(last_turn=1, turn=0, empty=2): 1,
             Markpat(last_turn=1, turn=1, empty=1): 1,
             Markpat(last_turn=1, turn=2, empty=0): 1,
             Markpat(last_turn=2, turn=0, empty=1): 1})
score -1 best score 0
====================
move (2, 1)
Turn o
x..
.oX
..o

defaultdict(<class 'int'>,
            {Markpat(last_turn=0, turn=1, empty=2): 3,
             Markpat(last_turn=1, turn=0, empty=2): 2,
             Markpat(last_turn=1, turn=1, empty=1): 2,
             Markpat(last_turn=1, turn=2, empty=0): 1})
score -1 best score 0
====================
move (0, 2)
Turn o
x..
.o.
X.o

defaultdict(<class 'int'>,
            {Markpat(last_turn=0, turn=1, empty=2): 3,
             Markpat(last_turn=1, turn=0, empty=2): 1,
             Markpat(last_turn=1, turn=1, empty=1): 2,
             Markpat(last_turn=1, turn=2, empty=0): 1,
             Markpat(last_turn=2, turn=0, empty=1): 1})
score 0 best score 0
APPEND
  best moves [(2, 0), (0, 2)]
====================
move (1, 2)
Turn o
x..
.o.
.Xo

defaultdict(<class 'int'>,
            {Markpat(last_turn=0, turn=1, empty=2): 3,
             Markpat(last_turn=1, turn=0, empty=2): 2,
             Markpat(last_turn=1, turn=1, empty=1): 2,
             Markpat(last_turn=1, turn=2, empty=0): 1})
score -1 best score 0
====================
Finished
best score 0
best moves [(2, 0), (0, 2)]
```
</details>

__実行結果__ から、__想定通り__ に、__下記の表__ と __同じ計算__ が __行われる__ ことが __確認__ できます。

| |  | 「201」 | 「021」 | 「102」 | 「012」 |評価値 |
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| | __評価値__ | 1：`+2`<br>2~：`100` | `-100` | 1 つで<br>`+1` | 1 つで<br>`-1` | |
| 1 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/3d1fdd6a-2a1d-06e2-1ff7-f9bb9aae2216.png" width="140"> | 1 | | 1 | 4 | -1 |
| 2 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/2a606113-076e-2b87-8268-a76e4bfd89b7.png" width="140"> | 1 | | 1 | 3 | 0 |
| 3 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/5f16999c-609b-1748-91af-2ff41108eefe.png" width="140"> |  | | 2 | 3  | -1 |

下記の __実行結果__ の __最後の 2 行__ から、__合法手 2 のみ__ が __選択される__ ことが __確認__ できます。

```
best score 0
best moves [(2, 0), (0, 2)]
```

## `ai10s` との対戦

次に、__`ai_match`__ を使って、__`ai10s`__ と __対戦__ を行い、__`ai11s`__ が __× を担当__ した場合に、__`ai10s`__ に __負けなくなったか__ どうかを __確認__ します。

### `ai_match` の問題点

その際に、初心者の方は、__下記__ のプログラムのように __記述すれば良い__ と __思うかもしれません__ が、__実行__ すると、__実行結果__ のような __エラーが発生__ します。

```python
ai_match(ai=[ai11s(mb, score_201=2, score_102=1, score_012=-1), ai10s])
```

実行結果

```
略
---> 37     print(f"{ai[0].__name__} VS {ai[1].__name__}")
     39     mb = Marubatsu()
     41     # ai[0] VS ai[1] と ai[1] VS a[0] の対戦を match_num 回行い、通算成績を数える

AttributeError: 'tuple' object has no attribute '__name__'
```

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

* __AttrubuteError__
オブジェクトの属性（attribute）に関するエラー
* __'tuple' object has no attribute '__name__'__
tuple というオブジェクト（object）は、`__name__` という属性（attribute）を持たない（has no）
:::

上記の __エラー__ が __発生する理由__ は以下の通りです。

* __`ai_match`__ の __仮引数 `ai`__ には、__`[ai11s(mb, score_201=2, score_102=1, score_012=-1), ai10s]`__ という __list__ が __代入__ される
* 上記の __エラーが発生__ する __`print(f"{ai[0].__name__} VS {ai[1].__name__}")`__ の __行__ では、__`ai[0]`__ と __`ai[1]`__ に __対する処理__ を行う
* __`ai[0]`__ に __代入__ されるのは、__`ai11s(mb, score_201=2, score_102=1, score_012=-1)`__ という __`ai11s`__ の __関数呼び出し__ の __返り値__ であり、__その値__ は __`ai11s`__ が __選択__ した __着手__ を表す __`(1, 1)`__ のような __tuple__ である
* 従って、__`print(f"{ai[0].__name__} VS {ai[1].__name__}")`__ の __`ai[0].__name__`__ では、__`(1, 1).__name__`__ という、__tuple__ に対する __`__name__` 属性の値__ を __参照する__ という __処理__ が行われる
* __tuple__ には __`__name__`__ という __属性__ は __存在しない__ ので、__上記__ のような __エラーが発生__ する

先程のプログラムの __問題点__ は、__仮引数 `ai`__ に __代入__ する __list の要素__ に、__関数オブジェクト__ を __記述する必要__ があるにも関わらず、__関数呼び出し__ を __記述__ してしまった点にあります。 

### `ai_match` への仮引数の追加

__`ai_match`__ の __問題点__ は、__対戦__ を行う __AI の 処理__ を行う __関数オブジェクト__ を __代入__ する __仮引数__ は __存在するが__、それらの __関数__ を __呼び出す際__ に __記述する実引数__ を __代入__ する __仮引数__ が __存在しない__ 点です。そこで、__`ai_match`__ に、そのような __仮引数を追加__ することにします。

まず、__仮引数__ に __代入__ する __データ__ の __データ型__ と、__名前__ を __決める必要__ があります。__`ai_match`__ は、__2 種類__ の __AI__ の __関数を呼び出す__ 処理を行うので、__それぞれの AI__ ごとに、__個別__ の __パラメータのデータ__ を __記述__ する __必要__ があります。そのため、__仮引数__ に __代入__ するデータの __データ型__ は __list__[^2]にすればよいでしょう。__list__ の __要素__ に __記述__ する __データ__ は、__AI__ が __評価値__ を __計算する際__ に __利用__ する __パラメータ__ なので、__仮引数__ の __名前__ を __parameters__ の __略__ である __`params`__ にすることにします。

従って、__`ai_match`__ の __定義__ の __1 行目__ を __下記__ のプログラムのように __修正__ します。

```python
def ai_match(ai, params, match_num=10000):
```

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

```diff_python
-def ai_match(ai, match_num=10000):
+def ai_match(ai, params, match_num=10000):
```
</details>

### 仮引数 `params` を利用した関数の呼び出し

次に、この __仮引数 `params`__ に __代入__ する __list の要素__ に __どのようなデータ__ を __記述__ し、__どのように__ して __そのデータ__ を、__AI の関数__ を __呼び出す際__ の __実引数に記述__ するかを __説明__ します。その __方法__ は、初心者の方にはおそらく思いつくことはできないと思いますが、[以前の記事](https://qiita.com/ysgeso/items/474db9ea1805993eaa34#%E3%83%9E%E3%83%83%E3%83%94%E3%83%B3%E3%82%B0%E5%9E%8B%E3%81%AE%E5%B1%95%E9%96%8B)で説明した __マッピング型の展開__ を使って __行う__ ことが __できます__。
 
おそらく忘れてしまった方が多いのではないかと思いますので、__マッピング型の展開__ を __おさらい__ します。__マッピング型の展開__ とは、__dict__ などの __マッピング型__ の __データ__ の __キー__ と __キーの値__ を、__キーワード引数__ に __変換__ したものと __みなして__、__関数呼び出しを行う__ という __処理__ で、__マッピング型__ の __先頭__ に __`**`__ を __記述__ します。例えば、__下記__ のプログラムの __1 行目__ のプログラムは、__マッピング型の展開__ を使って、__2 ~ 7 行目__ のように __記述できます__。

なお、__実行結果__ は、先程実行した、下記の __1 行目__ のプログラムの __実行結果__ が __2 回続けて表示__ されるので、__省略__ します。

```python
1  ai11s(mb, score_201=1, score_102=2, score_012=3)
2  param = { 
3      "score_201": 1,
4      "score_102": 2,
5      "score_012": 3
6  }
7  ai11s(mb, **param)
```

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

```python
ai11s(mb, score_201=1, score_102=2, score_012=3)
param = { 
    "score_201": 1,
    "score_102": 2,
    "score_012": 3
}
ai11s(mb, **param)
```
</details>


従って、__`ai_match`__ の __`params`__ に __代入__ する __list の要素__ には、__それぞれの AI__ の __関数__ を __呼び出す際__ に __実引数に記述__ する __キーワード引数を表す dict__ を __記述__ し、__AI の関数__ を __呼び出す際__ に、__マッピング型の展開__ を __記述すれば良い__ ことが __分かります__。

ただし、__実際__ の __マッピング型の展開__ の __記述__ は、__`play`__ メソッドに __記述する必要__ があります。その理由は、下記のプログラムのように、__AI の関数__ を __呼び出す処理__ は、__`ai_match`__ ではなく、__`play`__ メソッドで __行っている__ からです。

```python
def ai_match(ai, params, match_num=10000):
略
    for _ in range(match_num):
        # ai_match では、play メソッドを呼び出す処理を行っている
        count_list[0][mb.play(ai, verbose=False)] += 1
        count_list[1][mb.play(ai=[ai[1], ai[0]], verbose=False)] += 1
略

def play(self, ai, verbose=True):
略
            if ai[index] is not None:
                # 実際の AI の関数呼び出しは、ここで行われている
                x, y = ai[index](self)
略
```

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

__`play`__ メソッドは __下記__ のプログラムのように __修正__ します。

* __1 行目__：__仮引数 `params`__ を __追加__ する
* __4 行目__：__AI の関数__ を __呼び出す際__ に、__対応__ する __`params`__ の __要素__ の __マッピング型の展開__ を __実引数に記述__ する（局所変数 __`index`__ が __対応する AI__ の __インデックス__ を表す）

```python
1  def play(self, ai, params, verbose=True):
元と同じなので省略
2          # ai が着手を行うかどうかを判定する
3          if ai[index] is not None:
4              x, y = ai[index](self, **params[index])
元と同じなので省略
5  Marubatsu.play = play
```

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

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

    # 決着がついたので、ゲーム盤を表示する
    if verbose:
        print(self)
    return self.status

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

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

```diff_python
-def play(self, ai, verbose=True):
+def play(self, ai, params, verbose=True):
元と同じなので省略
        # ai が着手を行うかどうかを判定する
        if ai[index] is not None:
-           x, y = ai[index](self)
+           x, y = ai[index](self, **params[index])
元と同じなので省略
Marubatsu.play = play
```
</details>


次に、__`ai_match`__ を、下記のプログラムの __4、5 行目__ のように、__`play`__ メソッドに __`params`__ の __実引数__ を __追加__ するように __修正__ します。なお、__元__ の __プログラム__ の __5 行目__ では、__手番__ を __入れ替えて対戦を行う__ 際に、__ai の要素__ の __順番__ を __入れ替える処理__ を __`ai=[ai[0], a[1]]`__ のように記述していましたが、[以前の記事](https://qiita.com/ysgeso/items/6ca248133ff4d59a81d4#%E3%82%B9%E3%83%A9%E3%82%A4%E3%82%B9%E8%A1%A8%E8%A8%98-%E3%81%9D%E3%81%AE-7%E8%B2%A0%E3%81%AE-step-%E3%82%92%E6%8C%87%E5%AE%9A%E3%81%97%E3%81%9F%E5%A0%B4%E5%90%88)で説明したように、__スライス表記__ を __利用する__ ことで、__list__ の __要素の順番__ を __逆__ にした __list__ は、__`ai[::-1]`__ のように __簡潔__ に __記述できる__ ので、今回の記事では __その方法__ で __`ai`__ や __`params`__ の __要素__ を __逆にした list__ を __記述__ しています。

```python
1  from collections import defaultdict
2
3  def ai_match(ai, params, match_num=10000):
元と同じなので省略
4      for _ in range(match_num):
5          count_list[0][mb.play(ai, params, verbose=False)] += 1
6          count_list[1][mb.play(ai=ai[::-1], params=params[::-1], verbose=False)] += 1
元と同じなので省略
```

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

```python
from collections import defaultdict

def ai_match(ai, params, match_num=10000):
    print(f"{ai[0].__name__} VS {ai[1].__name__}")
    
    mb = Marubatsu()

    # ai[0] VS ai[1] と ai[1] VS a[0] の対戦を match_num 回行い、通算成績を数える
    count_list = [ defaultdict(int), defaultdict(int)]
    for _ in range(match_num):
        count_list[0][mb.play(ai, params, verbose=False)] += 1
        count_list[1][mb.play(ai=ai[::-1], params=params[::-1], verbose=False)] += 1

    # ai[0] から見た通算成績を計算する
    count_list_ai0 = [
        # ai[0] VS ai[1] の場合の、ai[0] から見た通算成績
        { 
            "win": count_list[0][Marubatsu.CIRCLE],
            "lose": count_list[0][Marubatsu.CROSS],
            "draw": count_list[0][Marubatsu.DRAW],
        },
        # ai[1] VS ai[0] の場合の、ai[0] から見た通算成績
        { 
            "win": count_list[1][Marubatsu.CROSS],
            "lose": count_list[1][Marubatsu.CIRCLE],
            "draw": count_list[1][Marubatsu.DRAW],
        },
    ]           

    # 両方の対戦の通算成績の合計を計算する
    count_list_ai0.append({})
    for key in count_list_ai0[0]:
        count_list_ai0[2][key] = count_list_ai0[0][key] + count_list_ai0[1][key]

    # それぞれの比率を計算し、ratio_list に代入する
    ratio_list = [ {}, {}, {} ]
    for i in range(3):
        for key in count_list_ai0[i]:
            ratio_list[i][key] = count_list_ai0[i][key] / sum(count_list_ai0[i].values())
            
    # 各行の先頭に表示する文字列のリスト
    item_text_list = [ Marubatsu.CIRCLE, Marubatsu.CROSS, "total" ]    
    
    # 通算成績の回数と比率の表示
    width = max(len(str(match_num * 2)), 7)
    diff_list = [ ("count", count_list_ai0, f"{width}d"),
                  ("ratio", ratio_list, f"{width}.1%") ]
    for title, data, format in diff_list:
        print(title, end="")
        for key in data[0]:
            print(f" {key:>{width}}", end="")
        print()
        for i in range(3):
            print(f"{item_text_list[i]:5}", end="")
            for value in data[i].values():
                print(f" {value:{format}}", end="")
            print()
        print()
```
</details>

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

```diff_python
from collections import defaultdict

-def ai_match(ai, match_num=10000):
+def ai_match(ai, params, match_num=10000):
元と同じなので省略
    for _ in range(match_num):
-       count_list[0][mb.play(ai, verbose=False)] += 1
+       count_list[0][mb.play(ai, params, verbose=False)] += 1
-       count_list[1][mb.play(ai=[ai[1], ai[0]], verbose=False)] += 1
+       count_list[1][mb.play(ai=ai[::-1], params=params[::-1], verbose=False)] += 1
元と同じなので省略
```
</details>


### 修正したパラメータでの `ai10s` との対戦

下記は、__修正後__ の __パラメータ__ で __`ai11s`__ VS __`ai10s`__ の __対戦__ を行うプログラムです。__`ai10s`__ のように、__パラメータ__ が __存在しない__ 場合は、__空の dict__ を __記述__ します。これは、__空の dict__ に対して __マッピング型の展開__ を行った場合は __何も行われない__ からです。

```python
param_ai11s = {
    "score_201": 2,
    "score_102": 1,
    "score_012": -1
}
ai_match(ai=[ai11s, ai10s], params=[param_ai11s, {}])
```

実行結果

```
ai11s VS ai10s
count     win    lose     draw
o           0       0    10000
x           0       0    10000
total       0       0    20000

ratio     win    lose    draw
o        0.0%    0.0%  100.0%
x        0.0%    0.0%  100.0%
total    0.0%    0.0%  100.0%
```

下記は、__パラメータ__ の __修正前__ と __修正後__ の __`ai11s`__ VS __`ai10s`__ の __対戦結果__ の __表__ です。

| 関数名 | o 勝 | o 負 | o 分 | x 勝 | x 負 | x 分 | 勝 | 負 | 分 |
|:-:|--:|--:|--:|--:|--:|--:|--:|--:|--:|
| __修正前__ | 22.2 | 0.0 | 77.8 | 0.0 | 50.5 | 49.5 | 11.1 | 25.3 | 63.6 |
| __修正後__ | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 100.0 |

__修正後__ の __`ai11s`__ は、__4 手目__ で __必ず引き分け__ になる __合法手を選択__ するようになったので、__`ai11s`__ が __× を担当__ した __場合__ に __必ず引き分けになる__ のは __想定通り__ で __問題__ は __ありません__。一方、__`ai11s`__ が __〇 を担当__ した __場合__ は、__修正する__ ことで __勝率__ が __22.1 %__ から __0 %__ に __減ってしまう__ という、__新しい問題__ が __発生__ しています。

長くなったので今回はここまでにします。何故このようなことが起きるかについて、少し考えてみて下さい。このようなことが起きる理由については次回の記事で説明します。

## もう一つの修正方法の確認

__おまけ__ として、『「__自 1 敵 0 空 2__」が __1 つあたり__ 』の __評価値__ を __減らす__ という、下記の、__2 つめ__ の __修正方法__ でも __目的__ を __達成できる__ ことを示します。

1. 『「__自 2 敵 0 空 1__」が __1 つ__ の場合』の __評価値__ を __1 より大きな値__ にする
2. 『「__自 1 敵 0 空 2__」が __1 つあたり__ 』の __評価値__ を __1 未満__ の __正の値__ にする

具体的には、__下記の表__ のように、『「__自 1 敵 0 空 2__」が __1 つあたり__ 』の __評価値__ を __0.5__ に __減らしてみる__ ことにします。

|  | 評価値 |
|:-:|:-:|
| 「__自 2 敵 0 空 1__」が __1 つ__ の場合  | 2  |
| 「__自 1 敵 0 空 2__」が __1 つあたり__ | 1 → __0.5__  |
| 「__自 0 敵 1 空 2__」が __1 つあたり__ | -1  |

__下記__ は、そのように __修正__ した場合の __表__ です。特に __問題はなさそう__ です。

| 順位 | 局面の状況 | 個別 | 評価値 | 
|:-:|:--|:-:|:-:|
| 1 | __真ん中のマスに着手している__ | | `300` |
| 2 | __自分が勝利している__  | | `200` |
| 4 | __「自 2 敵 0 空 1」が 2 つ以上存在する__  | | `100` |
| 5 | __「自 2 敵 0 空 1」が 1 つ存在する__<br>__「自 1 敵 0 空 2」が x 個存在する__<br>__「自 0 敵 1 空 2」が y 個存在する__| `1` (`0～1`)<br>`0.5 × x` (`0～4`)<br>`-y` (`-8~0`) | `-8～5`|
| 3 | __相手が勝利できる__  | | `-100` |

| |  | 「201」 | 「021」 | 「102」 | 「012」 |評価値 |
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| | __評価値__ | 1：`+1`<br>2~：`100` | `-100` | 1 つで<br>`+0.5` | 1 つで<br>`-1` | |
| 1 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/3d1fdd6a-2a1d-06e2-1ff7-f9bb9aae2216.png" width="140"> | 1 | | 1 | 4 | -2.5 |
| 2 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/2a606113-076e-2b87-8268-a76e4bfd89b7.png" width="140"> | 1 | | 1 | 3 | -1.5 |
| 3 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/5f16999c-609b-1748-91af-2ff41108eefe.png" width="140"> |  | | 2 | 3  | -2 |

下記は、__4 手目__ で、__このパラメータ__ で __`ai11s`__ を __実行__ するプログラムです。

```python
mb = Marubatsu()
mb.move(1, 1)
mb.move(0, 0)
mb.move(2, 2)
param = { 
    "score_201": 1,
    "score_102": 0.5,
    "score_012": -1,
}
ai11s(mb, **param)
```

<details><summary><strong>実行結果（長いのでクリックして開いてください）</strong></summary>

```
Start ai_by_score
Turn x
x..
.o.
..O

legal_moves [(1, 0), (2, 0), (0, 1), (2, 1), (0, 2), (1, 2)]
====================
move (1, 0)
Turn o
xX.
.o.
..o

defaultdict(<class 'int'>,
            {Markpat(last_turn=0, turn=1, empty=2): 4,
             Markpat(last_turn=1, turn=0, empty=2): 1,
             Markpat(last_turn=1, turn=1, empty=1): 1,
             Markpat(last_turn=1, turn=2, empty=0): 1,
             Markpat(last_turn=2, turn=0, empty=1): 1})
score -2.5 best score -inf
UPDATE
  best score -2.5
  best moves [(1, 0)]
====================
move (2, 0)
Turn o
x.X
.o.
..o

defaultdict(<class 'int'>,
            {Markpat(last_turn=0, turn=1, empty=2): 3,
             Markpat(last_turn=1, turn=0, empty=2): 1,
             Markpat(last_turn=1, turn=1, empty=1): 2,
             Markpat(last_turn=1, turn=2, empty=0): 1,
             Markpat(last_turn=2, turn=0, empty=1): 1})
score -1.5 best score -2.5
UPDATE
  best score -1.5
  best moves [(2, 0)]
====================
move (0, 1)
Turn o
x..
Xo.
..o

defaultdict(<class 'int'>,
            {Markpat(last_turn=0, turn=1, empty=2): 4,
             Markpat(last_turn=1, turn=0, empty=2): 1,
             Markpat(last_turn=1, turn=1, empty=1): 1,
             Markpat(last_turn=1, turn=2, empty=0): 1,
             Markpat(last_turn=2, turn=0, empty=1): 1})
score -2.5 best score -1.5
====================
move (2, 1)
Turn o
x..
.oX
..o

defaultdict(<class 'int'>,
            {Markpat(last_turn=0, turn=1, empty=2): 3,
             Markpat(last_turn=1, turn=0, empty=2): 2,
             Markpat(last_turn=1, turn=1, empty=1): 2,
             Markpat(last_turn=1, turn=2, empty=0): 1})
score -2.0 best score -1.5
====================
move (0, 2)
Turn o
x..
.o.
X.o

defaultdict(<class 'int'>,
            {Markpat(last_turn=0, turn=1, empty=2): 3,
             Markpat(last_turn=1, turn=0, empty=2): 1,
             Markpat(last_turn=1, turn=1, empty=1): 2,
             Markpat(last_turn=1, turn=2, empty=0): 1,
             Markpat(last_turn=2, turn=0, empty=1): 1})
score -1.5 best score -1.5
APPEND
  best moves [(2, 0), (0, 2)]
====================
move (1, 2)
Turn o
x..
.o.
.Xo

defaultdict(<class 'int'>,
            {Markpat(last_turn=0, turn=1, empty=2): 3,
             Markpat(last_turn=1, turn=0, empty=2): 2,
             Markpat(last_turn=1, turn=1, empty=1): 2,
             Markpat(last_turn=1, turn=2, empty=0): 1})
score -2.0 best score -1.5
====================
Finished
best score -1.5
best moves [(2, 0), (0, 2)]
```
</details>

__実行結果__ から、__上記の表__ のように __評価値__ が __計算される__ ことが __確認__ できます。また、下記の __実行結果__ の __最後の 2 行__ から、__合法手 2 のみ__ が __選択される__ ことが __確認__ できます。

```
best score -1.5
best moves [(2, 0), (0, 2)]
```

下記は、__修正後__ の __パラメータ__ で、__`ai10s`__ と __対戦__ するプログラムです。こちらのパラメータでも、__すべての場合__ で __引き分けになる__ ことが __確認__ できました。

```python
param_ai11s = {
    "score_201": 1,
    "score_102": 0.5,
    "score_012": -1
}
ai_match(ai=[ai11s, ai10s], params=[param_ai11s, {}])
```

実行結果

```
ai11s VS ai10s
count     win    lose     draw
o           0       0    10000
x           0       0    10000
total       0       0    20000

ratio     win    lose    draw
o        0.0%    0.0%  100.0%
x        0.0%    0.0%  100.0%
total    0.0%    0.0%  100.0%
```

# 今回の記事のまとめ

今回の記事では、__`ai10s`__ VS __`ai11s`__ で、__`ai11s`__ が __負ける__ 場合がある __原因__ を __検証__ し、その __問題点__ を __解決する方法__ を紹介しました。また、__問題点__ を __解決するため__ に、__評価値__ を __計算__ するための __パラメータ__ を __修正する方法__ について紹介しました。

下記は、__問題点__ の __解決方法__ と、__パラメータ__ の __修正方法__ です。

:::note warn
__解決方法__： __4 手目__ で、__(0, 2)__（または (2, 0)）__のみ__ に __着手__ が __行われる__ ようにする

__パラメータの修正方法__：以下の __いずれか__ の __方法__ で __パラメータを修正__ する

1. 『「__自 2 敵 0 空 1__」が __1 つ__ の場合』の __評価値__ を __1 より大きな値__ にする
2. 『「__自 1 敵 0 空 2__」が __1 つあたり__ 』の __評価値__ を __1 未満__ の __正の値__ にする
:::

一方、__パラメータの修正__ によって、__`ai11s`__ が __負ける__ 場合があるという __問題__ は __解決できました__ が、今度は __`ai11s`__ VS __`ai10s`__ で、__`ai11s`__ が __勝てなくなる__ という __新しい問題__ が __発生しました__。次回の記事ではそのような問題が発生する原因などについて説明します。

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

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

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

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

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

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

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

# 次回の記事

https://qiita.com/ysgeso/items/3d340c07f3d515ce5fe9

[^1]:__仮引数__ は __変数__ の __一種__ なので、__プログラム用語__ での __parameter__ も、__厳密__ には __変数__ を __表します__
[^2]:__tuple__ でもかまいません。また、__tuple を記述__ してもプログラムを __変更する必要__ は __ありません__