Pythonで〇×ゲームのAIを一から作成する　その61　ai11s の検証と限定的な条件による問題の解決

# 目次と前回の記事

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

https://qiita.com/ysgeso/items/5fe28ae6c765a169fd34

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

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

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

https://github.com/ysgeso/marubatsu/blob/master/061/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>そうでない場合は __ランダム__ なマスに __着手__ する |

__ルール 11__ で __評価値を計算__ する際の __パラメータ__ は以下の通りです。

|  | 修正前 | 修正後 |
|:-:|:-:|:-:|
| __「自 2 敵 0 空 1」が 1 つの場合の評価値__  | 1  | 2 | 
| __「自 1 敵 0 空 2」が 1 つあたりの評価値__ | 1  | 1 |
| __「自 0 敵 1 空 2」が 1 つあたりの評価値__ | -1 | -1 |


基準となる __`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)__<br>修正前  | __98.1__ | __0.0__ | 1.9 | __82.5__ | __1.9__ | 15.6 | __90.3__ | __1.0__ | 8.7 | あり |
| __[`ai11s`](https://qiita.com/ysgeso/items/5fe28ae6c765a169fd34#ai11s-%E3%81%AE%E4%BF%AE%E6%AD%A3)__<br>修正後 | __98.8__ | __0.0__ | 1.2 | __87.7__ | __2.4__ | 10.0 | __93.2__ | __1.2__ | 5.6 | |

# `ai2` VS `ai11s` の検証と `ai11s` の修正

[前回の記事](https://qiita.com/ysgeso/items/5fe28ae6c765a169fd34#%E3%83%A9%E3%83%B3%E3%83%80%E3%83%A0%E3%81%AA-ai-%E3%81%A8%E3%81%AE%E5%AF%BE%E6%88%A6%E3%81%AB%E3%82%88%E3%82%8B-ai11s-%E3%81%AE%E6%A4%9C%E8%A8%BC)では、__ランダムな AI__ である __`ai2`__ と __`ai11s`__ を __対戦__ させた結果、__`ai11s`__ が __敗北する__ 場合があることが __確認__ できたので、__`ai11s`__ は __[弱解決の AI](https://qiita.com/ysgeso/items/5fe28ae6c765a169fd34#%E3%82%B2%E3%83%BC%E3%83%A0%E3%81%AE%E8%A7%A3%E6%B1%BA%E3%81%A8%E3%81%9D%E3%81%AE%E7%A8%AE%E9%A1%9E)__ では __ありません__。

そこで、その __原因を検証__ するために、下記のプログラムのように、__`show_progress`__ を利用することで、__`ai2`__ VS __`ai11s`__ で __`ai11s`__ が __敗北__ する場合の __試合経過__ を __表示__ しました。

```python
from ai import show_progress, ai2, ai11s
from marubatsu import Marubatsu

show_progress(ai=[ai2, ai11s], winner=Marubatsu.CIRCLE)
```

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

```
Turn x
.O.
...
...

Turn o
.o.
.X.
...

Turn x
.o.
.x.
O..

Turn o
.o.
.x.
o.X

Turn x
Oo.
.x.
o.x

Turn o
oo.
.x.
oXx

winner o
oo.
Ox.
oxx
```

今回の記事では、__`ai11s`__ が __敗北する理由__ を __検証__ し、__敗北しない__ ように __修正__ します。

## `ai2` VS `ai11s` の検証

下図は、上記の __`show_progress`__ で __表示__ された __`ai2`__ VS __`ai11s`__ の __試合経過__ です。[以前の記事](https://qiita.com/ysgeso/items/fc19eb2764e0766b59d5#4-%E6%89%8B%E7%9B%AE%E3%81%A7-0-2-%E3%81%AB%E7%9D%80%E6%89%8B%E3%82%92%E8%A1%8C%E3%81%A3%E3%81%9F%E5%A0%B4%E5%90%88%E3%81%AE%E6%A4%9C%E8%A8%BC)と __同様の方法__ で、__`ai11s`__ が __どのような判断__ で __着手を選択したか__ を __検証__ します。なお、__`ai2`__ は __ランダムな着手__ を行う AI なので、__`ai2`__ が行う __着手の検討__ は __省略__ します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/5f643f91-f20f-dba6-aaea-379e1e6aac17.png" width="500">

### 2 手目の検証

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/1fef4447-df97-b25e-b48d-19678cd42171.png" width="140">

__ルール 11__ の __最も優先順位が高い条件__ から、__`ai11s`__ は __真ん中のマス__ に __優先的に着手__ するので、__この局面__ では、__`ai11s`__ は __(1, 1)__ に __着手__ します。

### 4 手目の検証

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/1d94dd0b-9cb7-d24f-f611-b9d240275995.png" width="140">

[以前の記事](https://qiita.com/ysgeso/items/fc19eb2764e0766b59d5#5-%E6%89%8B%E7%9B%AE%E3%81%AE%E6%A4%9C%E8%A8%BC)と __同様の方法__ で、__下記__ のプログラムで __`ai11s`__ がそれぞれの __合法手__ に対して __どのように__ 評価値を __計算__ するかを __表示__ し、その __結果__ を __表にまとめます__。

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

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

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

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

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

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

defaultdict(<class 'int'>,
            {Markpat(last_turn=0, turn=0, empty=3): 1,
             Markpat(last_turn=0, turn=1, empty=2): 2,
             Markpat(last_turn=1, turn=0, empty=2): 1,
             Markpat(last_turn=1, turn=1, empty=1): 3,
             Markpat(last_turn=2, turn=0, empty=1): 1})
score 1 best score 2
====================
move (2, 1)
Turn o
.o.
.xX
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=2, turn=0, empty=1): 1})
score 1 best score 2
====================
move (1, 2)
Turn o
.o.
.x.
oX.

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

defaultdict(<class 'int'>,
            {Markpat(last_turn=0, turn=1, empty=2): 2,
             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})
score 2 best score 2
APPEND
  best moves [(0, 0), (2, 2)]
====================
Finished
best score 2
best moves [(0, 0), (2, 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/e32ebe09-4fa9-5113-3aca-56f1f9f1adad.png" width="65">  | 1 | | 1 | 1 | 2 |
| 2 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/e762416b-a06b-439e-4834-5495f54bd63e.png" width="65">  |  | | 3 | 2 | 1 |
| 3 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/58e63cd0-bf00-de69-a370-dde2b4990df6.png" width="65">  | 1 | | 1 | 2 | 1 |
| 4 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/e7d9593f-9717-f55f-7e41-7c1a80ef160b.png" width="65">  | 1 | | 2 | 3 | 1 |
| 5 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/452de602-8084-f4bc-b6a5-55d4bfe0cf2c.png" width="65">  |  | | 2 | 2 | 0 |
| 6 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/58a72c2e-481a-24a3-9a1b-f95c5b1596a5.png" width="65">  | 1 | | 2 | 2 | 2 |

__上記の表__ から、__合法手 1__ と __6__ の __評価値__ が __2__ で __最も高くなる__ ことがわかります。従って、__2 つの合法手__ である __(0, 0)__ と __(2, 2)__ がそれぞれ 1 / 2 = __50 %__ の __確率__ で __選択される__ ことが __わかります__。そのことは、下記の __実行結果__ の __最後の 2 行__ からも __確認__ できます。

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

従って、__4 手目__ では、__50 % の確率__ で、__(2, 2)__ が __選択された__ ことがわかります。

### 5 手目の検証

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/438ed25d-9117-dad9-7612-6cdfb04c32a3.png" width="140">

__5 手目__ で __`ai2`__ が上記のように __(0, 0)__ に __着手__ を行った場合は、__〇 にとって__「__自 2 敵 0 空 1__」が __2 つ存在__ するので、__〇 の必勝の局面__ であることがわかります。__`ai2`__ は __ランダムな AI__ なので、__必勝の局面__ になってもその後で __最善手__ を __選択しない可能性__ がありますが、今回で __検証__ している __下記の試合経過__ のように、この後で __`ai2`__ が __最善手__ を __選択し続けた場合__ は、__`ai2` の勝利__ になります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/b6604dfa-a93b-911a-0491-7884e378ed5d.png" width="220">

### 4 手目で (0, 0) に着手した場合の検証

上記から、__4 手目__ で __`ai11s`__ が __選択__ した __(2, 2)__ が __最善手ではない可能性がある__ ことが __わかりました__。そのことを __確認する__ ために、__4 手目__ での __`ai11s`__ の __もう一つの候補__ である __(0, 0)__ に __着手した場合__ の __局面の状況__ の __検証__ を行うことにします。

__4 手目__ で __(0, 0)__ に __着手__ を行った場合は、__下記__ のような __局面__ になります。なお、__実際の対戦__ では __(0, 0)__ に __着手していない__ ので、__そのこと__ を __明確にする__ ために、__(0, 0) のマス__ は、黄色ではなく __オレンジ色__ で __塗りつぶして__ います。__(0, 0)__ に __着手__ した場合の __局面の状況__ を __調べるため__ に、__以後__ は __最善手のみ__ を __選択__ した場合の __試合の経過__ と __結果__ を __調べます__。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/fe5d2616-6e1f-430d-6178-0f591e6c2f4e.png" width="140">

__上記の局面__ は、__次__ の __× の手番__ で × が __(2, 2)__ に __着手__ を行うと、__× の勝利__ になるので、__5 手目__ の __〇 の最善手__ は __(2, 2)__ です。__下記__ は __5 手目__ で __(2, 2)__ に __着手を行った場合__ の図です。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/b6aa32da-24ff-4c5b-14c4-b70970e33306.png" width="140">

__上記の局面__ は、__次__ の __〇 の手番__ で 〇 が __(1, 2)__ に __着手__ を行うと、__〇 の勝利__ になるので、__6 手目__ の __× の最善手__ は __(1, 2)__ です。__下記__ は __6 手目__ で __(1, 2)__ に __着手を行った場合__ の図です。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/0f7ae2bd-8409-f780-2bc6-2fb3d2f05e63.png" width="140">

__上記の局面__ には、__3 種類__ の __合法手__ があるので、__それぞれ__ について __調べます__。

__(2, 0)__ に __着手__ を行った場合は、__×__ は __〇 の勝利を阻止__ するために __(2, 1)__ に __着手__ を行い、__最後__ に __〇__ が __(0, 1)__ に __着手__ を行い、__下図__ のように __引き分け__ になります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/5183a50a-86e9-908b-74a7-a23db27a266d.png" width="290">

__(2, 1)__ に __着手__ を行った場合は、__×__ は __〇 の勝利を阻止__ するために __(2, 0)__ に __着手__ を行い、__最後__ に __〇__ が __(0, 1)__ に __着手__ を行い、__下図__ のように __引き分け__ になります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/2d81c2fe-f32e-e9cc-6235-be34ba5d94d9.png" width="290">

__(0, 0)__ に __着手__ を行った場合は、__下図__ の __局面__ になり、この後で × と 〇 が __どのような着手__ を行っても __引き分け__ になります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/7520b2bf-0776-06c0-389d-46256bd4e052.png" width="140">

__上記__ から、__4 手目__ で __(0, 0)__ に __着手__ を行った場合に、その後で __お互い__ が __最善手のみ__ を __選択__ すると __引き分け__ になります。一方、__(2, 2)__ に __着手__ を行った場合は、__お互い__ が __最善手のみ__ を __選択__ すると __敗北する__ ので、__4 手目__ の __(2, 2)__ は __最善手ではない__ ことがわかりました。

:::note
__上記__ から __わかること__ は、「__4 手目__ の __局面__ で __(2, 2)__ が __最善手__ では __ないこと__」__だけ__ で、「__(0, 0)__ が __最善手__ で __あること__」までは __わかりません__。__そのこと__ を __示すため__ には、__残り__ の __合法手 2__ ~ __5__ に __着手__ した __局面__ が __引き分けの局面__ か、__× の必敗の局面__ であることを __示す必要__ があります。長くなるので本記事ではその確認を省略しますが、__実際に確認__ することで、__(0, 0)__ が __最善手__ の __1 つ__ であることが __わかります__。興味と余裕がある方は実際に確認してみて下さい。
:::

## パラメータの修正

__`ai2`__ VS __`ai11s`__ で、__`ai11s`__ が __敗北__ した __理由__ は、__下記の局面__ で __`ai11s`__ が __最善手ではない (2, 2)__ を __選択__ する __可能性がある__ ことです。また、__この局面__ では __(2, 2)__ と __同じ評価値__ が計算される __(0, 0)__ を選択すると __引き分けの局面__ になるので、__`ai11s`__ が __この局面__ で __(0, 0)__ の __評価値のみ__ を __最も高く計算__ するように __できれば__、__`ai11s`__ が __敗北しない__ ように __なります__。その方法を少し考えてみて下さい。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/c9ccf601-402c-b776-5737-f3ebe6566dff.png" width="70">

下記は、__(0, 0)__ と __(2, 2)__ を表す __合法手 1__ と __6__ の表です。__この表__ から、__合法手 1__ と __6__ では、__評価値__ を __計算__ する際に __利用する__、いくつかの __マークのパターン__ の __数__ が __異なる__ ことが分かります。従って、それらのマークのパターンの __パラメータ__ を __うまく変更する__ ことで、__合法手 1__ の __評価値__ を、__合法手 6__ の __評価値より__ も __大きくする__ ことが __できる可能性__ が __あります__。その方法を少し考えてみて下さい。

| |  | 「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/e32ebe09-4fa9-5113-3aca-56f1f9f1adad.png" width="65">  | 1 | | 1 | 1 | 2 |
| 6 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/58a72c2e-481a-24a3-9a1b-f95c5b1596a5.png" width="65">  | 1 | | 2 | 2 | 2 |

そのような方法としては、__以下__ の __2 種類__ の方法が __考えられます__。

* 「__自 1 敵 0 他 2__」の 1 つ当たりの __評価値__ を __減らす__
* 「__自 0 敵 1 他 2__」の 1 つ当たりの __評価値__ を __増やす__

__いずれの方法__ でも __かまいません__ ので、本記事では、「__自 1 敵 0 他 2__」の 1 つ当たりの __評価値__ を __減らす方法__ を __考えてみる__ ことにします。興味と余裕がある方は、もう一つの方法も実際に試してみて下さい。

次に、__具体的__ に __評価値__ を __どの程度減らすか__ について __考える必要__ があります。[以前の記事](https://qiita.com/ysgeso/items/fc19eb2764e0766b59d5#%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF%E3%81%AE%E8%AA%BF%E6%95%B4%E3%81%AE%E3%81%95%E3%82%89%E3%81%AA%E3%82%8B%E6%B3%A8%E6%84%8F%E7%82%B9-%E3%81%9D%E3%81%AE-1)で説明したように、ルールの __条件の意味__ を考えると、 「__自 1 敵 0 他 2__」の 1 つ当たりの __評価値__ は __正の値__ にする __必要__ があります。そこで、この __パラメータ__ を 0 と 1 の中間の __0.5 に設定__ してみることにします。

まず、そのように __パラメータ設定__ した場合の、それぞれの合法手の __評価値__ を、下記のプログラムで __計算してみる__ ことにします。「__自 1 敵 0 他 2__」の 1 つ当たりの __評価値を変更__ しましたが、__他のパラメータ__ は __変更していない__ ので、下記のように、__対応する `score_102`__ の __キーワード引数のみ__ を __記述__ するだけで __かまいません__。

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

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

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

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

defaultdict(<class 'int'>,
            {Markpat(last_turn=0, turn=0, empty=3): 1,
             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): 4,
             Markpat(last_turn=2, turn=0, empty=1): 1})
score 1.5 best score -inf
UPDATE
  best score 1.5
  best moves [(0, 0)]
====================
move (2, 0)
Turn o
.oX
.x.
o..

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

defaultdict(<class 'int'>,
            {Markpat(last_turn=0, turn=0, empty=3): 1,
             Markpat(last_turn=0, turn=1, empty=2): 2,
             Markpat(last_turn=1, turn=0, empty=2): 1,
             Markpat(last_turn=1, turn=1, empty=1): 3,
             Markpat(last_turn=2, turn=0, empty=1): 1})
score 0.5 best score 1.5
====================
move (2, 1)
Turn o
.o.
.xX
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=2, turn=0, empty=1): 1})
score 0.0 best score 1.5
====================
move (1, 2)
Turn o
.o.
.x.
oX.

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

defaultdict(<class 'int'>,
            {Markpat(last_turn=0, turn=1, empty=2): 2,
             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})
score 1.0 best score 1.5
====================
Finished
best score 1.5
best moves [(0, 0)]
```
</details>

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

| |  | 「201」 | 「021」 | 「102」 | 「012」 | 評価値 |
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| | __評価値__ | 1：`+2`<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/e32ebe09-4fa9-5113-3aca-56f1f9f1adad.png" width="65">  | 1 | | 1 | 1 | 1.5 |
| 2 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/e762416b-a06b-439e-4834-5495f54bd63e.png" width="65">  |  | | 3 | 2 | -0.5 |
| 3 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/58e63cd0-bf00-de69-a370-dde2b4990df6.png" width="65">  | 1 | | 1 | 2 | 0.5 |
| 4 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/e7d9593f-9717-f55f-7e41-7c1a80ef160b.png" width="65">  | 1 | | 2 | 3 | 0 |
| 5 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/452de602-8084-f4bc-b6a5-55d4bfe0cf2c.png" width="65">  |  | | 2 | 2 | -1 |
| 6 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/58a72c2e-481a-24a3-9a1b-f95c5b1596a5.png" width="65">  | 1 | | 2 | 2 | 1 |

__上記の表__ から、__合法手 1__ の __評価値のみ__ が __1.5__ で __最も高く__ なり、__(0, 0) のみ__ が __選択される__ ようになります。そのことは、下記の __実行結果__ の __最後の 2 行__ からも __確認__ できます。

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

__上記の修正__ によって、__他__ の __優先順位が高い__ 条件の __評価値の範囲__ を __超えているか__ どうかを下記の表で __確認__ します。特に __問題はなさそう__ です。

| 順位 | 局面の状況 | 個別 | 評価値 | 
|:-:|:--|:-:|:-:|
| 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>`0.5 * x` (`0～4`)<br>`-y` (`-8~0`) | `-8～6`|
| 3 | __相手が勝利できる__  | | `-100` |

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

__修正したパラメータ__ で __`ai2`__ と __対戦__ することで、__問題__ が __解決されたか__ を __確認__ することにします。その際には、__下記__ のプログラムのように、__`ai_match`__ の __キーワード引数 `params`__ の __`ai11s` に対応__ する __要素__ には、__`ai11s`__ の __修正を行ったパラメータの値のみ__ を __記述__ します。

```python
from ai import ai_match

ai_match(ai=[ai11s, ai2], params=[{"score_102":0.5}, {}])
```

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

```
ai11s VS ai2
count     win    lose    draw
o        9909       0      91
x        8768      80    1152
total   18677      80    1243

ratio     win    lose    draw
o       99.1%    0.0%    0.9%
x       87.7%    0.8%   11.5%
total   93.4%    0.4%    6.2%
```

下記は、__上記の結果__ と、__これまで__ の __`ai11s`__ が __`ai2`__ と __対戦__ した際の __結果__ を表に __まとめたもの__ です。なお、これまでは __修正前__ と __修正前__ の __`ai11s`__ のように __表記__ していましたが、__今回の修正__ で __`ai11s`__ の __種類__ が __3 種類__ になったので、__それぞれ__ を __`ai11s`__ を __作成した順番__ で __`ai11s` ver 1__ のように __表記__ することにします。

| 関数名 | o 勝 | o 負 | o 分 | x 勝 | x 負 | x 分 | 勝 | 負 | 分 |
|:-:|--:|--:|--:|--:|--:|--:|--:|--:|--:|
| __[`ai11s` ver 1](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 |
| __[`ai11s` ver 2](https://qiita.com/ysgeso/items/5fe28ae6c765a169fd34#ai11s-%E3%81%AE%E4%BF%AE%E6%AD%A3)__ | __98.8__ | __0.0__ | 1.2 | __87.7__ | __2.4__ | 10.0 | __93.2__ | __1.2__ | 5.6 |
| __`ai11s` ver 3__  | __99.1__ | __0.0__ | 0.9 | __87.7__ | __0.8__ | 11.5 | __93.4__ | __0.4__ | 6.2 | 

__上記__ の表から、__今回__ の __パラメータの修正__ によって、__× を担当__ した場合の __敗率__ が __約 2 %__ から、__1 % 未満__ に __減っている__ ので、__先程の問題__ が __改善__ された __可能性が高い__ ことがわかります。ただし、依然として __× を担当__ した際の __敗率__ が __0 %__ に __なっていない__ ので、__この修正__ を行っても __`ai11s` ver 3__ は __弱解決の AI__ には __なっていない__ ことがわかります。

### パラメータの他の修正方法の検証

__上記__ では、「__自 1 敵 0 他 2__」の 1 つ当たりの __評価値__ を 1 から __0.5__ に __減らしました__ が、__1 未満__ の __他の正の値__ に __修正__ することで、__もしかすると__、__敗率__ が __0 %__ に __なるかもしれません__。そこで、下記のプログラムで、__パラメータ__ を __0.9__、__0.75__、__0.25__、__0.1__、__0__ に __変えて__、__`ai2`__ と __対戦__ してみることにします。

なお、__正ではない 0__ に変えて __対戦する理由__ は、「__自 1 敵 0 他 2__」の __マークのパターンの数__ が、__弱解決の AI__ を作る際に、__実際__ に __役に立っていること__ を __確認するため__ です。「__自 1 敵 0 他 2__」の 1 つあたりの __評価値__ を __0 にする__ ということは、「__自 1 敵 0 他 2__」の __マークのパターンの数__ を __考慮しない__ という __意味__ になるので、その場合に __敗率__ が __増えなければ__、「__自 1 敵 0 他 2__」の __マークのパターンの数__ が __役に立っていない__ ことになります。

```python
ai_match(ai=[ai11s, ai2], params=[{"score_102":0.9}, {}])
```

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

```
ai11s VS ai2
count     win    lose    draw
o        9895       0     105
x        8776      68    1156
total   18671      68    1261

ratio     win    lose    draw
o       99.0%    0.0%    1.1%
x       87.8%    0.7%   11.6%
total   93.4%    0.3%    6.3%
```

```python
ai_match(ai=[ai11s, ai2], params=[{"score_102":0.75}, {}])
```

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

```
ai11s VS ai2
count     win    lose    draw
o        9893       0     107
x        8797      52    1151
total   18690      52    1258

ratio     win    lose    draw
o       98.9%    0.0%    1.1%
x       88.0%    0.5%   11.5%
total   93.5%    0.3%    6.3%
```

```python
ai_match(ai=[ai11s, ai2], params=[{"score_102":0.25}, {}])
```

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

```
ai11s VS ai2
count     win    lose    draw
o        9893       0     107
x        8808      59    1133
total   18701      59    1240

ratio     win    lose    draw
o       98.9%    0.0%    1.1%
x       88.1%    0.6%   11.3%
total   93.5%    0.3%    6.2%
```

```python
ai_match(ai=[ai11s, ai2], params=[{"score_102":0.1}, {}])
```

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

```
ai11s VS ai2
count     win    lose    draw
o        9912       0      88
x        8805      61    1134
total   18717      61    1222

ratio     win    lose    draw
o       99.1%    0.0%    0.9%
x       88.0%    0.6%   11.3%
total   93.6%    0.3%    6.1%
```

```python
ai_match(ai=[ai11s, ai2], params=[{"score_102":0}, {}])
```

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

```
ai11s VS ai2
count     win    lose    draw
o        9916       0      84
x        8685     159    1156
total   18601     159    1240

ratio     win    lose    draw
o       99.2%    0.0%    0.8%
x       86.9%    1.6%   11.6%
total   93.0%    0.8%    6.2%
```

__実行結果__ から、__パラメータ__ を __0.9__、__0.75__、__0.25__、__0.1__ に __変えても__、__0.5 の場合__ と __結果__ が __変わらない__ ことが分かりました。また、__0 に変更__ した場合は、__× を担当__ した場合の __敗率__ が __約 1.5 %__ に __上昇__ することから、「__自 1 敵 0 他 2__」の __マークのパターンの数__ が、__弱解決の AI__ を作る際に、__実際__ に __役に立っている__ ことが __確認__ できました。

そこで、__`ai11s` ver 3__ の 「__自 1 敵 0 他 2__」 の __パラメータ__ を __0.5__ とし、下記のプログラムのように、__`ai11s`__ の __`score_012`__ の __デフォルト引数値__ を __0.5__ に __修正__ することにします。

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

def ai11s(mb, score_201=2, score_102=0.5, score_012=-1, debug=False):
以下同じなので略
```

<details><summary><strong>プログラム全体</strong></summary>

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

def ai11s(mb, score_201=2, score_102=0.5, score_012=-1, 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 marubatsu import Markpat
from ai import ai_by_score
from pprint import pprint

-def ai11s(mb, score_201=2, score_102=1, score_012=-1, debug=False):
+def ai11s(mb, score_201=2, score_102=0.5, score_012=-1, debug=False):
以下同じなので略
```
</details>


# `ai2` VS `ai11s` ver 3 の検証と `ai11s` ver 3 の修正

__上記__ から __`ai11s` ver 3__ は、__弱解決の AI ではない__ ことがわかったので、下記のプロラムのように、再び __`show_progress`__ を利用して、__`ai2`__ VS __`ai11s` ver3__ で __`ai11s` ver 3__ が __敗北__ する場合の __試合経過__ を表示し、__敗北__ する __原因を検証__ することにします。

```python
show_progress(ai=[ai2, ai11s], winner=Marubatsu.CIRCLE)
```

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

```
Turn x
...
...
..O

Turn o
...
.X.
..o

Turn x
O..
.x.
..o

Turn o
o..
.x.
X.o

Turn x
o.O
.x.
x.o

Turn o
oXo
.x.
x.o

winner o
oxo
.xO
x.o
```

## `ai2` VS `ai11s` ver 3 の検証

下図は、上記の __`show_progress`__ で __表示__ された __`ai2`__ VS __`ai11s` ver 3__ の __試合経過__ です。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/701a3b0a-8e71-3d2c-e9a1-6b245ff8b1d4.png" width="500">

### 2 手目の検証

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/ad21a2a9-a9c8-f446-16cd-6bd47a320216.png" width="140">

__ルール 11__ の __最も優先順位が高い条件__ から、__`ai11s` ver 3__ は __真ん中のマス__ に __優先的に着手__ するので、__この局面__ では、__`ai11s` ver 3__ は __(1, 1)__ に __着手__ します。

### 4 手目の検証

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/be17e62a-9633-c437-be50-41173f60aa17.png" width="140">

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

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

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

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

legal_moves [(1, 0), (2, 0), (0, 1), (2, 1), (0, 2), (1, 2)]
====================
move (1, 0)
Turn o
oX.
.x.
..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): 1,
             Markpat(last_turn=1, turn=2, empty=0): 1,
             Markpat(last_turn=2, turn=0, empty=1): 1})
score 0.0 best score -inf
UPDATE
  best score 0.0
  best moves [(1, 0)]
====================
move (2, 0)
Turn o
o.X
.x.
..o

defaultdict(<class 'int'>,
            {Markpat(last_turn=0, turn=1, empty=2): 2,
             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})
score 1.0 best score 0.0
UPDATE
  best score 1.0
  best moves [(2, 0)]
====================
move (0, 1)
Turn o
o..
Xx.
..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): 1,
             Markpat(last_turn=1, turn=2, empty=0): 1,
             Markpat(last_turn=2, turn=0, empty=1): 1})
score 0.0 best score 1.0
====================
move (2, 1)
Turn o
o..
.xX
..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): 1,
             Markpat(last_turn=1, turn=2, empty=0): 1,
             Markpat(last_turn=2, turn=0, empty=1): 1})
score 0.0 best score 1.0
====================
move (0, 2)
Turn o
o..
.x.
X.o

defaultdict(<class 'int'>,
            {Markpat(last_turn=0, turn=1, empty=2): 2,
             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})
score 1.0 best score 1.0
APPEND
  best moves [(2, 0), (0, 2)]
====================
move (1, 2)
Turn o
o..
.x.
.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): 1,
             Markpat(last_turn=1, turn=2, empty=0): 1,
             Markpat(last_turn=2, turn=0, empty=1): 1})
score 0.0 best score 1.0
====================
Finished
best score 1.0
best moves [(2, 0), (0, 2)]
```
</details>

下記は、__上記の結果__ を __まとめた表__ です。なお、[同一局面](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)は、__同じ合法手__ に __まとめました__。

| |  | 「201」 | 「021」 | 「102」 | 「012」 | 評価値 |
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| | __評価値__ | 1：`+2`<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/98769553-7a0b-65fb-f3e1-e89420fa8b2d.png" width="140">  | 1 | | 2 | 2 | 1 |
| 2 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/5eb6c999-ca2f-533f-e8fa-7a7db91699e6.png" width="140">  | 1 | | 2 | 3 | 0 |

__上記の表__ から、__合法手 1__ の __評価値__ が __1__ で __最も高くなる__ ことがわかります。従って、__合法手 1__ の __2 つの合法手__ である __(2, 0)__ と __(0, 2)__ がそれぞれ 1 / 2 = __50 %__ の __確率__ で __選択される__ ことが __わかります__。そのことは、下記の __実行結果__ の __最後の 2 行__ からも __確認__ できます。

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

従って、__4 手目__ では、__50 % の確率__ で、__(0, 2)__ が __選択された__ ことがわかります。

### 6 手目以降の検証

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/438023b8-5286-f688-2b02-f6c201cf7e96.png" width="210">

__5 手目__ で __`ai2`__ が上記のように __(2, 0)__ に __着手__ を行った場合は、__〇 にとって__「__自 2 敵 0 空 1__」が __2 つ存在__ するので、__〇 の必勝の局面__ であることがわかります。__`ai2`__ は __ランダムな AI__ なので、__必勝の局面__ になってもその後で __最善手__ を __選択しない可能性__ がありますが、今回で __検証__ している __上記の試合経過__ のように、この後で __`ai2`__ が __最善手__ を __選択した場合__ は、__`ai2` の勝利__ になります。

### 4 手目で合法手 2 に着手した場合の検証

上記から、__4 手目__ で __`ai11s`__ が __選択__ した __合法手 1__ である __(0, 2)__ が __最善手ではない可能性がある__ ことが __わかりました__。そのことを __確かめる__ ために、__4 手目__ での __`ai11s`__ の __もう一つの候補__ である __合法手 2__ が __最善手であるか__ どうかを __調べる__ ことにします。

:::note
先程検証した __`ai2`__ VS __`ai11s` ver 2__ の場合の __4 手目__ の __局面__ には __合法手__ が __6 種類__ あったので、そのうちの __2 つ__ の合法手を __検証するだけ__ では、__どの合法手__ が __最善手であるか__ までは __わかりません__ でした。一方、__今回の場合__ は、__合法手__ が __2 種類__ しかないので、__片方の合法手__ を着手した場合の __局面の状況__ が、__もう片方の合法手__ を着手した場合の __局面の状況より__ も __良ければ__、__最善手__ であることが __確定__ します。
:::

__4 手目__ で __合法手 2__ の __1 つ__ である __(0, 1)__ に __着手__ を行った場合は、__下図__ の __左の局面__ になります。__(0, 1)__ が __最善手であるか__ どうかを __調べるため__ に、その後で __最善手のみ__ を __選択__ した場合の __試合の経過__ と __結果__ を __調べる__ と、__その後に出現__ するいずれの __局面__ も、そこに __着手しなければ敗北__ する __マスが存在__ するので、__そのマスが最善手__ になります。下図は、__最善手__ を __選択し続けた場合__ の図で、__引き分けになる__ ことが __わかります__。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/f15147b8-709b-348b-c840-3e8d7a5ef9a1.png" width="430">

__上記__ から、__4 手目__ で __合法手 2__ のいずれかに __着手__ を行った場合に、その後で __お互い__ が __最善手のみ__ を __選択__ すると __引き分け__ になります。一方、__合法手 1__ のいずれか __着手__ を行った場合は、__お互い__ が __最善手のみ__ を __選択__ すると __敗北する__ ので、__4 手目__ の局面では __合法手 1__ は __最善手ではない__ ことがわかりました。

## パラメータの修正方法の検討

__`ai2`__ VS __`ai11s` ver 3__ で、__`ai11s` ver 3__ が __敗北__ した __理由__ は、__3 手目__ で __最善手ではない合法手 1__ を __選択__ することです。また、上記から、この局面の __合法手 2__ は __最善手である__ ことがわかったので、__`ai11s` ver 3__ が __この局面__ で __合法手 2__ の __評価値__ を __最も高く計算__ するように __できれば__、__`ai11s` ver 3__ が __敗北しない__ ように __なります__。その方法を少し考えてみて下さい。

下記は、先程の __合法手 1__ と __2__ の表を再掲したものです。図から、__合法手 1__ と __2__ では、__評価値__ を __計算__ する際に __利用する__、__マークのパターン__ の __数__ が __異なる__ のは、「__自 0 敵 1 他 2__」__のみ__ であることが __わかります__。

| |  | 「201」 | 「021」 | 「102」 | 「012」 | 評価値 |
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| | __評価値__ | 1：`+2`<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/98769553-7a0b-65fb-f3e1-e89420fa8b2d.png" width="140">  | 1 | | 2 | 2 | 1 |
| 2 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/5eb6c999-ca2f-533f-e8fa-7a7db91699e6.png" width="140">  | 1 | | 2 | 3 | 0 |

従って、「__自 0 敵 1 他 2__」の __マークのパターン__ に対する __パラメータ__ を __変える__ ことで、__下記の式__ のように、__合法手 2__ の __評価値__ が、__合法手 1__ の __評価値より__ も __大きくなる__ ようにする __方法__ を __考える__ ことにします。

$合法手 1 の評価値 < 合法手 2 の評価値$

「__自 0 敵 1 他 2__」の __マークのパターン__ の 1 つ当たりの __評価値__ を $x$ とすると、__合法手 1__ と __2__ の __評価値__ の __計算式__ は、以下のようになります。

* $合法手 1 の評価値 = 2 + 0.5 × 2 + 2y = 3 + 2y$
* $合法手 2 の評価値 = 2 + 0.5 × 2 + 3y = 3 + 3y$

__上記の式__ を、__目的の式__ に __当てはめる__ と、__下記の式__ になります。

$3 + 2y < 3 + 3y$

この式の __両辺から__ $3 + 2y$ を __引くと__ 、__下記の式__ になります。

$0 < y$

上記から、「__自 0 敵 1 他 2__」の __マークのパターン__ の 1 つ当たりの __評価値__ を __正の数__ にすることで、__合法手 2__ の __評価値__ が __合法手 1__ の __評価値より__ も __大きくなる__ ことが __わかりました__。

:::note
__式__ で考えるのが __苦手な人__ は、__以下__ のように __言葉で考える__ ことも __できます__。ただし、__慣れれば__、__式で考えたほう__ が、__分かりやすさ__ や __計算のしやすさ__ の面ではるかに __優れている__ ので、__式で考える方法__ に __慣れる__ ことを __お勧めします__。

1. 「__自 0 敵 1 他 2__」の __マークのパターン__ の __数__ は、__合法手 2__ のほうが、__合法手 1 より__ も __1__ だけ __多い__
2. 従って、その __パラメータ__ を __1 増やす__ と、__合法手 2__ の __評価値__ のほうが、__合法手 1__ の __評価値より__ も、__1__ だけ __多く増える__
3. __現時点__ では、__合法手 1__ の __評価値__ の方が、__合法手 2__ の __評価値より__ も __1__ だけ __多い__
4. 従って、__パラメータ__ を __1 より多く増やせば__、__合法手 2__ の __評価値__ の方が __合法手 1__ の __評価値より__ も __大きくなる__
5. __現時点__ の __パラメータ__ は __-1__ で、$-1 + 1 = 0$ なので、__パラメータ__ を __0 より大きい__、__正の値__ に __すればよい__ ことが __わかる__
:::

## パラメータの修正の効果の確認

__問題__ の __解決方法__ がわかったので、下記のプログラムで「__自 0 敵 1 空 2__」のパラメータを、__正の値__ である __1 に修正__ して、__4 手目__ の __局面__ で __`ai11s`__ が __計算__ す る __評価値を表示__ し、__合法手 2 のみ__ が __選択される__ ようになったかどうかを __確認__ することにします。

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

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

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

legal_moves [(1, 0), (2, 0), (0, 1), (2, 1), (0, 2), (1, 2)]
====================
move (1, 0)
Turn o
oX.
.x.
..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): 1,
             Markpat(last_turn=1, turn=2, empty=0): 1,
             Markpat(last_turn=2, turn=0, empty=1): 1})
score 6.0 best score -inf
UPDATE
  best score 6.0
  best moves [(1, 0)]
====================
move (2, 0)
Turn o
o.X
.x.
..o

defaultdict(<class 'int'>,
            {Markpat(last_turn=0, turn=1, empty=2): 2,
             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})
score 5.0 best score 6.0
====================
move (0, 1)
Turn o
o..
Xx.
..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): 1,
             Markpat(last_turn=1, turn=2, empty=0): 1,
             Markpat(last_turn=2, turn=0, empty=1): 1})
score 6.0 best score 6.0
APPEND
  best moves [(1, 0), (0, 1)]
====================
move (2, 1)
Turn o
o..
.xX
..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): 1,
             Markpat(last_turn=1, turn=2, empty=0): 1,
             Markpat(last_turn=2, turn=0, empty=1): 1})
score 6.0 best score 6.0
APPEND
  best moves [(1, 0), (0, 1), (2, 1)]
====================
move (0, 2)
Turn o
o..
.x.
X.o

defaultdict(<class 'int'>,
            {Markpat(last_turn=0, turn=1, empty=2): 2,
             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})
score 5.0 best score 6.0
====================
move (1, 2)
Turn o
o..
.x.
.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): 1,
             Markpat(last_turn=1, turn=2, empty=0): 1,
             Markpat(last_turn=2, turn=0, empty=1): 1})
score 6.0 best score 6.0
APPEND
  best moves [(1, 0), (0, 1), (2, 1), (1, 2)]
====================
Finished
best score 6.0
best moves [(1, 0), (0, 1), (2, 1), (1, 2)]
```
</details>

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

| |  | 「201」 | 「021」 | 「102」 | 「012」 | 評価値 |
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| | __評価値__ | 1：`+2`<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/98769553-7a0b-65fb-f3e1-e89420fa8b2d.png" width="140">  | 1 | | 2 | 2 | 5 |
| 2 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/5eb6c999-ca2f-533f-e8fa-7a7db91699e6.png" width="140">  | 1 | | 2 | 3 | 6 |

__上記__ の表から、__合法手 2__ の __評価値__ が __6__ になり、__合法手 1__ の __評価値__ である __5 より__ も __大きくなる__ ことが __確認__ できます。また、__合法手 2__ のマスである、__辺のマスのみ__ が __選択される__ ようになったことは、下記の __実行結果__ の __最後の 2 行__ からも __確認__ できます。以後は、__このパラメータ__ で __評価値__ を __計算__ する __`ai11s`__ を __`ai11s` ver 4__ と __表記__ することにします。

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

次に、下記のプログラムで、__`ai2`__ と __対戦__ してみることにします。

```python
ai_match(ai=[ai11s, ai2], params=[{"score_012": 1}, {}])
```

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

```
ai11s VS ai2
count     win    lose    draw
o        9887       0     113
x        9128     121     751
total   19015     121     864

ratio     win    lose    draw
o       98.9%    0.0%    1.1%
x       91.3%    1.2%    7.5%
total   95.1%    0.6%    4.3%
```

下記は、__上記の結果__ と、__これまで__ の __`ai11s`__ が __`ai2`__ と __対戦__ した際の __結果__ を表に __まとめたもの__ です。

| 関数名 | o 勝 | o 負 | o 分 | x 勝 | x 負 | x 分 | 勝 | 負 | 分 |
|:-:|--:|--:|--:|--:|--:|--:|--:|--:|--:|
| __[`ai11s` ver 1](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 |
| __[`ai11s` ver 2](https://qiita.com/ysgeso/items/5fe28ae6c765a169fd34#ai11s-%E3%81%AE%E4%BF%AE%E6%AD%A3)__ | __98.8__ | __0.0__ | 1.2 | __87.7__ | __2.4__ | 10.0 | __93.2__ | __1.2__ | 5.6 |
| __`ai11s` ver 3__  | __99.1__ | __0.0__ | 0.9 | __87.7__ | __0.8__ | 11.5 | __93.4__ | __0.4__ | 6.2 | 
| __`ai11s` ver 4__  | __98.9__ | __0.0__ | 1.1 | __91.3__ | __1.2__ | 7.5 | __95.1__ | __0.6__ | 4.3 | 

__上記の表__ から、__`ai11s` ver 4__ は、__× を担当__ した場合の __勝率__ が __最も高くなっている__ ので、__改善している__ ように __見えるかもしれません__ が、__敗率__ が __0.8 %__ から __1.2 %__ に __上昇__ しているという __問題__ が __あります__。__〇×ゲーム__ は、__引き分けのゲーム__ なので、[前回の記事](https://qiita.com/ysgeso/items/5fe28ae6c765a169fd34#%E5%BC%95%E3%81%8D%E5%88%86%E3%81%91%E3%81%AE%E3%82%B2%E3%83%BC%E3%83%A0%E3%81%AE%E5%A0%B4%E5%90%88)で説明した __引き分けのゲーム__ の場合の __弱解決の AI__ の __性質__ から、__弱解決の AI__ に __近づいているか__ どうかの __判断__ としては、__勝率ではなく__、__敗率が重要__ です。従って、__敗率が上昇する__ ということは、__修正前__ の __`ai11s` ver 3__ よりも、__弱解決の AI__ から __遠ざかっている__ と __考えられます__。何故このようなことが起きたかについて少し考えてみて下さい。

### 敗率が上昇した理由

下記のプログラムで __`ai2`__ VS __`ai11s` ver 4__ で __`ai11s` ver 4__ が __敗北__ する __試合経過を表示__ すると、__これまで__ とは __異なる試合経過__ で __敗北している__ ことが __分かります__。

```python
show_progress(ai=[ai2, ai11s], params=[{}, {"score_012": 1}], winner=Marubatsu.CIRCLE)
```

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

Turn o
X..
.o.
...

Turn x
x..
.o.
..O

Turn o
x..
Xo.
..o

Turn x
x..
xo.
O.o

Turn o
xX.
xo.
o.o

winner o
xxO
xo.
o.o
```

__パラメータの修正__ によって、__`ai11s` ver 4__ は、__`ai2`__ VS __`ai11s` ver 3__ の __4 手目の局面__ で、__最善手__ を __選択する__ ようになりましたが、一方で、__上記__ のように __これまで最善手__ を __選択できていた__ 別の __局面__ で、__最善手__ を __選択できなくなる__ ことが、__敗率__ が __上昇した理由__ です。


__長くなる__ ので __本記事__ では __検証は行いません__ が、興味と余裕がある方は、__上記__ の __試合経過__ を __実際に検証__ することで、__パラメータの修正__ によって __最善手__ が __選択できなく__ なった __局面__ があることを __確認__ してみて下さい。

__そのようなこと__ が __起きた原因__ は、今回行った __パラメータの修正__ が、__ルール 11__ の __条件__ の __目的に反する__ ような __修正__ だったからです。具体的には、__ルール 11__ の『__相手__ が __不利になる__ ように、「__自 0 敵 1 空 2__」が __最も少ない__ 着手を行う』という条件から、「__自 0 敵 1 他 2__」の 1 つ当たりの __評価値__ を __負の値__ にする __必要__ があります。これは、__先程求めた__、この __パラメータ__ を __正の値__ にするという __修正方法__ と __両立しない__ ので、__どのように__ パラメータを __修正__ しても __目的を達成__ することが __できない__ ことが __わかりました__。

このように、__`ai11s` ver 4__ には __問題がある__ ため、__本記事__ では __このパラメータ__ は __採用しない__ ことにします。

# 限定的な条件による問題の解決

上記から、__ルール 11__ の __条件だけ__ では、__`ai11s` ver 3__ の __問題__ を __解決__ して、__弱解決の AI を作る__ ことは __できない__ ことが __わかりました__。このような場合は、__`ai11s` ver 3__ の __問題を解決__ するための、__新しい条件__ を __考える必要__ が __あります__。

これまでは、__ルール__ に __ヒューリスティックな条件__ を __追加する際__ に、__マークのパターン__ の __数__ を __利用した条件__ を __考えてきました__ が、__今回の問題__ は __マークのパターンを利用__ しても __うまくいきません__ ので、何か __別の方法__ を __考える必要__ が __あります__。どのような方法が良いかについて少し考えてみて下さい。

## 汎用的な条件と限定的な条件

__これまで__ に __ルール__ に __加えてきた__『__自分__ が __有利になる__ ように、「__自 1 敵 0 空 2__」が __最も多い__ 着手を行う』などの、__ヒューリスティックな条件__ は、__すべての局面__ で __適用する__、__汎用的な条件__ でした。しかし、__物事__ には __例外がつきもの__ なので、__汎用的な条件__ では __対処できない__ 場合は、__例外__ に __対応する__ ために、__限定的な条件__ を __加える__ ことが __良くあります__。

例えば、__現実の世界__ での __具体例__ としては、__下記__ のような例が挙げられます。

* __信号が青__ の時は __横断歩道__ を __渡って良い__（__汎用的な条件__）
* ただし、救急車などの __緊急車両__ が __サイレンを鳴らして通る場合__ は、信号が青でも __横断歩道__ を __渡ってはいけない__（__限定的な条件__）

実際に、__世の中__ にある __ほとんどのルール__ には、「ただし、__〇〇の場合__ には __×× である__」のような、__限定的な条件__ が存在します。

## 汎用的な条件と限定的な条件の性質

__汎用的な条件__ と、__限定的な条件__ には、下記のような __利点と欠点__ があります。

* __汎用的な条件__
    * __利点__：__一つの条件__ で __多くの状況__ に __対処する__ ことが __できる__ ので、__効率的__ である
    * __欠点__：__特定の状況__ に対して、__想定外__ の __悪影響を与える__ 場合がある
* __限定的な条件__
    * __利点__：__特定の状況以外__ の状況に __悪影響を与える__ ことが __ない__
    * __欠点__：__特定の状況__ にのみ __影響を与える__ ので、__効率が悪い__

## 条件が与える影響の範囲

上記では __汎用的な条件__ と __限定的な条件__ のように、__2 種類__ に __分類__ しましたが、__実際__ には、__条件__ が __影響__ を与える __範囲__ は、__千差万別__ です。例えば、__条件__ が __影響__ を与える __範囲__ として、__以下__ のような __様々な範囲__ が __考えられます__。従って、__条件を考える__ ときは、__どのような範囲__ に __適用できるか__ について __考える__ ことも __重要__ です。

* 全世界
* 特定の国
* 特定の標高の範囲
* 特定の地域
* 特定の学校
* 特定の家族
* 特定の趣味を持つ人

__ゲーム__ で __例を挙げる__ と、__以下__ のような __様々な範囲__ が __考えられる__ でしょう。

* すべての局面
* 先手の手番の局面
* 後手の手番の局面
* 先手の必勝の局面
* 引き分けの局面
* 後手の必勝の局面
* 序盤の局面
* 中盤の局面
* 終盤の局面

## 条件を考える手順

__問題を解決__ する際に、__ヒューリスティックな条件__ を __考える__ 場合は、__一般的__ には __下記__ のような __手順__ で、__最初__ は __効率を重視__ して __広い範囲__ で __適用できる__ の __条件__ を考え、__徐々__ に __範囲を狭めながら__ 新しい __条件__ を __考えます__。意識していないかもしれませんが、多くの人は実際に下記のような手順で問題を解決しようと試みているはずです。

1. __なるべく多くの範囲__ で __問題を解決できる__ ような、__条件__ を __考える__
2. __考えた条件__ で問題が __解決されなかったり__、__問題が起きる状況__ を __検討__ する
3. __考えた条件__ が、__メリット__ のほうが __デメリットより__ も __大きい場合__ は __採用__ する
4. __手順 2__ で __明らかになった問題__ を __新しい問題__ として __設定__ し、__手順 1__ に戻る
5. 上記を、__満足のいく程度__ に __問題が解決される__ まで __繰り返す__


この方法の __問題点__ としては、__以下__ のようなものが挙げられます。多くの人は、日々そのことを実感しているのではないでしょうか。また、これらのいくつかは、[以前の記事](https://qiita.com/ysgeso/items/1e8732db6cd1b0104276#ai-%E3%81%AE%E7%A8%AE%E9%A1%9E)で説明した __ルールベース AI__ の __欠点__ でもあります。
 
* あまり __性質の良くない条件__ を __設定__ すると、__さまざまな状況__ で __悪影響__ が __発生する__
* __実際には__、上記の __手順 2__ で __検討を行っても__、__考えた条件__ が、__どのような状況__ で __悪影響を与える__ かが __分からない__ 場合が __多い__
* __実際__ に __その条件__ を用いて __問題を解決__ しようと __試みた後__ で __問題が判明__ する __場合が多い__
* __後から問題__ が __判明__ した場合、__その問題__ を __解決するため__ の __限定的な条件__ を __追加する必要__ があるが、そのような __限定的な条件__ が __多くなりすぎる__ と、__わけがわからなくなる__
* __後から発生__ した __問題を解決__ するために __加えた条件__ が、__別の問題を発生__ させたり、__以前に解決__ した __問題__ が __再び発生する__ ような場合がある
* この方法で、__問題__ が __100 % 解決できない__ 場合が __多い__

ただし、__この手順__ は上記のような欠点はあるものの、__他に良い方法__ が __無かったり__、__条件が少ないうち__ はこの方法が __有効なことが多い__ ので、__実際__ に __良く使われます__。

:::note
__問題__ が __見つかってから__、__条件を追加して対処__ するという、__場当たり的__ な __対処方法__ を続けると、本当に __わけがわからなくなります__。そのような場合は、__一から__ 条件を __考えなおしたほうが早い__ ことも __よくあります__。
:::

:::note
__法律の条文__ や __契約書__ などが、__長くて分かりづらい__ のは、__すべての状況__ に __対応する条件__ を __漏れなく記述__ しようとしているからです。__ほとんどの問題__ には、__例外的__ な __状況がある__ ので、__それらに対応__ するためには、どうしても __条件__ が __数多く__、__複雑にならざるを得ません__。また、__完璧__ に __見えた__ 場合でも、__後から欠陥が発見__ されて、__修正__ を __余儀なくされる__ ことは __良くあります__。

__プログラム__ の __バグ__ が __後から発見される__ ことが __多い理由__ も、__複雑な処理__ を行うプログラムの場合は、__100 % 正しい処理__ を __完璧__ に __記述する__ ことが __困難__ だからです。
:::

## 限定的な条件の追加によるルール 12 の定義
 
__`ai11s` ver 3__ が、__`ai2s` に敗北__ するのは、__0.7 %__ という、__かなりレアなケース__ です。また、下記のプログラムで、__`ai2`__ VS __`ai11s` ver 3__ で、__`ai11s` ver 3__ が __敗北__ する __試合経過__ を __何度も表示__ すると、__同一局面__ を __考慮する__ と __1 つ__ の __パターンしか存在しない__ ことが __分かります__。興味がある方は、下記のプログラムを何度か実行して、そのことを確認してみて下さい。

```python
show_progress(ai=[ai2, ai11s], winner=Marubatsu.CIRCLE)
```

従って、__`ai11s` ver 3__ が、__`ai2s` に敗北__ する __局面のみ__ を __対象__ とする、__下記の条件__ を、__ルール 11__ の __ヒューリスティックな条件より__ も __高い優先順位__ で __追加する__ という __方法__ が __考えられます__。なお、__この条件__ による __合法手__ の __選択__ は、__先程の検証__ によって、__最善手__ であることが __確認できている__ ので、__演繹法による条件__ です。従って、__この条件を加える__ ことによって、__他の局面__ に __悪影響を与える__ ことは __ありません__。

* __下記__ の __いずれかの局面__ の場合は、__辺のマス__ を __選択__ する

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/9bc84c40-35c5-a52f-e226-87110c408ea2.png" width="140">

下記は、__上記の条件__ を __ルール 11__ に __加えた__、__ルール 12__ の __定義__ です。__新しい条件__ は、元の __ルール 11__ の __優先順位__ が __1 ~ 4__ の __どの条件__ とも __同時に満たされる__ ことは __ない__ ので、__優先順位__ が __1 ~ 4__ の条件の __前後__ であれば、__どこに入れても構いません__。本記事では、__優先順位__ が __4 の条件__ の __次に__、__優先順位__ が __5 の条件__ として __追加__ しました。

| 順位 | 条件 | 種類 |
| :-:|:--|:--|
| 1 | __真ん中__ のマスに __優先的__ に __着手__ する  |   |
| 2 | __勝てる場合__ に __勝つ__  | 十分条件  |
| 3 | __相手__ が __勝利できる__ 着手を __行わない__  | 必要条件  |
| 4 | 「__自 2 敵 0 空 1__」が __2 つ以上存在__ する着手を行う  | 十分条件  |
| 5 | __斜め方向__ に __〇×〇__ が __並び__、__他の 6 マス__ が __空のマス__ の<br>場合は、__いずれか__ の __辺のマス__ に着手を行う  | 十分条件  |
| 6 | 「__自 2 敵 0 空 1__」が __1 つ存在__ する着手を行う<br>「__自 1 敵 0 空 2__」が __最も多い__ 着手を行う<br>「__自 0 敵 1 空 2__」が __最も少ない__ 着手を行う |
| 7 | __ランダム__ なマスに __着手__ する  |   |

## 評価値の設定

__ルール 12__ の条件に対する __評価値__ は、__下記__ の表のように __設定__ することが __できます__。具体的には、__新しく追加__ した条件の __評価値__ を __100 に設定__ し、それよりも __優先順位__ が __高い評価値__ にそれぞれ __100 を加えました__。

| 順位 | 局面の状況 | 個別 | 評価値 | 
|:-:|:--|:-:|:-:|
| 1 | __真ん中のマスに着手している__ | | `400` |
| 2 | __自分が勝利している__  | | `300` |
| 4 | __「自 2 敵 0 空 1」が 2 つ以上存在する__  | | `200` |
| 5 | __片方__ の __斜め方向__ に __〇×〇__ が __並び__、__いずれか__ の<br>__辺__ の __1 つのマスのみ__ に __× が配置__ されている  | | `100` |
| 6 | __「自 2 敵 0 空 1」が 1 つ存在する__<br>__「自 1 敵 0 空 2」が x 個存在する__<br>__「自 0 敵 1 空 2」が y 個存在する__| `2` (`0～2`)<br>`0.5 * x` (`0～4`)<br>`-y` (`-8~0`) | `-8～6`|
| 3 | __相手が勝利できる__  | | `-100` |

## `ai12s` の定義の方法

__`ai12s`__ を __定義__ するためには、ゲームの __局面__ が「__片方__ の __斜め方向__ に __〇×〇__ が __並び__、__いずれか__ の __辺__ の __1 つのマスのみ__ に __× が配置__ されている」__であるか__ どうかを __判定する必要__ が __あります__。どのような方法で判定できるかについて少し考えてみて下さい。

__上記の条件__ を __満たすため__ には、以下の __3 つの条件__ が __すべて満たされる必要__ が __あります__。

* __片方__ の __斜め方向__ に __〇×〇__ が __並ぶ__
* __いずれか__ の __辺__ の __1 マスのみ__ に __× が配置__ されている
* __他__ の __すべてのマス__ は __空のマス__ である

### 片方の斜め方向に〇×〇が並ぶ

この場合は、__以下__ の __いずれか__ の __条件__ が __満たされる必要__ が ___あります__。

* __(0, 0)__ に __〇__、__(1, 1)__ に __×__、__(2, 2)__ に __〇__ が __配置__ されている
* __(2, 0)__ に __〇__、__(1, 1)__ に __×__、__(0, 2)__ に __〇__ が __配置__ されている

__`mb`__ に __`Marubatsu` クラス__ の __インスタンスが代入__ されている場合、__上記__ を __判定する条件式__ は、__以下__ のようになります。このような条件式を記述する際には、__and 演算子__ のほうが __or 演算子より__ も __優先順位が高い点__ に __注意__ して下さい。なお、このプログラムを 1 行で記述することもできますが、わかりづらいので、適宜改行を行っています。

```python
(mb.board[0][0] == Marubatsu.CIRCLE and \
 mb.board[1][1] == Marubatsu.CROSS and \
 mb.board[2][2] == Marubatsu.CIRCLE) \
 or \
(mb.board[2][0] == Marubatsu.CIRCLE and \
 mb.board[1][1] == Marubatsu.CROSS and \
 mb.board[0][2] == Marubatsu.CIRCLE)
```

__上記__ の __条件式__ でも __問題はありません__ が、__どちらの場合__ も __(1, 1)__ に __× が配置__ されていることから、__下記__ の __2 つの条件__ が __すべて満たされるか__ どうかで __判定__ することも __できます__。

* __(1, 1)__ に __× が配置__ されている
* 下記の __いずれか__ の __条件__ が __満たされる__
    * __(0, 0)__ と __(2, 2)__ に __〇 が配置__ されている
    * __(2, 0)__ と __(0, 2)__ に __〇 が配置__ されている

__下記__ は、__上記を判定__ する __条件式__ です。__どちらを使っても構いません__ が、プログラムが __若干短くなる__ ので、__本記事__ では __下記の条件式__ を __採用する__ ことにします。

```python
mb.board[1][1] == Marubatsu.CROSS and \
(mb.board[0][0] == mb.board[2][2] == Marubatsu.CIRCLE or \
 mb.board[2][0] == mb.board[0][2] == Marubatsu.CIRCLE)
```

### いずれかの辺の 1 マスのみに × が配置されている

この場合は、__以下__ の __いずれかの条件__ が __満たされる必要__ が __あります__。

* __(1, 0)__ に __× が配置__ されている
* __(0, 1)__ に __× が配置__ されている
* __(2, 1)__ に __× が配置__ されている
* __(1, 2)__ に __× が配置__ されている

その __条件式__ は、下記のプログラムのように __記述するしかない__ でしょう。

```python
mb.board[1][0] == Marubatsu.CROSS or \
mb.board[0][1] == Marubatsu.CROSS or \
mb.board[2][1] == Marubatsu.CROSS or \
mb.board[1][2] == Marubatsu.CROSS
```

### 他のすべてのマスは空のマスである

初心者の方は、この条件を判定する方法が難しく感じられるかもしませんが、__ある事__ にさえ __気が付けば__、__簡単に判定__ することが __できる__ ので、分からない方は少し考えてみて下さい。

__〇×ゲーム__ の __性質__ から、__4 回の着手__ が行われた __局面__ で、__4 つのマス__ に __マークが配置__ されていることが __確認__ できた場合は、__残りのマス__ は必ず __空のマス__ になります。__4 つのマス__ に __マークが配置__ されていることは、__上記__ の __2 つの条件式__ で __判定できる__ ので、__その条件__ に __加えて__、__4 回の着手__ が __行われている__ ことを __判定__ することで、__他__ の __すべてのマス__ が __空のマス__ であることが __判定__ できます。

__`Marubatsu` クラス__ の __インスタンス__ には、__着手__ が __行われた回数__ を表す __`move_count` 属性__ があるので、そのことは、__下記の条件式__ で __簡単に判定__ できます。

```python
mb.move_count == 4
```

### 判定を行う条件式

__求める条件式__ は、__下記__ のプログラムのように、__上記__ の __3 つの条件式__ をすべて __and 演算子__ で __つないだもの__ です。初心者の方の多くは、下記のような複雑な条件式をつくることが難しいと思うかもしれません。__慣れないうち__ は、上記のように、__個別の条件__ を表す __条件式__ を __順番に記述__ し、__最後__ にそれらを __つなぎ合わせる__ という __方法を取る__ と __良いでしょう__。なお、ある程度 __プログラミング__ に __慣れてくれば__、下記のような __複雑な条件式__ を __一度に考える__ ことが __できるようになります__。

```python
mb.board[1][1] == Marubatsu.CROSS and \
(mb.board[0][0] == mb.board[2][2] == Marubatsu.CIRCLE or \
 mb.board[2][0] == mb.board[0][2] == Marubatsu.CIRCLE) and \
(mb.board[1][0] == Marubatsu.CROSS or \
 mb.board[0][1] == Marubatsu.CROSS or \
 mb.board[2][1] == Marubatsu.CROSS or \
 mb.board[1][2] == Marubatsu.CROSS) and \
 mb.move_count == 4
```

:::note
上記が __わかりづらい__ と __思った方__ は、__下記__ のプログラムのように __それぞれの条件式__ の __計算結果__ を、__変数に代入__ し、その __変数__ を __つなぎ合わせる__ という __方法__ が __あります__。なお、__変数の名前__ の __`cond`__ は、__条件__ を表す __condition__ の __略__ です。

```python
cond1 = mb.board[1][1] == Marubatsu.CROSS and \
        (mb.board[0][0] == mb.board[2][2] == Marubatsu.CIRCLE or \
         mb.board[2][0] == mb.board[0][2] == Marubatsu.CIRCLE)
cond2 = mb.board[1][0] == Marubatsu.CROSS or \
        mb.board[0][1] == Marubatsu.CROSS or \
        mb.board[2][1] == Marubatsu.CROSS or \
        mb.board[1][2] == Marubatsu.CROSS
cond3 = mb.move_count == 4

cond1 and cond2 and cond3
```
:::

## `ai12s` の定義

__上記__ の __条件式__ を __利用__ することで、__`ai12s`__ を __下記__ のプログラムのように __定義__ できます。

* __5、9、19 行目__：__評価値__ を __修正__ する
* __23 ~ 31 行目__：__上記の条件式__ で __判定__ を行うプログラムを __追加__ します

```python
 1  def ai12s(mb, score_201=2, score_102=0.5, score_012=-1, debug=False):   
 2      def eval_func(mb):      
 3          # 真ん中のマスに着手している場合は、評価値として 400 を返す
 4          if mb.last_move == (1, 1):
 5              return 400
 6   
 7          # 自分が勝利している場合は、評価値として 300 を返す
 8          if mb.status == mb.last_turn:
 9              return 300
10
11          markpats = mb.count_markpats()
12          if debug:
13              pprint(markpats)
14          # 相手が勝利できる場合は評価値として -100 を返す
15          if markpats[Markpat(last_turn=0, turn=2, empty=1)] > 0:
16              return -100
17          # 次の自分の手番で自分が必ず勝利できる場合は評価値として 200 を返す
18          elif markpats[Markpat(last_turn=2, turn=0, empty=1)] >= 2:
19              return 200
20       
21          # 斜め方向に 〇×〇 が並び、いずれかの辺の 1 つのマスのみに × が
22          # 配置されている場合は評価値として 100 を返す
23          if mb.board[1][1] == Marubatsu.CROSS and \
24             (mb.board[0][0] == mb.board[2][2] == Marubatsu.CIRCLE or \
25              mb.board[2][0] == mb.board[0][2] == Marubatsu.CIRCLE) and \
26             (mb.board[1][0] == Marubatsu.CROSS or \
27              mb.board[0][1] == Marubatsu.CROSS or \
28              mb.board[2][1] == Marubatsu.CROSS or \
29              mb.board[1][2] == Marubatsu.CROSS) and \
30             mb.move_count == 4:
31              return 100              
以下同じなので略
```

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

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

        markpats = mb.count_markpats()
        if debug:
            pprint(markpats)
        # 相手が勝利できる場合は評価値として -100 を返す
        if markpats[Markpat(last_turn=0, turn=2, empty=1)] > 0:
            return -100
        # 次の自分の手番で自分が必ず勝利できる場合は評価値として 200 を返す
        elif markpats[Markpat(last_turn=2, turn=0, empty=1)] >= 2:
            return 200
        
        # 斜め方向に 〇×〇 が並び、いずれかの辺の 1 つのマスのみに × が
        # 配置されている場合は評価値として 100 を返す
        if mb.board[1][1] == Marubatsu.CROSS and \
           (mb.board[0][0] == mb.board[2][2] == Marubatsu.CIRCLE or \
            mb.board[2][0] == mb.board[0][2] == Marubatsu.CIRCLE) and \
           (mb.board[1][0] == Marubatsu.CROSS or \
            mb.board[0][1] == Marubatsu.CROSS or \
            mb.board[2][1] == Marubatsu.CROSS or \
            mb.board[1][2] == Marubatsu.CROSS) and \
           mb.move_count == 4:
            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
-def ai11s(mb, score_201=2, score_102=0.5, score_012=-1, debug=False):   
+def ai12s(mb, score_201=2, score_102=0.5, score_012=-1, debug=False):   
    def eval_func(mb):      
        # 真ん中のマスに着手している場合は、評価値として 400 を返す
        if mb.last_move == (1, 1):
-           return 300
+           return 400
    
        # 自分が勝利している場合は、評価値として 300 を返す
        if mb.status == mb.last_turn:
-           return 200
+           return 300

        markpats = mb.count_markpats()
        if debug:
            pprint(markpats)
        # 相手が勝利できる場合は評価値として -100 を返す
        if markpats[Markpat(last_turn=0, turn=2, empty=1)] > 0:
            return -100
        # 次の自分の手番で自分が必ず勝利できる場合は評価値として 200 を返す
        elif markpats[Markpat(last_turn=2, turn=0, empty=1)] >= 2:
-           return 100
+           return 200
        
        # 斜め方向に 〇×〇 が並び、いずれかの辺の 1 つのマスのみに × が
        # 配置されている場合は評価値として 100 を返す
+       if mb.board[1][1] == Marubatsu.CROSS and \
+          (mb.board[0][0] == mb.board[2][2] == Marubatsu.CIRCLE or \
+           mb.board[2][0] == mb.board[0][2] == Marubatsu.CIRCLE) and \
+          (mb.board[1][0] == Marubatsu.CROSS or \
+           mb.board[0][1] == Marubatsu.CROSS or \
+           mb.board[2][1] == Marubatsu.CROSS or \
+           mb.board[1][2] == Marubatsu.CROSS) and \
+          mb.move_count == 4:
+           return 100              
以下同じなので略
```
</details>

## `ai2` との対戦

下記のプログラムで、__`ai2`__ との __対戦__ を行います。

```python
ai_match(ai=[ai12s, ai2])
```

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

```
ai12s VS ai2
count     win    lose    draw
o        9907       0      93
x        8820       0    1180
total   18727       0    1273

ratio     win    lose    draw
o       99.1%    0.0%    0.9%
x       88.2%    0.0%   11.8%
total   93.6%    0.0%    6.4%
```

__実行結果__ から、ついに __`ai12s`__ の __敗率__ が、__どちらの手番__ を __担当した場合__ でも __0 %__ になりました。__対戦回数__ が __足りない__ ため、__偶然 `ai12s`__ が __敗北__ する __対戦__ が __生じなかったのではないか__ と __心配する方__ は、実行に時間はかかりますが、下記のプログラムのように、__対戦回数__ を 10 倍の __10 万回__ にして __対戦__ を行ってみて下さい。なお、筆者のパソコンで、下記のプログラムの実行には約 5 分かかりました。

```python
ai_match(ai=[ai12s, ai2], match_num=100000)
```

実行結果

```
ai12s VS ai2
count     win    lose    draw
o       98919       0    1081
x       88168       0   11832
total  187087       0   12913

ratio     win    lose    draw
o       98.9%    0.0%    1.1%
x       88.2%    0.0%   11.8%
total   93.5%    0.0%    6.5%
```

実行結果から、__10 万回__ でも __敗率が 0 %__ になったので、__`ai12s`__ は __弱解決の AI__ である __可能性__ が __非常に高い__ ことが __確認__ できました。

下記は、__上記の結果__ と、__これまで__ の __`ai11s`__ が __`ai2`__ と __対戦__ した際の __結果__ を表に __まとめたもの__ です。

| 関数名 | o 勝 | o 負 | o 分 | x 勝 | x 負 | x 分 | 勝 | 負 | 分 |
|:-:|--:|--:|--:|--:|--:|--:|--:|--:|--:|
| __[`ai11s` ver 1](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 |
| __[`ai11s` ver 2](https://qiita.com/ysgeso/items/5fe28ae6c765a169fd34#ai11s-%E3%81%AE%E4%BF%AE%E6%AD%A3)__ | __98.8__ | __0.0__ | 1.2 | __87.7__ | __2.4__ | 10.0 | __93.2__ | __1.2__ | 5.6 |
| __`ai11s` ver 3__  | __99.1__ | __0.0__ | 0.9 | __87.7__ | __0.8__ | 11.5 | __93.4__ | __0.4__ | 6.2 | 
| __`ai12s`__  | __98.9__ | __0.0__ | 1.1 | __88.2__ | __0.0__ | 11.8 | __93.5__ | __0.0__ | 6.5 | 

これで、__ルールベース__ の __AI__ で、__弱解決の AI__ を __作成__ することが __できました__。__`ai12s`__ が __最強の AI__ であることは、__現時点__ では __証明できません__ が、今後の記事で __探索型の AI__ で __最強の AI__ を __作成する__ ので、__その際__ に __最強の AI と対戦__ させることで、__`ai12s`__ が __最強の AI であるか__ どうかを __確認する__ ことにします。

# 今回の記事のまとめ

今回の記事では、__`ai11s` ver 3__ の __パラメータ__ を __修正__ し、さらに __新しいルール__ を __付け加える__ ことで、`ai12s` という __弱解決の AI__ を作成しました。残念ながら、__現時点__ では、__`ai12s`__ が __最強の AI__ であることの __証明は行えません__ が、__ルールベース__ で __作成する AI__ としては、__最も強い AI__ が __作れた__ のではないかと思います。


なお、これでルールベースの AI の話は終わったのではないかと思う人がいるかもしれませんが、あと 数回ほど、ルールベースの AI の話は続きます。

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

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

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

今回の記事では marubatsu.py は変更していません。

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

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

# 次回の記事

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