Pythonで〇×ゲームのAIを一から作成する　その60　弱解決の定義と性質とその判定方法

# 目次と前回の記事

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

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

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

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

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

https://github.com/ysgeso/marubatsu/blob/master/060/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 | あり |

# 最強の AI であるかどうかの判定

[前回の記事](https://qiita.com/ysgeso/items/d7f60ba34f167f77a87e#%E6%BC%94%E7%B9%B9%E6%B3%95)で説明した、__演繹法__ を使って、__最強の AI__ の __定義__ を __完全に満たす__ ような __ルール__ の __条件__ で __AI を 作成__ した場合は、間違いなく __最強の AI__ です。一方、少しでも __ヒューリスティック__ の __手法__ が用いられた __ルール__ の __条件__ で __作られた AI__ が、本当に __最強の AI であるか__ を知るためには、__検証__ を行う __必要__ が __あります__。

作成した __AI__ が __最強の AI であるか__ どうかを __検証__ するためには、下記の __最強の AI__ の __定義__ の __通り__、__すべての局面__ で __最善手__ を __選択するか__ どうかを __調べる必要__ が __あります__。

:::note warn
__最強の AI__ とは、__すべての局面__ で、__最善手__ を __選択する AI__ のことである。
:::

しかし、__下記__ のような __理由__ で、そのようなことを行うのは __容易ではありません__。

* オセロ、勝利、囲碁[^1]などのように、__局面の数__ が __多すぎる__ 場合は、__現実的__ に、__すべての局面__ で __最善手を選択するか__ どうかを __調べる__ ことが __不可能__ である
* __AI__ が、__すべての局面__ で __最善手を選択する__ ことを __判定するため__ には、__すべての局面__ の __最善手__ が __何であるか__ が __分かっている必要__ がある。それは、__最強の AI であるか__ どうかを __判定するため__ に、__最強の AI__ が __必要になる__ ということであり、__矛盾__ する

従って、__演繹法__ で __最強の AI__ を __作れない場合__ に、__ヒューリスティック__ な __手法__ で __作成された AI__ が __最強の AI であるか__ どうかを __判定する__ ことは、__多くのゲーム__ では __ほぼ不可能__ です。ただし、__最強の AI__ であることは __わからなくても__、__他の AI__ と __比較__ して、__相対的に強いか__ どうかであれば、__他の AI__ との __[総当たり戦](https://qiita.com/ysgeso/items/3d340c07f3d515ce5fe9#%E7%B7%8F%E5%BD%93%E3%81%9F%E3%82%8A-%E3%81%A8-%E5%8B%9D%E3%81%A1%E6%8A%9C%E3%81%8D%E6%88%A6-%E3%81%AB%E3%82%88%E3%82%8B%E6%96%B9%E6%B3%95)__ や、__[レーティング](https://qiita.com/ysgeso/items/3d340c07f3d515ce5fe9#%E3%83%AC%E3%83%BC%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0%E3%81%AB%E3%82%88%E3%82%8B%E5%BC%B7%E3%81%95%E3%81%AE%E5%AE%9A%E7%BE%A9)__ などの __方法__ で __判定__ することは __可能__ なので、__一般的__ には __それらの方法__ で、__特定の AI の中__ での、__最も強い AI__ を __判定__ します。また、__実際__ に、__それらの方法__ は __さまざまな競技__ で __用いられています__。

## 弱解決 の AI であるかの判定方法

__これまで__ の記事では、__〇×ゲーム__ の __新しい AI__ を __作成__ した際に、その __強さを検証__ するために、__ランダムな AI__ との __対戦__ と、__それまで__ に __作成__ した __最も強い AI__ との __対戦__ を行ってきました。__それまで__ の __最も強い AI__ と __対戦する__ ことは、__新しい AI__ の __強さを検証__ する上で、__直観的__ に __理にかなっている__ と __思う人が多い__ と思います。しかし、__実際__ には、__それまで__ の __最も強い AI__ と __対戦するだけ__ では、__新しく作成__ した __AI__ が __強くなったか__ どうかを __判定できる__ とは __限りません__。

その __理由の一つ__ は、__対戦成績__ が __良かった__ としても、__作成した AI__ が __偶然__ それまでの中で __最も強いの AI__ の __弱点__ を __つくことができる__、__相性の良い AI__ である __可能性がある__ からです。これは __対戦成績__ が __悪かった場合__ でも同様です。

__他の理由__ としては、__前回の記事__ で __検証__ を行った、__修正後__ の __`ai11`__ VS __`ai10s`__ で行われる __[着手の図](https://qiita.com/ysgeso/items/3d340c07f3d515ce5fe9#%E4%BF%AE%E6%AD%A3%E5%BE%8C%E3%81%AE-ai11s-vs-ai10s-%E3%81%AE%E6%A4%9C%E8%A8%BC)__ を見ればわかるように、__強い AI__ は、__ルール__ の __条件__ が __厳しくなる__ ために、__特定__ の __着手__ のみを __選択する傾向__ があります。分かりづらいと思った方は、__修正前__ の  __`ai11s`__ VS __`ai10s`__ で行われる __[着手の図](https://qiita.com/ysgeso/items/3d340c07f3d515ce5fe9#%E4%BF%AE%E6%AD%A3%E5%89%8D%E3%81%A8%E4%BF%AE%E6%AD%A3%E5%BE%8C%E3%81%AE-ai11s-vs-ai10s-%E3%81%AE%E6%A4%9C%E8%A8%BC)__ と __見比べて下さい__。

そのため、__強い AI どうし__ で __対戦__ を行っても、__〇× ゲーム__ の __特定の局面__ に対する __検証しか行えません__。従って、__強い AI どうし__ で __対戦した際__ に、__出現しなかった局面__ でも、__新しい AI__ が __最善手__ を __選択するか__ どうかの __検証__ を行うことは __できません__。

一方、__AI の強さ__ を __検証__ する際に、__明らか__ に __弱い AI__ である、__ランダムな AI__  と __対戦__ することの __必要性__ に __疑問を感じた人__ はいないでしょうか？__ランダムな AI__ は、__平均的__ にみると __弱い AI__ ですが、__ありとあらゆる着手__ を __行う可能性がある__ ので、繰り返して __何度も戦う__ と、その中の __いくつかの対戦__ で __常に最善手を選択__ する __可能性が生じます__。__実際__ に、__〇×ゲーム__ のような、__局面の種類__ があまり __多くないゲーム__ では、__数万回対戦__ を行うことで、その中の __いくつかの試合__ では __ランダムな AI__ が __最善手のみ__ を __選択する__ ことが __期待できます__。

__〇×ゲーム__ は、__ゲーム開始時__ の __局面の状況__ は __引き分け__ の局面であるので、__最強の AI__ であれば、__どのような AI__ と __対戦__ しても、__負ける__ ことは __決してありません__。従って、__ランダムな AI__ と __数多く対戦__ した __結果__、__一度__ も __負けることがなければ__、__最強の AI__ である __可能性が高い__ と __判断する__ ことが __できます__。これが、__これまで__ の記事で、__新しい AI__ を __作成するたび__ に、__ランダムな AI__ と __対戦した__ これまでに説明していなかった __理由__ です。

__ランダムな AI__ と __数多く対戦する__ ことで、__最強の AI であるか__ どうかを __判定する__ という __方法__ は、〇×ゲームだけではなく、__理論的__ には __すべて__ の __[二人零和有限確定完全情報ゲーム](https://qiita.com/ysgeso/items/3d340c07f3d515ce5fe9#%E4%BA%8C%E4%BA%BA%E9%9B%B6%E5%92%8C%E6%9C%89%E9%99%90%E7%A2%BA%E5%AE%9A%E5%AE%8C%E5%85%A8%E6%83%85%E5%A0%B1%E3%82%B2%E3%83%BC%E3%83%A0%E3%81%AE%E6%80%A7%E8%B3%AA)__ に __適用できます__。ただし、__厳密__ には、__判定できる__ のは、__最強の AI であるか__ どうか __ではなく__、この後で説明する __弱解決の AI であるか__ どうかの __判定__ です。また、__現実的__ に __判定できる__ のは、〇×ゲームのような、__局面の数__ が __少ないゲーム__ に __限られます__。

今回の記事では、そのようなことができる __具体的な理由__ と __方法__ について __説明__ します。

## ゲームの解決とその種類

__ゲーム__ の __ある局面__ から、__お互い__ が __最善手のみ__ を __選択__ した場合の __結果__ が __判明している__ ことを、__ゲーム__ が __解決された__（solved）と呼び、以下の __3 種類__ に __分類__ されています[^2]。

* __超弱解決__（ultra weakly solved）
ゲームの __開始時__ の __局面の状態__ は __判明__ しているが、その __局面の状況以上__ の __結果__ になる __合法手__ を __選択する方法__ が __判明していないゲーム__ のこと[^3]。__本記事__ では __取り扱いません__

* __弱解決__（weakly solved）
ゲームの __開始時__ の __局面の状態__ が __判明__ しており、__どのような相手と対戦__ しても、その __局面の状況以上__ の __結果__ になる __合法手__ を __選択する方法__ が __判明しているゲーム__ のこと
* __強解決__（strongly solved）
__すべての局面__ で、__局面の状況__ と、__最善手__ が __判明しているゲーム__ のこと

__強解決__ の __意味__ は __簡単__ で、__最強の AI__ の __定義__ から、__強解決__ された __ゲームである__ ということと、__最強の AI__ が __作れる__ ということは、__同じ意味__ を持ちます。

一方、__弱解決__ の __意味__ は __わかりづらい__ と思いますので、__詳しく説明__ します。

__最強の AI どうし__ が、__実際__ に __対戦を行う場合__ のことを考えると、__すべての局面__ で __最善手__ を __みつけることができる必要__ は __ありません__。

例えば、__`ai11s`__ は、__ルール 11__ の「__真ん中__ のマスに __優先的__ に __着手__ する」という __条件__ から、__〇 を担当__ した際に、__1 手目__ では __下図__ の __左__ の __(1, 1)__ の __合法手のみ__ を __選択__ します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/d891db42-e364-8299-c237-59e2e422d27e.png" width="200">

このことから、__`ai11s`__ が __ゲームの開始時__ から __対戦を行う際__ には、__1 手目__ で、__上図__ の __真ん中__ や __右__ のような、__隅__ や __辺__ の __マス__ への __着手__ が行われる __局面__ は __決して生じない__ ことが __わかります__。従って、__`ai11s`__ は、__それらの局面__ で __最善手__ を __選択できる必要__ は __ありません__。

__強解決__ と __弱解決__ の __違い__ は以下のようになります。

* __強解決__ されたゲームは、__すべての局面__ の __最善手__ が __判明__ している
* __弱解決__ されたゲームは、__必要な局面のみ__ で、__目的を達成__ できる __合法手__ が __判明__ している

本記事では、__弱解決__ の __定義を元__ に、__弱解決の AI__ を __以下__ のように __定義__ します。

:::note warn
__弱解決の AI__ とは、__ゲーム開始時__ の __局面__ から、__どのような AI__ と __対戦__ しても、__ゲーム開始時__ の __局面の状況以上__ の __結果__ になる __合法手を選択__ する __AI__  のことである。
:::

:::note
__弱解決の AI__ という呼び方は、__最強の AI__ と __区別するため__ に __筆者が作った__ 本記事 __独自__ の __呼び方__ です。おそらく、__他では使われていない__ と思います。

なお、__これまで__ の __最強の AI__ のことを、__強解決の AI__ と呼ぶこともできますが、本記事では __弱解決の AI__ と __強く区別したい場合__ を __除いて__、これまで通り __最強の AI__ と __表記する__ ことにします。
:::

__弱解決 の AI__ は、__最善手__ を __選択できる必要__ がある __局面の数__ が、__最強の AI__ と __比べて大幅に少ない__ ので、__最強の AI より__ は __簡単に作る__ ことが __できます__。

__実際__ に、__オセロ__ は、2024 年 3 月の時点では、__強解決されていません__ が、2023 年 10 月に __弱解決がされた__ という __[論文](https://arxiv.org/abs/2310.19387) が発表__ されました。

:::note
とはいえ、__将棋__ や __囲碁__ などの、__オセロより__ かなり __複雑なゲーム__ では、__弱解決の AI__ であっても __作成__ することは __ほぼ不可能__ だと __考えられています__
:::

## 弱解決でよくある誤解

上記で、「__弱解決__ されたゲームは、__必要な局面のみ__ で、__目的を達成__ できる __合法手__ が __判明__ している」と説明しましたが、__強解決の説明__ と __同様__ に、「__弱解決__ されたゲームは、__必要な局面のみ__ で、__最善手__ が __判明__ している」のように __説明しなかった点__ が __気になっている__ 人はいないでしょうか？__筆者__ も __最初__ は __そのように考えていた__ のですが、__よく考える__ とその説明では __間違っている__ のではないかと思いました。ただし、これは __筆者__ の __勘違いかもしれない__ ので、間違っていたらコメントで指摘していただけると嬉しいです。

実際に、「__弱解決__」を __検索__ すると、「ゲーム開始時の局面から、__お互い__ が __最善手を選択__ した場合の __結果がわかっている__ ゲームのこと」のような __説明__ が __いくつか発見__ できました。また、__下記の論文__ の __96 ページ__ でも、__弱解決__ を「初期局面の勝敗が解明されていて、それを証明するのに必要な __各局面__ の __最善手__ も __分かっている__」のように説明しています。

https://www.jstage.jst.go.jp/article/sugaku/65/1/65_0651093/_pdf/-char/ja

しかし、下記の論文の __8 ページ__ にある __本来__ の __弱解決の定義__ で __述べられている__、__必要な局面__ で __ゲームの開始時__ の __局面の状況以上__ になるような __合法手__ を __選択できる__ ことと、__最善手__ が __わかっている__ ことは __同じではありません__。

http://fragrieu.free.fr/SearchingForSolutions.pdf

そのことを理解するためには、__弱解決の AI__ の __性質__ を __理解する必要__ があるので、その __説明を行いながら__、__弱解決の誤解の意味__ を説明します。

## 弱解決 の AI の性質

__二人零和有限確定完全情報ゲーム__ には、__先手__ と __後手__ があります。そのため、__先手を担当__ した場合は __弱解決 の AI__ や __最強の AI__ であるが、__後手を担当__ した場合は __そうではない__ という場合が __あります__。これは、__先手と後手__ を __入れ替えた場合__ も __同様__ です。

:::note warn
__一般的__ には __弱解決の AI__ や __最強の AI__ は、__先手と後手__ の __両方__ で __その性質を持つ AI__ のことを __指す__ ので、__本記事__ でも、__そのような意味__ でそれらの __表記を行います__。

また、__片方__ の __手番だけ__ で __性質を満たす__ 場合は、__先手の弱解決の AI__ のように、__手番つけて表記__ することで、__区別する__ ことにします。
:::

次に、__弱解決の AI__ の __性質__ を、ゲームが __先手の必勝__、__引き分け__、__後手の必勝__ の __3 つの場合__ と、__先手__ と __後手__ を担当する __場合__ に __分けて説明__ します。

### 先手の必勝のゲームで、先手を担当する場合

__弱解決の AI__ が __先手を担当__ する場合は、__ゲーム開始時__ の __局面__ は __必勝の局面__ です。__勝利より__ も __良い状況__ は __存在しない__ ので、__勝利以上__ の __結果__ は __勝利だけ__ です。従って、__弱解決の AI の定義__ から __弱解決の AI__ は、この場合は __必ず勝利__ する __必要__ があります。

[以前の記事](https://qiita.com/ysgeso/items/3d340c07f3d515ce5fe9#%E4%B8%80%E8%88%AC%E7%9A%84%E3%81%AA%E6%84%8F%E5%91%B3%E3%81%AE%E6%9C%80%E5%96%84%E3%81%A8%E6%9C%80%E5%96%84%E6%89%8B%E3%81%AE%E6%84%8F%E5%91%B3%E3%81%AE%E9%81%95%E3%81%84)で説明したように、__最善手を選択するか__ どうかで、__局面の状況__ は __下記__ の表のようになります。__この表__ から、__局面の状況__ が __悪化した場合__ は、__相手__ が __最善手__ を __選択し続ける__ と、__元の状況__ に __戻ることはない__ ことが __わかります__。従って、__どのような AI__ に対しても __勝利する__ ためには、__弱解決の AI__ は __常に最善手__ を __選択__ する __必要がある__ 事が __わかります__。

|  | 局面の状況 |
|:-:|:-:|
| __最善手を選択する__  | 変化しない  |
| __最善手を選択しない__  | 悪化する  |

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

:::note warn
__先手の必勝のゲーム__ で、__ゲームの開始時__ の __局面__ から __弱解決の AI__ が __先手を担当__ する場合は、__以下__ のような __性質__ がある

* __出現__ する __すべての局面__ で __最善手を選択__ する
* __相手__ が __どのような AI__ であっても __100 % 勝利__ する
* __最強の AI__ と __同じ強さ__ を持つ
:::

従って、__この場合__ は、__弱解決の AI__ は、__最強の AI__ と __同じ強さ__ を持ち、「__弱解決__ されたゲームは、__必要な局面のみ__ で、__最善手__ が __判明__ している」という説明は __正しい説明__ です。

### 先手の必勝のゲームで、後手を担当する場合

ゲームの __開始時の局面__ が __先手の必勝の局面__ の場合で、__後手を担当__ する場合は、__後手の立場__ から見ると、__後手の必敗の局面__ です。__敗北以上__ の __結果__ は、__勝利__、__引き分け__、__敗北__ という、__すべての結果__ を __含みます__。従って、__弱解決の AI の定義__ から __弱解決の AI__ は、__出現__ する __すべての局面__ で、__どのような着手__ を __選択__ しても __構わない__ ことになります。つまり、__この場合__ は、__すべての AI__ が __弱解決の AI である__ と __言える__ ということです。

__別の言葉__ で __説明__ すると、__この場合__ は、__弱解決の AI__ は、__出現__ するどの __局面__ でも __最善手__ を __知る必要がない__ ということです。従って、この場合は、「__弱解決__ されたゲームは、__必要な局面のみ__ で、__最善手__ が __判明__ している」という __説明__ は __間違っています__。

一方、__最強の AI__ は、__すべての局面__ で __最善手を選択__ するので、__この場合__ は、__最強の AI__ と __弱解決の AI__ は __同じ強さ__ では __ありません__。

具体例としては、__先手の必勝の局面__ で、__先手__ が __最善手__ を __選択しなかった場合__ は、__引き分けの局面__ または __先手の必敗の局面__ になります。__後手__ が __最強の AI__ であれば、そのような場合でも __常に最善手を選択__ するので、決して __敗北する__ ことは __ありません__。一方、__弱解決の AI__ は __そのような局面__ で __最善手を選択__ するとは __限らない__ ので、__敗北__ してしまう __可能性__ が __あります__。これが、__最強の AI__ と __弱解決の AI__ の __大きな違い__ です。

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

:::note warn
__先手の必勝のゲーム__ で __後手を担当__ する場合は、__以下__ のような __性質__ がある。

* 勝利、引き分け、敗北の __すべての結果__ になる __可能性がある__
* __すべての AI__ が __後手の場合__ は __弱解決の AI__ である
* __最善手__ を __選択__ するとは __限らない点__ が、__最強の AI__ とは __異なる__
:::

### 後手の必勝のゲームの場合

__後手の必勝のゲーム__ で __先手を担当__ する場合は、__先手の立場__ から見ると、__先手の必敗の局面__ です。__後手の必勝のゲーム__ で __後手を担当__ する場合は、__必勝の局面__ です。

従って、__先手の必勝のゲーム__ の場合と __同様の考え方__ で __それぞれの性質__ を __知る__ ことができ、__下記__ のように、__一つにまとめる__ ことができます。

なお、『「__先手の必勝のゲーム__ で、__先手を担当__ する」または「__後手の必勝のゲーム__ で、__後手を担当__ する」』のような表記は長いので、そのことを __自分の必勝のゲーム__ のように __表記__ することにします。__自分の必敗のゲーム__ も __同様の意味__ です。

:::note warn
__自分の必勝のゲーム__ で、__ゲームの開始時__ の __局面__ から __対戦__ を行った場合は、__弱解決の AI__ には __以下の性質__ がある。

* __出現__ する __すべての局面__ で __最善手を選択__ する
* __相手__ が __どのような AI__ であっても __100 % 勝利__ する
* __最強の AI__ と __同じ強さ__ を持つ

__自分の必敗のゲーム__ で、__ゲームの開始時__ から __対戦__ を行った場合は、__以下__ のような __性質__ がある。

* 勝利、引き分け、敗北の __すべての結果__ になる __可能性がある__
* __すべての AI__ が __自分の手番__ の場合は __弱解決の AI__ である
* __最善手__ を __選択__ するとは __限らない点__ が、__最強の AI__ とは __異なる__
:::

### 引き分けのゲームの場合

__ゲーム開始時__ の __局面の状況__ が __引き分けの局面__ 場合は、__弱解決の AI__ が __先手を担当__ しても、__後手を担当__ しても、その __定義__ から __結果__ は __引き分け以上__ になります。従って、この場合は、__弱解決の AI__ は __どのような AI__ と __対戦__ しても __敗北する__ ことは __ありません__。そのためには、__弱解決の AI__ が __どのような合法手__ を __選択__ する __必要があるか__ について __説明__ します。

__相手__ が __最善手__ を __選択し続けた__ 場合、__局面の状況__ が __良くなる__ ことは __ない__ ので、__弱解決の AI__ は、__必敗の局面__ につながる __合法手__ を __選択__ しては __いけない__ ことが __わかります__。

従って、__引き分けの局面__ では、__弱解決の AI__ は、常に __最善手__ を __選択する必要__ が __あります__。

__弱解決の AI__ が __対戦する相手__ が、__最善手__ を __選択しない__ 場合は、__弱解決の AI__ の __手番の局面__ が __自分の必勝の局面__ になる __場合__ が __あります__。この場合も、__弱解決の AI__ は __最善手__ を __選択__ する __必要がある__ と __思う人__ が __いるかもしれません__ が、__そうではない__ 点に __注意__ して下さい。

その理由は、__自分の必勝の局面__ から、__引き分けの局面__ につながるような、__最善手ではない__ 合法手を __選択しても__、__引き分け以上__ の __結果__ になるという __条件__ が __満たされる__ からです。__この点__ が、__常に最善手__ を __選択__ する、__最強の AI__ との __違い__ です。

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

:::note warn
__引き分けのゲーム__ で、__ゲームの開始時__ の __局面__ から __対戦__ を行った場合は、__弱解決の AI__ には __先手と後手__ の __いずれを担当__ した場合でも、__以下__ のような __性質__ がある。

* __どのような AI__ と __対戦__ しても __敗北__ する __ことはない__
* __自分の必勝の局面__ では __必敗の局面__ につながる __合法手以外__ を __選択__ する。__この点__ が、__最強の AI__ とは __異なる__
* __引き分けの局面__ では、__最善手を選択__ する
:::

### 弱解決の AI の性質のまとめ

__弱解決の AI__ は、__ゲームの開始時__ の __局面__ から __対戦__ を行った際に __生じる局面__ に対して、__局面の状況__ によって、__下記__ のような __合法手__ を __選択__ します。なお、__空欄__ は、__そのような局面__ が __生じない__ ことを意味します。

|  | 必勝の局面 | 引き分けの局面 | 必敗の局面 |
|:--|:-:|:-:|:-:|
| __自分の必勝のゲーム__  | 最善手  |   |   |
| __引き分けのゲーム__  | 必敗の局面につながる<br>以外の合法手  | 最善手  |   |
| __自分の必敗のゲーム__  | 任意の合法手  | 任意の合法手  | 最善手[^4]  |

表から、__弱解決の AI__ は、__ゲーム開始時__ の __局面の状況__ と __同じ状況の局面__ では、__常に最善手__ を __選択__ する AI であることが __わかります__。そのため、__相手__ が __常に最善手を選択__ する場合は、下記の表のように、__弱解決の AI__ と __最強の AI__ は __同じ結果__ になります。

|  | 弱解決の AI VS 最強の AI | 最強の AI VS 最強の AI |
|:-:|:-:|:-:|
| __自分の必勝のゲーム__   | 100 % 勝利  | 100 % 勝利  |
| __引き分けのゲーム__   | 100 % 引き分け  | 100 % 引き分け |
| __自分の必敗のゲーム__  | 100 % 敗北  | 100 % 敗北  |

一方、試合で __相手__ が __最善手以外__ を __1 回以上選択__ する場合は、__以下__ のようになります。このことから、__弱解決の AI__ は、__相手__ が __最善手を選択しない__ という、__ミス__ に __つけこむ__ という __能力を持っていない__ ということが分かります。

|  | 弱解決の AI | 最強の AI |
|:-:|:-:|:-:|
| __自分の必勝のゲーム__   | 100 % 勝利  | 100 % 勝利  |
| __引き分けのゲーム__   | 勝利、引き分け  | 100 % 勝利 |
| __自分の必敗のゲーム__  | 勝利、引き分け、敗北 | 勝利、引き分け  |

また、__弱解決の AI__ は、__ゲーム開始時__ の __局面__ から __着手の選択__ を行った場合に __出現__ する __可能性__ のある __局面以外__ では、__自分の必勝の局面__ であっても __最善手を選択__ する __能力を持ちません__。そのため、例えば __人間どうし__ が行った __対戦__ の __途中の局面__ から、__弱解決 の AI__ が __引き継いで対戦__ を行った場合に、その __局面の状況__ よりも悪い結果になる場合があります。

最近では、将棋や囲碁の __対局の中継__ で、__AI による形成判断__ が __表示される場合__ が良くありますが、そのような __形成判断__ は、__弱解決__ を __目的__ として __作られた AI__[^5] では __行えません__。

## ランダムな AI による弱解決の AI の判定

ここまでで説明した、__弱解決の AI__ の __性質__ から、__ランダムな AI__ を __利用する__ ことで、__原理的__ には、AI が __弱解決の AI であるか__ を __判定__ することが __できます__ 。その理由と手順について少し考えてみて下さい。

### 弱解決の AI であることの判定手順

具体的には、__下記の手順__ で __AI__ が __弱解決の AI であるか__ を __高確率__ で __判定__ することが __できます__。なお、__条件 1__ の __具体的な意味__ は、この後で説明します。

:::note warn
1. __判定したい AI__ と __ランダムな AI__ を、__先後を入れ替えて__、__すべての AI__ と __高確率__ で __1 回以上対戦__ することが __できる__ ように、__十分な数だけ対戦__ する
2. __下記__ の __いずれか__ の場合は、__判定する AI__ は __弱解決の AI__ であり、__いずれの条件__ も __満たさない__ 場合は、__弱解決の AI ではない__
    * __先手__ または、__後手__ の __いずれかの場合__ に __勝率__ が __100 %__ になる
    * __先手__ と __後手__ のいずれも __敗率__ が __0 %__ になる
:::

このような、__乱数__（ランダムなもの）を使って __問題を解決する方法__ の事を __モンテカルロ法__ と呼びます。__モンテカルロ法__ は、__[ヒューリスティック](https://qiita.com/ysgeso/items/d7f60ba34f167f77a87e#%E3%83%92%E3%83%A5%E3%83%BC%E3%83%AA%E3%82%B9%E3%83%86%E3%82%A3%E3%83%83%E3%82%AF)__ な __手法の一種__ なので、__100 % 正しい__ かどうかは __判定できません__。従って、上記では __高確率__ という __表現__ を行いました。

__モンテカルロ法__ については、__AI を作成する方法__ の一つとして、__今後の記事__ で __取り扱う予定__ です。参考までに、モンテカルロ法の Wikipedia のリンクを下記に示します。

https://ja.wikipedia.org/wiki/%E3%83%A2%E3%83%B3%E3%83%86%E3%82%AB%E3%83%AB%E3%83%AD%E6%B3%95#:~:text=%E3%83%A2%E3%83%B3%E3%83%86%E3%82%AB%E3%83%AB%E3%83%AD%E6%B3%95%20%EF%BC%88%20%E3%83%A2%E3%83%B3%E3%83%86%E3%82%AB%E3%83%AB%E3%83%AD%E3%81%BB%E3%81%86%20%E3%80%81%20%E8%8B%B1,%E3%81%AB%E3%82%88%E3%82%8A%E5%91%BD%E5%90%8D%E3%81%95%E3%82%8C%E3%81%9F%E6%89%8B%E6%B3%95%E3%80%82

__上記の方法__ で __判定できる理由__ を少し考えてみて下さい。

### ランダムな AI の性質

__AI__ が __弱解決な AI であるか__ を __判定するため__ には、__定義__ から __どのような AI__ と __対戦__ しても、__ゲーム開始__ の __局面の状況__ よりも __悪化しない__ ことを __確認する必要__ があります。

__単純__ に __考えると__、そのようなことを __判定する__ ことは __難しい__ と __思えるかも__ しれませんが、__ランダムな AI__ を __利用する__ ことで __そのような判定__ を行うことが __できます__。その __理由__ は __ランダムな AI__ の __以下__ の __性質__ にあります。

__ランダムな AI__ は、__すべての局面__ で、__すべての合法手__ を __選択__ する __可能性がある AI__ です。従って、__ランダムな AI__ は、__世の中__ に __存在__ する __すべての AI__ が __選択する着手__ と、__まったく同じ着手__ を __選択する可能性__ があります。そのため、__ランダムな AI__ と __何度も対戦する__ ことで、__理論上__ は __すべての AI__ と __1 回以上対戦__ することが __できます__。また、__その中__ には __ランダムな AI__  の __すべての着手__ が、__最強の AI__ が __選択__ する __合法手と同じ__ である __試合__ があります。

:::note
これは、__下手な鉄砲も数撃てば当たる__ という、__でたらめな行動__ であっても、__何回も行えば__、偶然 __うまくいく場合がある__ という意味の __ことわざ__ に相当します。
:::

ただし、これはあくまで __理論上の話__ で、オセロ、将棋、囲碁といった、__局面の数__ が __多いゲーム__ では、__現実的__ では __ありません__。その __理由__ は、__すべての AI__ と __1 回以上対戦__ するということは、__その AI__ が __対戦した際__ に __出現__ する可能性がある __すべての局面__ が、__何度も行う対戦__ の中で __1 度は出現__ する __必要がある__ ということを __意味__ するからです。

例えば、__オセロ__ は、__一回の対戦__ で、基本的には __60 回の着手__ を行うので、__約 60 種類__ の __局面__ しか __出現しません__。__オセロ__ は、__局面の数__ が $10^{28}$ __もある__ といわれているので、__最低__ でも $10^{28}$ / 60 = __約 $10^{26}$ 回以上__ の __対戦__ を __行わなければ__、__すべての局面__ が __出現しません__。従って、オセロでは、__ランダムな AI__ と __対戦__ することで、__すべての AI__ と __1 回以上対戦__ するためには、__現実的でないほど__ の、__途方もない回数__ の __対戦__ を __行う必要__ が __あります__。

:::note
__上記__ を、__サイコロ__ を使った __例え__ で __説明__ します。__サイコロ__ を __2 つ振った__ 場合の __合計__ は、__2 ~ 12__ までの __11 種類__ があります。これを __ゲームの局面__ に __対応__ させて考えてみて下さい。__サイコロの出目__ は __ランダム__ なので、__サイコロ__ を __2 つ__ 、__2 ~ 12__ までの __すべての出目__ が __出現するまで振る__ ことは、__すべての局面__ が __出現するまで__、__ランダムな AI と対戦__ することに（全く同じではありませんが）__似ています__。

プログラムの説明は省略しますが、__下記__ は __2 ~ 10 個まで__ の __サイコロを振った__ 際に、__何回__ で __すべての種類__ の __出目が出現するか__ を __数えて表示__ するプログラムです。

<details><summary><strong>プログラム（興味がある方はクリックしてみて下さい）</strong></summary>

```python
from random import randint

def dice(num):
    """ num 個のサイコロの合計を返す """
    total = 0
    for _ in range(num):
        # randint は実引数で指定した範囲のランダムな整数を返す
        total += randint(1, 6)
    return total

def countall(num):
    """ num 個のサイコロを振った場合に、すべての出目が出るまでの回数を返す"""
    
    # 出現した出目を記録する set
    s = set()
    # 出目の種類を計算する。最小値が num で 最大値が num * 6
    left = num * 5 + 1
    # サイコロを振った数を数える変数を初期化する
    count = 0
    # 全ての出目が出るまで繰り返す
    while left > 0:
        # サイコロを振る
        dicenum = dice(num)
        # 振った回数を増やす
        count += 1
        # s の要素に登録されているかを判定する
        if dicenum not in s:
            # 登録されていなければ、left を 1 減らし s に出目を登録する
            left -= 1
            s.add(dicenum)
    return count

# 2 ~ 10 個のそれぞれの場合の回数を表示する
for i in range(2, 11):
    print(i, countall(i))
```
</details>

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

```
2 45
3 333
4 3746
5 2333
6 99256
7 833719
8 4610084
9 10782768
10 51201303
```

__実行結果__ からわかるように、__サイコロの数__ が __増える__ と、__すべての出目__ が __出現するまで__ の __回数__ が __大幅に増えていき__、__10 個振った場合__ は __約 5000 万回__ もかかることが __わかります__。なお、__筆者のパソコン__ では、__上記__ の __実行__ に __約 5 分__ かかりました。
:::

一方、__〇×ゲーム__ は、__局面の数__ が __5478__ のように[^6]  __少ない__ ので、__１万回程度__ の __ランダムな AI__ との __対戦__ で、__高確率__ で、__すべての AI__ と __1 回以上対戦__ することが __できます__。

### 判定手順で、弱解決の AI であることを判定できる理由

次に、先程示した、__下記__ の __判定手順__ で、__弱解決の AI であるか__ を __判定__ することができる __可能性が高い理由__ について __説明__ します。なお、__以降の説明__ では、実際に __すべての AI__ と __1 回以上対戦できた__ ことを __前提__ に __説明__ します。運悪く、その __前提__ が __満たされなかった場合__ は、__弱解決の AI__ であると __正しく判定できない__ 場合が __あります__。それが、上記で「__判定__ することができる __可能性が高い__」と表記した __理由__ です。

:::note warn
1. __判定したい AI__ と __ランダムな AI__ を、__先後を入れ替えて__、__すべての AI__ と __高確率__ で __1 回以上対戦__ することが __できる__ ように、__十分な数だけ対戦__ する
2. __下記__ の __いずれか__ の場合は、__判定する AI__ は __弱解決の AI__ であり、__いずれの条件__ も __満たさない__ 場合は、__弱解決の AI ではない__
    * __先手__ または、__後手__ の __いずれかの場合__ に __勝率__ が __100 %__ になる
    * __先手__ と __後手__ のいずれも __敗率__ が __0 %__ になる
:::

下記は、__弱解決の AI__ が、__相手__ が __最善手のみを選択__ する場合と、__そうでない場合__ の __試合の結果__ をまとめた表です。

|  | 相手が最善手のみを選択 | そうでない場合 |
|:-:|:-:|:-:|
| __自分の必勝のゲーム__  | 100 % 勝利  | 100 % 勝利  |
| __引き分けの__ ゲーム | 100 % 引き分け  | 勝利、引き分け |
| __自分の必敗のゲーム__ | 100 % 敗北  | 勝利、引き分け、敗北 |

__自分の必勝のゲーム__ とは、__先手が必勝__ のゲームで __先手を担当__ する場合か、__後手が必勝__ のゲームで __後手を担当__ する __場合__ のことを表します。

__自分の必敗のゲーム__ とは、__先手が必勝__ のゲームで __後手を担当__ する場合か、__後手が必勝__ のゲームで __先手を担当__ する __場合__ のことを表します。

そこで、__先手の必勝__、__後手の必勝__、__引き分けのゲーム__ に __分けて説明__ することにします。

### 先手の必勝のゲーム

__弱解決の AI__ が __先手を担当__ する場合は、上記の表から __100 % 勝利__ します。一方、__弱解決の AI__ では __ない__ AI の場合は、その __定義__ から、__ゲーム開始時__ の __局面の状況__ よりも __悪化する場合がある__ ので、__すべての AI__ と __対戦__ を行った場合は、__いずれかの対戦__ で __勝利できない__ 場合が __あります__。従って、__勝率__ は __100 %__ に __なりません__。

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

|  | 勝率 | 引き分け率 | 敗率 |
|:--|:-:|:-:|:-:|
| __弱解決の AI__  | 100 %  |  0 % | 0 % |
| __弱解決の AI ではない__  | 100 % 未満  | 0 ~ 100 %  | 0 ~ 100 % |

__先手の必勝のゲーム__ で、__後手を担当__ した場合は、先程説明したように、__すべての AI__ が __後手の場合__ は __弱解決の AI__ になります。従って、__先手の必勝__ のゲームでは、__弱解決の AI であるか__ どうかは、__その AI__ が __先手を担当__ した __場合のみ__ で __判定すればよい__ ことが分かります。

__後手を担当__ した場合に、__相手__ が __常__ に __最善手を選択__ した場合は、__敗北する__ ので、__敗率__ は __必ず 0 % より__ も __大きく__ なります。__下記__ は、__上記の考察__ を __まとめた表__ です。


|  | 勝率 | 引き分け率 | 敗率 |
|:--|:-:|:-:|:-:|
| __後手を担当するすべての AI__  | 0 ~ 100 % 未満  | 0 ~ 100 % 未満  | 0 % より大きい |

下記は、__先手__ と __後手__ の __勝率__ と __敗率__ の __関係__ をまとめたものです。__△__ は、__どちらの可能性もある__ 事を意味します。

|  | 弱解決の AI | 弱解決でない AI |
|:--|:-:|:-:|
| __先手__ の場合に __勝率__ が __100 %__ になる  | 〇  | ×  |
| __後手__ の場合に __勝率__ が __100 %__ になる  | ×  | ×  |
| __先手__ の場合に __敗率__ が __0 %__ になる  | 〇  | △  |
| __後手__ の場合に __敗率__ が __0 %__ になる  | ×  | ×  |

__上記の表__ から、__先手の必勝のゲーム__ の場合は、下記の __判定方法__ が __正しい__ ことが分かります。なお、下記の __2 つ目の条件__ は、__引き分けのゲーム__ の場合に __必要となる条件__ であり、__先手の必勝のゲーム__ の場合は __常に満たされません__。


__下記__ の __いずれか__ の場合は、__判定する AI__ は __弱解決の AI__ であり、__いずれの条件__ も __満たさない__ 場合は、__弱解決の AI ではない__

* __先手__ または、__後手__ の __いずれかの場合__ に __勝率__ が __100 %__ になる
* __先手__ と __後手__ のいずれも __敗率__ が __0 %__ になる

### 後手の必勝の場合

__先手__ と __後手__ を __入れ替えて考える__ ことで、__上記__ と __同じ考察__ ができるので、__後手の必勝__ の場合も、上記の __判定方法__ が __正しい__ ことが分かります。

### 引き分けのゲーム場合

__引き分けのゲーム__ の場合は、__先手__ と __後手__ の __どちらを担当__ しても、__ゲーム開始時__ の __局面の状況__ は __引き分けの局面__ です。従って、以下の説明は、__先手を担当__ した場合と __後手を担当__ した場合の __両方__ に __当てはまります__。

__引き分けのゲーム__ の場合は、__弱解決の AI__ の __定義__ から、__どのような AI__ と __対戦__ しても、__ゲーム開始時__ の __局面の状態__ よりも __悪化することはない__ ので、__敗北__ することは __ありません__。一方、__弱解決の AI__ では __ない__ AI の場合は、__ゲーム開始時__ の __局面の状態__ よりも __悪化する場合がある__ ので、__敗率__ は __0 % より__ も __大きくなります__。

また、__引き分けのゲーム__ の場合は、__相手__ が __最善手のみ__ を __着手__ した場合に、__必ず引き分けになる__ ので、__勝率__ が __100 %__ になることは __ありません__。

下記は、__先手__ と __後手__ の __両方の場合__ の __勝率__ と __敗率__ の __関係__ をまとめたものです。

|  | 弱解決の AI | 弱解決でない AI |
|:--|:-:|:-:|
| __勝率__ が __100 %__ になる  | ×  | ×  |
| __敗率__ が __0 %__ になる  | 〇  | ×  |

__上記の表__ から、__引き分けのゲーム__ の場合は、下記の __判定方法__ が __正しい__ ことが分かります。

__下記__ の __いずれか__ の場合は、__判定する AI__ は __弱解決の AI__ であり、__いずれの条件__ も __満たさない__ 場合は、__弱解決の AI ではない__

* __先手__ または、__後手__ の __いずれかの場合__ に __勝率__ が __100 %__ になる
* __先手__ と __後手__ のいずれも __敗率__ が __0 %__ になる

なお、__先手の場合__ に __敗率__ が __0 % になっても__、__後手の場合__ に __敗率__ が __0 % にならなければ__、__弱解決の AI__ では __ない__ 点に注意して下さい。

以上で、__先手の必勝__、__後手の必勝__、__引き分け__ の __すべての場合__ で、__上記の手順__ で __弱解決の AI であるか__ どうかを __高確率__ で __判定__ することが __できる__ ことが __わかりました__。

:::note
繰り返しになりますが、__上記__ の __判定方法__ は、オセロ、将棋、囲碁のような、__局面の数が多いゲーム__ では __利用できない__ 点に __注意__ して下さい。その __理由__ は、__局面の数__ が __多い場合__ は、__ランダムな AI__ と対戦することで、__すべての AI__ と __1 回以上対戦__ することができる __可能性__ が __ほぼ 0 になる__ からです。
:::

# ランダムな AI との対戦による `ai11s` の検証

__実際__ に __`ai11s`__ と __ランダムな AI__ である __`ai2`__ を __対戦__ させることで、__`ai11s`__ が __弱解決の AI であるか__ を __判定する__ ことにします。その際に、[以前の記事](https://qiita.com/ysgeso/items/fc19eb2764e0766b59d5#ai_match-%E3%81%B8%E3%81%AE%E4%BB%AE%E5%BC%95%E6%95%B0%E3%81%AE%E8%BF%BD%E5%8A%A0)で、__`ai_match`__ を、__仮引数 `params`__ に __AI__ の __パラメータ__ を __代入__ するように __修正__ したので、[以前の記事](https://qiita.com/ysgeso/items/fc19eb2764e0766b59d5#%E4%BF%AE%E6%AD%A3%E3%81%97%E3%81%9F%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF%E3%81%A7%E3%81%AE-ai10s-%E3%81%A8%E3%81%AE%E5%AF%BE%E6%88%A6) で説明したように、下記のプログラムのように __記述する必要__ がある点に __注意__ して下さい。

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

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

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

```
ai11s VS ai2
count     win    lose    draw
o        9879       0     121
x        8767     236     997
total   18646     236    1118

ratio     win    lose    draw
o       98.8%    0.0%    1.2%
x       87.7%    2.4%   10.0%
total   93.2%    1.2%    5.6%
```

| 関数名 | o 勝 | o 負 | o 分 | x 勝 | x 負 | x 分 | 勝 | 負 | 分 |
|:-:|--:|--:|--:|--:|--:|--:|--:|--:|--:|
| __[`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 |
| 修正後 の __`ai11s`__  | __98.8__ | __0.0__ | 1.2 | __87.7__ | __2.4__ | 10.0 | __93.2__ | __1.2__ | 5.6 |

上記は、__`ai9s`__、__`ai10s`__、__修正前__ と __修正後__ の __`ai11s`__ のそれぞれが __`ai2`__ と __対戦__ した場合の __対戦結果__ の表です。__〇×ゲーム__ は、__ゲーム開始時__ の __局面の状態__ が __引き分けのゲーム__ なので、__表__ から __以下__ のようなことが __わかります__。

* __`ai9s`__ は __〇 を担当__ した場合も、__× を担当した__ 場合も、__弱解決の AI ではない__
* __`ai10s`__ と __修正前__ と __修正後__ の __`ai11s`__ は、__〇 を担当__ した場合は、__敗率が 0 %__ なので、__〇 を担当__ した場合のみ __弱解決の AI__ である __可能性が高い__。従って、__ルール 10__ で __ルール 9 に追加__ した __条件__ によって、__〇 を担当__ した場合は __弱解決の AI__ になった
* __いずれの AI__ も、__× を担当__ した場合に __敗率__ が __0 % ではない__ ので、__弱解決の AI ではない__

次に、`ai11s` が __〇 を担当__ した場合に __弱解決の AI ではない__ ことの __理由__ を __検証__ し、__`ai11s`__ の __ルール__ を __改善__ することにします。

## `ai_match` などの改良

__`ai11s`__ が __〇 を担当__ した場合に __弱解決の AI ではない__ ことの __理由__ を __検証__ する前に、このままでは、__`ai_match`__ が __使いづらい__ ので __改良__ することにします。

### `ai_match` の問題点

先程は、__下記__ のような __プログラム__ で、__`ai_match`__ を __呼び出し__ ました。__`ai11s`__ の __パラメータ__ は、__AI__ の __ルールの修正__ を __行う際__ には、__頻繁に変更__ しますが、__最適__ と思われる __パラメータ__ が __決まった後__ は、__頻繁に変更__ することは __ありません__。そのため、__パラメータ__ が __決まった後__ でその AI で __対戦を行う際__ に、下記のプログラムのように、__毎回パラメータ__ を __実引数に記述__ するのは __かなり面倒__ です。


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

__下記__ のプログラムのように、__昔__ の __`ai_match` と同様__ に、__`params`__ を __記述しなくても__ AI どうしの __対戦を行える__ と __便利__ だとは思いませんか？下記のように記述できるようにするための方法を少し考えてみて下さい。

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

### `ai11s` の修正

__`ai_match`__ の __`params`__ に __記述__ した __パラメータのデータ__ は、__[マッピング型の展開](https://qiita.com/ysgeso/items/fc19eb2764e0766b59d5#play-%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%A8-ai_match-%E3%81%AE%E4%BF%AE%E6%AD%A3)__ によって、__`ai11s`__ が __呼び出される際__ に、__キーワード引数__ として __記述__ されます。従って、__`ai11s`__ の __パラメータ__ を代入する __仮引数__ を、__調整済みのパラメータ__ を __デフォルト値__ とする、__デフォルト引数__ に __変更__ することで、__`ai11s`__ で __対戦する際__ に、毎回 __調整済のパラメータ__ を __記述__ する __必要__ が __なくなります__。__下記__ は、そのように __`ai11s` を修正__ した __プログラム__ です。

* __4 行目__：__評価値を計算__ する際に __利用__ する __パラメータ__ を __代入__ する __仮引数__ を、__調整したパラメータ__ を __デフォルト値__ とする、__デフォルト引数__ に __修正__ する

```python
1  from marubatsu import Marubatsu, Markpat
2  from ai import ai_by_score
3
4  def ai11s(mb, score_201=2, score_102=1, score_012=-1, debug=False):
5      def eval_func(mb):  
元と同じなので省略
```

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

```python
from marubatsu import Marubatsu, Markpat
from ai import ai_by_score

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

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

__上記の修正__ が __正しく動作するか__ を __確認__ するために、下記のプログラムで、__パラメータ__ を __実引数__ に __記述せず__ に、__`ai11s` を呼び出し__ てみます。__実行結果__ から、__パラメータ__ を __記述しなくても__、__`ai11s`__ を __呼び出せる__ ことが __確認__ できます。

```python
mb = Marubatsu()
print(ai11s(mb))
```

実行結果

```
(1, 1)
```

また、__上記の修正__ により、__`ai_match`__ で __対戦を行う際__ には、__`params`__ の __対応する要素__ には、下記のプログラムのように、__空の dict__ を __記述するだけで済む__ ようになります。なお、実行結果は、先程と変わらないので省略します。

```python
ai_match(ai=[ai11s, ai2], params=[{}, {}])
```

### `ai_match` の修正

__デフォルト値__ の __パラメータ__ を使って、__`ai_match`__ で __対戦を行う__ 際に、上記のプログラムのように、__`params`__ の __要素__ に __空の dict__ を __記述__ するのも __面倒__ です。そこで、下記のプログラムのように、__`ai_match`__ の __仮引数 `params`__ を、__デフォルト値__ が __`[{}, {}]`__ の __デフォルト引数__ にすることで、__`params`__ に対応する __実引数__ も __省略できる__ ようになります。

```python
from collections import defaultdict

def ai_match(ai, params=[{}, {}], match_num=10000):
    print(f"{ai[0].__name__} VS {ai[1].__name__}")
元と同じなので省略
```

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

```python
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, params, match_num=10000):
+def ai_match(ai, params=[{}, {}], match_num=10000):
    print(f"{ai[0].__name__} VS {ai[1].__name__}")
元と同じなので省略

```
</details>

__上記の修正__ を行うことで、__下記__ のプログラムのように、__`ai_match`__ に __`params`__  __に対応する実引数__ を __記述__ する __必要__ が __なくなります__。なお、実行結果は省略します。

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

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

[以前の記事](https://qiita.com/ysgeso/items/e78980b366e123ca57f1#%E6%8C%87%E5%AE%9A%E3%81%97%E3%81%9F%E6%9D%A1%E4%BB%B6%E3%81%AE%E8%A9%A6%E5%90%88%E7%B5%8C%E9%81%8E%E3%82%92%E8%A1%A8%E7%A4%BA%E3%81%99%E3%82%8B%E9%96%A2%E6%95%B0%E3%81%AE%E5%AE%9A%E7%BE%A9)で作成した __`show_progress`__ を使って、__`ai2`__ VS __`ai11s`__ で __`ai11s`__ が __敗北する理由__ を __検証__ します。しかし、__下記__ のプログラムを __実行する__ と、実行結果のように __エラーが発生__ します。このようなエラーが発生する理由を少し考えてみて下さい。

```python
from ai import show_progress

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

実行結果

```
略
--> 178     if mb.play(ai=ai, verbose=False) == winner:
    179         records = mb.records
    180         mb.restart()

TypeError: Marubatsu.play() missing 1 required positional argument: 'params'
```

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

* __TypeError__
データ型（type）に関するエラー
* __play() missing 1 required positional argument: 'params'__
play メソッドに必要とされる（required）、params という位置引数（positional argument）が記述されていない（missing）
:::

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

__このエラー__ は、__`play`__ メソッドに __仮引数 `params`__ が __追加__ されているのに、__`show_progress`__ で、__`play`__ メソッドを __呼び出す際__ に、__`params`__ の __実引数__ が __記述されていない__ からです。

__この問題__ を __解決__ する __方法__ の一つは、__下記__ のプログラムのように、__`play`__ メソッドの __仮引数 `params`__ を __デフォルト引数__ にするという方法です。なお、その方法は、[以前の記事](https://qiita.com/ysgeso/items/af8b5dda0ddffd5c6d13#%E9%96%A2%E6%95%B0%E3%81%AE%E3%83%87%E3%83%95%E3%82%A9%E3%83%AB%E3%83%88%E5%BC%95%E6%95%B0%E3%82%92%E5%88%A9%E7%94%A8%E3%81%97%E3%81%A6%E4%BA%92%E6%8F%9B%E6%80%A7%E3%82%92%E6%8C%81%E3%81%A4)で説明した __関数の互換性__ を持つ方法です。

```python
def play(self, ai, params=[{}, {}], verbose=True):
元と同じなので省略

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, params, verbose=True):
+def play(self, ai, params=[{}, {}], verbose=True):
元と同じなので省略

Marubatsu.play = play

```
</details>

__上記の修正__ を行うことで、下記のプログラムを実行しても __エラーが発生しなくなります__。

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

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

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

Turn o
...
.X.
o..

Turn x
...
.xO
o..

Turn o
X..
.xo
o..

Turn x
x..
.xo
o.O

Turn o
x..
Xxo
o.o

winner o
x.O
xxo
o.o
```

### `show_progress` の修正

上記のように、__`show_progress`__ によって、__`ai2`__ VS __`ai11s`__ で、__`ai11s`__ が __敗北__ する __試合の経過__ を __表示できています__ が、__このまま__ では、__`show_progress`__ を __呼び出す際__ に __AI のパラメータ__ を __指定できない__ ので、__`show_progress`__ にも下記のプログラムのように、__デフォルト引数 `params`__ を __追加__ し、__`play`__ メソッドを __呼び出す際__ に、__キーワード引数__ として __`params=params`__ を __記述する__ ように __修正__ します。

* __1 行目__：__`[{}, {}]`__ を __デフォルト値__ とする __デフォルト引数 `params`__ を __追加__ する
* __4 行目__：__`play`__ メソッドの __呼び出しの際__ に、__キーワード引数__ として __`params=params`__ を __記述する__

```python
 1  def show_progress(ai, winner, params=[{}, {}]):
 2      mb = Marubatsu()
 3      while True:
 4          if mb.play(ai=ai, verbose=False, params=params) == winner:
 5              records = mb.records
 6              mb.restart()
 7              for x, y in records:
 8                  mb.move(x, y)
 9                  print(mb)
10              break
```

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

```python
def show_progress(ai, winner, params=[{}, {}]):
    mb = Marubatsu()
    while True:
        if mb.play(ai=ai, verbose=False, params=params) == winner:
            records = mb.records
            mb.restart()
            for x, y in records:
                mb.move(x, y)
                print(mb)
            break
```
</details>

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

```diff_python
-def show_progress(ai, winner):
+def show_progress(ai, winner, params=[{}, {}]):
    mb = Marubatsu()
    while True:
-       if mb.play(ai=ai, verbose=False) == winner:
+       if mb.play(ai=ai, verbose=False, params=params) == winner:
            records = mb.records
            mb.restart()
            for x, y in records:
                mb.move(x, y)
                print(mb)
            break
```
</details>

__下記__ のプログラムで、__上記の修正__ を行っても __`show_progress`__ が __正しく動作__ することが __確認__ できます。なお、実行結果は先ほどと同様なので省略します。

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

長くなりましたので、__`ai2`__ VS __`ai11s`__ の検証は次回の記事で行うことにします。

# 今回の記事のまとめ

今回の記事では、__弱解決__ と __弱解決の AI__ の __定義__ と __性質__ を説明し、__〇×ゲーム__ のように、__局面の数__ が __少ないゲーム__ では、__ランダムな AI__ と __十分な回数の対戦__ を行うことで、__AI__ が __弱解決の AI であるか__ どうかを __判定できる__ ことを __示しました__。

また、__いくつかの関数__ を、使いやすいように __修正__ し、__`ai2`__ VS __`ai11s`__ で __`ai11s`__ が __敗北__ する __試合経過__ を表示しました。

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

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

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

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

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

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

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

# 次回の記事

https://qiita.com/ysgeso/items/70c50f75319373399871

[^1]:[以前の記事](https://qiita.com/ysgeso/items/3d340c07f3d515ce5fe9#%E4%BA%8C%E4%BA%BA%E9%9B%B6%E5%92%8C%E6%9C%89%E9%99%90%E7%A2%BA%E5%AE%9A%E5%AE%8C%E5%85%A8%E6%83%85%E5%A0%B1%E3%82%B2%E3%83%BC%E3%83%A0%E3%81%AE%E6%80%A7%E8%B3%AA)で説明したように、この中で __最も局面の数__ が __少ないオセロ__ でも、局面の数は $10^{28}$ __以上ある__ と __言われています__
[^2]:__本記事の説明__ は、__元の定義__ を __少し意訳__ しています。__原文の定義__ を知りたい方は、http://fragrieu.free.fr/SearchingForSolutions.pdf の __8 ページ__ を見て下さい
[^3]:__具体例__ を知りたい方は、https://www.jstage.jst.go.jp/article/sugaku/65/1/65_0651093/_pdf/-char/ja の __96 ページ__ を見て下さい
[^4]:__必敗の局面__ では、__すべての合法手__ が __最善手__ です
[^5]:「__目的__ として __作られた__」という __表現__ になっているのは、__将棋__ も __囲碁__ も、__現時点__ では __弱解決__ も __強解決__ も __されていない__ からです
[^6]:__局面の種類__ の __数__ が __5478__ であることは、__探索型の AI__ の __説明の際__ に __示します__