Pythonで〇×ゲームのAIを一から作成する　その55　必要条件と十分条件と、複数の条件を考慮した評価値

# 目次と前回の記事

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

https://qiita.com/ysgeso/items/085fd15fbd91d5cdf770

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

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

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

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

基準となる __`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 |

# 用語の定義

これまでの記事で行ってきたいくつかの表記は、長く分かりづらいので、今回の記事から、下記の表記を行うことにします。

| 表記 | これまでの記事での表記 |
|:--|:--|
| __ライン__  | __直線__（line）上に __並んだ 3 マス__  |
| __「自 x 敵 y 空 z」が存在する着手__  | __自分の手番__ で __着手__ を行った __結果__、「__自 x 敵 y 空 z__」が __1 つ以上__ 存在する __局面になる着手__  |

# 必要条件と十分条件

これまでの記事では、__強い AI__ を作るために、__さまざまな条件__ を考えてきましたが、それらの __条件__ は、__必要条件__、__十分条件__、その __どちらでもない条件__ に __分類__ されます。この __分類__ は、__条件__ によって AI が __強くなるかどうか__ に大きく関わる __重要な概念__ なので、今回の記事では、最初にこの __分類__ について、__ルール 8__ の __条件__ を元に __説明__ します。

## ルール 8 の条件

__ルール 8__ には、__さまざまな条件__ がありますが、最後の「__ランダムなマスに着手する__」という __条件__ は、__強い AI__ を作るための __条件ではない__ ので、それを __削除__ した、__下記の条件__ について __考える__ ことにします。以後の説明では、それぞれの条件を、__条件 1__ のように __番号__ を使って __表記__ することにします。

1. __真ん中__ のマスに __優先的__ に __着手__ する
2. そうでない場合は __勝てる場合__ に __勝つ__
3. そうでない場合は __相手__ が __勝利できる__ 着手を __行わない__
4. そうでない場合は、__次__ の __自分の手番__ で __勝利できる__ ように、__「自 2 敵 0 空 1」が存在する着手__ を行う

## 十分条件

ある __目的を達成__ するために、その __条件__ を __満たしていれば__ 必ず __目的を達成できる__ ような __条件__ のことを、__十分条件__ と呼びます。ルール 8 の条件の中で、「__勝てる場合__ に __勝つ__」という __条件 2__ は、__必ず勝利できる__ ので、__強いAI__ の __目的を達成できる__、__十分条件__ です。

__十分条件__ を __満たす__ ことで、必ず __目的を達成__ することが __できる__ ので、__ルール__ に __十分条件__ を __組み込む__ ことで、__多くの場合__ で AI を __強くする__ ことが __できます__。また、__どんな場合__ でも AI が __弱くなること__ は __ありません__。

ピンとこない人がいると思いますので、そのようなことが起きる __理由__ について説明します。〇× ゲームの __ある局面__ が、__下記の条件__ を __満たす__ 場合の事を考えてみて下さい。

* __3 つの合法手__ A、B、C が __存在__ する
* __合法手 A__ が 〇×ゲームに勝つための __十分条件__ を __満たし__、__残り__ の B と C は __満たさない__

この __十分条件__ を __ルール__ に __組み込んだ AI__ と __そうでない AI__ が __選択__ する __着手__ と、その __結果__ は __以下の表__ のようになります。

|  | 着手 | 結果 |
|:--|:--|:--|
| __組み込んだ AI__  | A  | __必ず勝利__ する  |
| __組み込んでいない AI__  | A、B、C | __A を選んだ場合__ は __必ず勝利__ する<br>__B、C を選んだ場合__ はどうなるか __わからない__  |

表からわかるように、この __十分条件__ を __組み込んだ AI__ のほうが、__組み込んでいない AI__ よりも __勝率が悪くなる__ ことは __決してありません__。また、上記では、合法手の数を 3 つとしましたが、__合法手の数__ が __いくつであっても同様__ です。

なお、__以下__ の __条件__ が満たされる場合は、__AI の強さ__ は __変わりません__。

* __A 以外__ を __選択__ した __後の局面__ に対して、__ルールを適用__ した __結果__、__必ず勝利__ する場合
* ルールの __他の条件__ によって、__A 以外__ が __選択されない__ 場合
* __A 以外__ の __合法手__ が __存在しない__ 場合

:::note
上記の説明は、[以前の記事](https://qiita.com/ysgeso/items/ff28a95dff50322e6b16#%E3%83%AB%E3%83%BC%E3%83%AB-5-%E3%81%AE%E6%A4%9C%E8%A8%BC)での __ルール 5__ の __検証__ と __同様__ ですが、今回はルール 5 __だけでなく__、__十分条件__ を __満たす場合__ に __一般化__ しています。
:::

:::note warn
__十分条件__ を __ルールに組み込むこ__ とで、__AI__ が __強くならない場合__ があるが、__弱くなる__ ことは __決してない__ ので、__十分条件__ であることが __分かっていれば__、 __安心して__ ルールに __組み込む__ ことが __できる__。
:::

__一般的__ に、__十分条件__ は、__目的__ を __達成する__ ための __条件の一部__ でしかありません。そのことは、__条件 2 のみ__ を __ルール__ とする、__[ルール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)__ によって作られた __`ai5`  より__ も __強い AI__ を、実際に __作成できている__ ことから __明らか__ でしょう。

## 必要条件

ある __目的を達成__ するために、__必要__ となる __条件__ のことを __必要条件__ と呼びます。ルール 8 の、「__相手__ が __勝利できる__ 着手を __行わない__」という __条件 3__ は、この __条件を満たす着手__ を __行わない__ と、次の相手の手番で __相手が勝利してしまう__ 可能性があるので、__必要条件__ です。

__必要条件__ は、__目的の達成__ を __保証__ するものでは __ありません__ が、少なくとも __その条件__ を __満たさなければ__、__目的を達成__ することは __不可能__ なので、AI のルールに __必要条件__ を __加える__ ことで、__AI__ が __強くなること__ が __期待できます__。また、AI が __弱くなる__ ことは __ありません__。

ピンとこない人がいると思いますので、そのようなことが起きる __理由__ について説明します。〇× ゲームの __ある局面__ が、__下記の条件__ を __満たす__ 場合の事を考えてみて下さい。

* __3 つの合法手__ A、B、C が __存在__ する
* __合法手 A__ が 〇×ゲームに勝つための __必要条件__ を __満たし__、__残り__ の B と C は __満たさない__

この __必要条件__ を __ルール__ に __組み込んだ AI__ と __そうでない AI__ が __選択__ する __着手__ と、その __結果__ は __以下の表__ のようになります。

|  | 着手 | 結果 |
|:--|:--|:--|
| __組み込んだ AI__  | A  | どうなるかは __わからない__  |
| __組み込んでいない AI__  | A、B、C | __A を選んだ場合__ はどうなるかは __わからない__<br>__B、C を選んだ場合__ は、__勝利__ することは __ない__  |

表から、__組み込んでいない AI__ は __絶対__ に __勝利できない B__ と __C__ を __選択する可能性__ がありますが、__組み込んだ AI__ は、それらを __選択__ することは __ありません__。そのため、__組み込んだ AI__ のほうが、__組み込んでいない AI__ よりも __勝率が悪くなる__ ことは __決してありません__。また、上記では、合法手の数を 3 つとしましたが、__合法手の数__ が __いくつであっても同様__ です。

なお、__以下__ の __条件__ が満たされる場合は、__AI の強さ__ は __変わりません__。

* __A__ を __選択__ した __後の局面__ に対して、__ルールを適用__ した __結果__、__必ず勝利できない__ 場合
* ルールの __他の条件__ によって、__A 以外__ が __選択されない__ 場合
* __A 以外__ の __合法手__ が __存在しない__ 場合

:::note warn
__必要条件__ を __ルールに組み込むこ__ とで、__AI__ が __強くならない場合__ があるが、__弱くなる__ ことは __決してない__ ので、__必要条件__ であることが __分かっていれば__、 __安心して__ ルールに __組み込む__ ことが __できる__。
:::

__十分条件__ と __必要条件__ で、上記のように __同じ結論__ が得られましたが、それぞれの __結論がでる理由__ は以下のように __異なります__。

* __十分条件__ は、__目的__ を __達成する選択__ を __行う__ ことで、AI を強くできる
* __必要条件__ は、__目的__ を __達成しない選択__ を __行わない__ ことで、AI を強くできる

なお、あまりにも __緩い必要条件__ を __満たしても__、__目的の達成__ には __ほとんど寄与しない__ 点に __注意__ して下さい。例えば、__テストで 100 点を取る__ という __目的__ に対する __必要条件__ として、__テストを受ける__ という __条件__ が考えられます。__テストを受けなければ__ 絶対に __100 点を取れない__ ので、__この条件__ はれっきとした __必要条件__ ですが、よほど簡単なテストでない限り、__テストを受けるだけ__ で __100 点をとる__ ことができることは __まずない__ でしょう。

__必要条件__ と __十分条件__ を __ルールに加える__ ことで、__AI__  を __強くできる根拠__ を示しましたが、__必要条件__ と __十分条件__ を __むやみに加えても__、AI が思ったように __強くならない__ ことが良くあります。次は、__どのような__ 必要条件と十分条件を __考えればよいか__ について __説明__ します。

## 必要十分条件

__必要条件__ と __十分条件__ の __両方を満たす__ ような __条件__ のことを __必要十分条件__ と呼び、__以下__ のような __性質__ を持ちます。

* __目的__ を __達成できない条件__ は __含まれない__
* __目的__ を __達成できる条件__ を __すべて含む__

__必要十分条件__ の数は __一つ__ ですが、__必要条件__ や __十分条件__ を満たす条件は __複数__ あります。

__必要条件__、__十分条件__、__必要十分条件__ の __関係__ を図で書くと、__下図__ のようになります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/50842244-f518-534b-a9f8-7ede42eba1de.png" width="250">

__それぞれの条件__ を、[以前の記事](https://qiita.com/ysgeso/items/3b8967122379079a2a82#%E5%8C%85%E5%90%AB%E9%96%A2%E4%BF%82%E3%81%A8%E7%9B%B8%E5%BD%93%E9%96%A2%E4%BF%82)で説明した、__集合__ として考えると、それぞれの条件は、下記のような __包含関係__ にあります。これらのことから、__必要条件__、__必要十分条件__、__十分条件__ の __順__ で、__条件が厳しくなる__ ことがわかります。

$必要条件 ⊃ 必要十分条件 ⊃ 十分条件$

__必要十分条件__ を __知る__ ことで、__その条件__ に __従うこと__ が __できれば__[^1]、__必ず目的を達成__ することが __できます__。しかし、__実際__ には、__必要条件__ や __十分条件__ が __わかっても__、__必要十分条件__ が何であるかがはっきりと __わからない__ ことが __良くあります__。実際に、__〇×ゲーム__ の __強い AI を作成__ するための __必要条件__ と __十分条件__ は既に __いくつか示しました__ が、__必要十分条件__ を考えることは __簡単__ なこと __ではありません__。

わかりやすいように、〇×ゲーム以外の __具体例__ を挙げて説明します。「__漢字のテストで良い点数をとる__」という __目的__ に対する __必要条件__ ははっきりとは __分かりません__ が、__必要条件__ や __十分条件__ であれば、下記のように __簡単に考える__ ことが __できます__。

* __必要条件__ の __一つ__ は「__漢字の勉強をする__」である
* __十分条件__ の __一つ__ は「__国語辞典の漢字をすべて暗記する__」である

ただし、上記のような __簡単すぎる必要条件__ は目的を達成の __役に立ちません__ し、__厳しすぎる十分条件__ は __実行することが困難__ という意味であまり __役に立ちません__。

__必要十分条件__ が __分からない場合__ は、__できるだけ厳しい__ 条件の __必要条件__ と、__できるだけ緩い__ 条件の __十分条件__ を考え、__2 つ__ の __中間の性質__ を持つ、__必要十分条件__ に __近い条件__ で __目的の達成__ を __試みる__ ことになります。例えば、上記の例の場合は __より厳しい必要条件__ と、__より緩い十分条件__ として下記のような条件を考え、その __中間の条件__ で勉強を行います。

* __必要条件__ に __勉強時間__ を __指定__ した「__漢字の勉強を 1 日 30 分勉強する__」を考える
* 漢字のテストには __漢字の意味__ は __必要ない__ ので、__十分条件__ を __緩めた__「__国語辞典の漢字の読み方だけをすべて暗記する__」を考える

__下図左__ は __元の条件__ を、__下図右__ は __新しい条件__ を __図で表した__ ものです。__実際に採用__ するのは __色のついている部分__ の __いずれかの条件__ で、__新しい条件__ を考えることで、__色のついている部分__ の __面積が減る__ ので、__採用する条件__ が __必要十分条件__ に __より近く__ なる __可能性が高くなる__ ことが分かります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/63054141-7778-6893-bc0b-311108097fba.png" width="215"> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/d38a70be-bf54-9510-0d74-a463cdc6b2cd.png" width="200">

:::note warn
__必要十分条件__ が __わからない場合__ は、__必要十分条件__ に __近くなる__ ように、__できるだけ厳しい必要条件__ と、__できるだけ緩い十分条件__ を考え、その __中間の性質__ をもつ __条件__ を __利用__ することで、__目的の達成__ を __試みる__ という方法がある。
:::

上記で __できるだけ厳しい必要条件__ と述べましたが、__厳しくしすぎる__ と __必要条件__ では __なくなって__ しまいます。また、__厳しさの限界__ を __見つける__ のは __簡単__ なことでは __ありません__。例えば、__漢字のテスト__ の例の場合は、一日に __勉強する時間__ を __増やす__ ことで、__条件を厳しく__ することは __できます__ が、例えば 「__漢字の勉強を 1 日 10 時間勉強する__」のように、__厳しくしすぎて__ しまうと、__必要条件__ では __なくなってしまう__ からです。これは、__十分条件__ の場合も __同様__ で、__緩くしすぎる__ と __十分条件__ では __なくなってしまいます__。

これは __あたりまえ__ のことで、__必要条件__ の __厳しさの限界__ が、__必要十分条件__ であり、そもその __必要十分条件__ が何であるかが __わからないこと__ に __困っている__ からです。

## 複数の必要条件、十分条件

上記の説明を聞くと、__必要条件__ と __十分条件__ は、__最も厳しい__（__緩い__）ものを __1 つだけ考えればよい__ と思う人がいるかもしれませんが、__そうではありません__。__異なる__ 必要条件や十分条件が __包含関係にない__ 場合は、__複数__ の __必要条件__ や __十分条件__ を __考える価値__ が __あります__。

下図は、__包含関係にない__、__必要条件 1__ と __必要条件 2__ を表したものです。この __2 つの十分条件__ の __両方を満たす__ ような __条件__ の __水色の面積__ は、元の __必要条件 1__ と __必要条件 2__ の __それぞれの面積__ よりも __小さくなっている__ ので、__条件が厳しくなる__ ことが分かります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/e08649fa-003a-34a4-8f8d-b61313c2bdce.png" width="200">


下図は、__包含関係にない__、__十分条件 1__ と __十分条件 2__ を表したものです。この __2 つの十分条件__ の __いずれかを満たす__ ような __条件の面積__ は、元の __十分条件 1__ と __十分条件 2__ の __それぞれの面積__ よりも __大きくなっている__ ので、__条件が緩くなる__ ことが分かります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/627eb1f4-319b-d2a2-cfd2-de387b6229d6.png" width="200">

逆に言えば、__包含関係__ にある __複数__ の __必要条件__ や __十分条件__ は、そのうちの __最も厳しい__（__緩い__）__条件以外__ は __必要ありません__。

## 条件 3 が必要条件ではないことの説明

[以前の記事](https://qiita.com/ysgeso/items/58754ba98c286f414ea4#%E3%80%87%E3%82%B2%E3%83%BC%E3%83%A0%E3%81%AB%E5%8B%9D%E3%81%A4%E6%96%B9%E6%B3%95%E3%81%AE%E8%80%83%E5%AF%9F%E3%81%AE%E5%BF%85%E8%A6%81%E6%80%A7)で、__〇×ゲームに勝つ方法__ について __考察__ し、 __勝つため__ の __条件__ として、「__自 2 敵 0 空 1__」が __存在する着手__ を行う __必要がある__ という __考察__ を行いました。このことから、__直観的__ に、__ルール 8__ の『「__自 2 敵 0 空 1__」が __存在する着手__ を __行う__』という __条件 4__ は __必要条件__ であるように __思えるかもしれません__ が、__実際__ には __必要条件ではありません__。

確かに、__〇×ゲーム__ に __勝つため__ には、ゲームが開始してから終了するまでの間の __いずれかの局面__ で、「__自 2 敵 0 空 1__」が __存在する着手__ を __行う必要がある__ ことは __間違いありません__ が、それを __いつ行うべきか__ についての __考察__ は、[以前の記事](https://qiita.com/ysgeso/items/58754ba98c286f414ea4#%E3%80%87%E3%82%B2%E3%83%BC%E3%83%A0%E3%81%AB%E5%8B%9D%E3%81%A4%E6%96%B9%E6%B3%95%E3%81%AE%E8%80%83%E5%AF%9F%E3%81%AE%E5%BF%85%E8%A6%81%E6%80%A7)では __行っていません__。そのため、__ある局面__ で「__自 2 敵 0 空 1__」が __存在する着手__ を __行うことができたとしても__、その着手を __すぐに行う__ ことが勝利するために __必要である__ とは __限りません__。

__条件 4__ は以前に考察した __必要条件__ と __異なり__、「__自 2 敵 0 空 1__」が __存在する着手__ を __行うことができる__ 場合に、__すぐに着手を行う__ という __条件__ なので、__必要条件__ ではありません。

そのような細かい違いはどうでも良いのではないかと思う人がいるかもしれませんが、__細かい違いであっても__、__目的の達成__ に __大きな影響を及ぼす可能性__ があるので __重要__ です。

__別の言葉__ で __説明__ すると、__目的を達成__ するために、__必ず行わなければならない__ ことであったとしても、__すぐに行わず__ に、__後回し__ にしたほうが __良い場合がある__ ということです。もちろん、逆に __後回し__ に __してはいけない場合__ も __あります__。

__ピンとこない人が多い__ と思いますので、いくつかの __具体例で説明__ します。本当は ゲームの例だけで説明をしたかったのですが、大富豪やオセロなどのゲームに詳しくない人には意味が分からない説明になるので、__最初の例__ は __ゲームとは関係のない例__ で説明します。

__カップ麺を食べる__ 際に、お湯を注いで蓋をした後に、__必ず__ 食べる前に __蓋を開ける必要__ があります。つまり、__蓋を開ける__ ことが、__カップ麺を食べる__ ための __必要条件__ です。しかし、__蓋を開ける必要がある__ からといって、__お湯を注いだ直後__ に __蓋を開けた場合__ は __おいしいカップ麺__ が __食べられません__。この場合は、__3 分待ってから__ 蓋を __開けるべき__ です。

__大富豪__ というトランプの __ゲームの目的__ は、__自分__ の手持ちの __カードをすべて捨てる__ ことなので、__カードを捨てる__ ことが勝つための __必要条件__ になります。しかし、__強いカード__ をゲームの __早いうちに捨ててしまう__ と、ゲームの終盤に弱いカードしか手元に残らなくなってしまうため、ゲームに __勝つこと__ が __困難になります__。

__オセロ__ というゲームは、ゲーム終了時に __自分の色の駒の数__ が __多いほうが勝利__ します。そのため __初心者__ は、ゲームの __序盤__ から __自分の色__ の __駒の数が多くなる__ ような __着手__ を __行う人が多い__ ようですが、実際には __序盤__ で __そのような着手__ を行うと、__大きく不利__ になる場合が多く、__勝つこと__ が __困難になります__。

## 必要条件でも十分条件でもない条件

__ルール 8__ の __条件 4__ は、__必要条件__ では __ありません__ が、__どのタイミング__ で「__自 2 敵 0 空 1__」が __存在する着手__ を __行えば良いよいか__ については、現時点での考察では __はっきりとしていません__。また、__実際__ に __どのタイミング__ で「__自 2 敵 0 空 1__」が __存在する着手__ を __行えば良いよいか__ を __考察__ して __見つける__ ことは __簡単ではありません__。興味がある方は少し考えてみて下さい。__簡単に答えが出せない__ ことが __実感できる__ のではないかと思います。

そのような場合に、__どこかで必ず__「__自 2 敵 0 空 1__」が __存在する着手__ を __行う必要がある__ のだから、必要条件ではない __条件 4__ を __入れる価値がある__ と __判断__ して __採用する__ という __考え方があり__、__実際__ に __そのような理由__ で、__必要条件__ でも __十分条件__ でも __ない__ 条件を ルールに __組み込む__ ことが __良く行われます__。

そのようなことが行われる __理由__ は、__目的を達成__ するために __有効__ な、__必要条件__ や __十分条件__ を __満たす条件__ を __数多く考える__ ことが、__困難な場合が多い__ からです。また、__思いついた場合__ でも、本当に __その条件__ が __必要条件__ や __十分条件__ であるかどうかの __証明を行う__ ことが __難しい場合__ が __良くあります__。なお、上記で __有効な__ という条件をつけたのは、先程の例で挙げた「__テストの勉強をする__」、「__辞書を暗記する__」のような、ほとんど __意味のない__、__緩い必要条件__ や、__厳しい十分条件__ であれば、__いくらでも考えることができる__ からです。

一方、__必要条件__ や __十分条件__ であることは __証明できない__ が、__有効そうである__ 条件であれば、__証明を行う必要がない__ ので、__気軽に考える__ ことが __できます__。__実際__ に __ルール 8__ の「__真ん中のマス__ に __優先的に着手__ する」という __条件 1__ は、__必要条件__ であることは __証明されていません__ が、__有効__ である __可能性が高い__ ことを[以前の記事](https://qiita.com/ysgeso/items/1e8732db6cd1b0104276#%E7%9C%9F%E3%82%93%E4%B8%AD%E3%81%AE%E3%83%9E%E3%82%B9%E3%81%B8%E3%81%AE%E7%9D%80%E6%89%8B%E3%81%8C%E6%9C%89%E5%88%A9%E3%81%A7%E3%81%82%E3%82%8B%E7%90%86%E7%94%B1%E3%81%AE%E8%80%83%E5%AF%9F)で __考察__ し、__実際__ に __ルール 3__ に __組み込んだ結果__、__強くなる__ ことが __確認できました__。

__必要条件__ でも __十分条件__ でも __ない__ 条件は、__ルールに組み込む__ ことによって __弱くなる可能性__ があります。そのため、その条件が __有効であるか__ どうかはどうかは、その __条件__ を __ルール__ に __組み込んでいない__ 他の __AI と対戦__ して __比較する__ ことで __確認する__ 必要があります。比較した結果、__強く__ なっていれば __採用__ し、__弱く__ なっていれば __採用を見送り__ ます。

下図の __薄い水色__ が、__必要条件__ でも __十分条件__ でも __ない条件__ の __例__ です。図からわかるように、__必要条件の外__ に __はみ出している部分__ があり、__その部分__ によって __選択が行われる__ と、__目的が達成できなく__ なります。また、__必要十分条件__ に __重なっている部分__ によって __選択が行われる__ と、__目的が達成__ できます。従って、__必要条件__ や __十分条件__ を __考える場合__ と __同様__ に、__必要十分条件__ に __近くなるような条件__ を __考える__ ことが __重要__ になります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/142d85b3-7a0f-9c05-09ea-5b8fe8bffb81.png" width="200">


[以前の記事](https://qiita.com/ysgeso/items/58754ba98c286f414ea4#%E3%83%AB%E3%83%BC%E3%83%AB%E3%83%99%E3%83%BC%E3%82%B9%E3%81%AE-ai-%E3%81%AE%E6%80%A7%E8%B3%AA)でも言及しましたが、__ルールベースの AI__ は、__必要条件__ でも __十分条件__ でもない __条件を追加__ することで、__下記__ のような __問題が発生__ することが __多い__ ため、ルールベースで __強い AI__ を __作る__ のはかなり __困難な作業__ になります。

* [以前の記事](https://qiita.com/ysgeso/items/1e8732db6cd1b0104276#%E3%83%AB%E3%83%BC%E3%83%AB-4-%E3%81%AE%E8%A9%95%E4%BE%A1)の __ルール 4__ で __追加__ した「__真ん中__、__隅__ のマスの __順__ で __優先的__ に __着手__ する」という __条件__ のように、__一見__ すると AI が __強く__ なっている __ように見える__ が、__実際__ には __特定__ の __AI__ に __対してのみ強く__ なっているという __場合__ がある
* __条件が増える__ と、__条件どうし__ の __相性__ や __矛盾__ などの __問題__ で、突然 __AI が弱くなる__ という __現象が発生__ することがある
* __条件が増える__ と、__ルール__ が __複雑になりすぎて__、__何を行っているか__ が __分かりづらくなる__[^2]
* __条件が増える__ と、__新しい__ 有効な __条件を考える__ ことが __困難__ になる
* __条件が増える__ と、__新しい条件__ を __追加__ してもほとんど __AI__ が __強くならなく__ なる

従って、__最近__ の __AI__ は __人間__ が __ルールを考える__ というルールベースではなく、__AI__ が __自動的__ に __ルール__ を __発見__ する、__機械学習__ という手法を使ったものが __主流__ になっています。〇×ゲームの AI を機械学習で作成する方法については、今後の記事で紹介します。

なお、__〇×ゲーム__ は、__単純__ なゲームなので、__ルールベース__ でも __最強の AI__ を作る ことが __可能__ なので、引き続きルールベースで 〇×ゲームの AI を作成します。


## ルールベースの AI の作成手順

__ルールベースの AI__ の __一般的__ な __作成手順__ をまとめます。__本記事__ でも、__実際__ に __下記の手順__ でこれまで __〇×ゲーム__ の __AI を作成__ してきました。

:::note warn

* __必要十分条件__ を __考える__ ことが __できれば__、それを __ルールに組み込む__ ことで __最強の AI__ を __作成できる__ が、__一般的__ には __必要十分条件__ が __分からない__ ことが __多い__
* __十分条件__ を考え、__ルール__ に __組み込む__ ことで、__AI__ を __強くする__
* __必要条件__ を考え、__ルール__ に __組み込む__ ことで、__AI__ を __強くする__
* __必要条件__ でも __十分条件__ でも __ない__、__有望そうな条件__ を考え、__ルール__ に __組み込んで__ AI を __実装__ し、__他の AI__  と __対戦する__ ことでその __性能を確かめる__
    * __強くなっていれば__ その __条件を採用__ する
    * __強くならない__（__弱くなっている__）場合は、その __条件__ を __破棄__ する[^3]
:::

# 2 つ目の十分条件を組み込んだ AI

__ルール 8__ の __条件 2__ は、__現在の局面__ で __勝利できる場合__ に __勝利する__ というものでしたが、__未来の局面__ で __必ず勝利できる場合__ でも __強い AI__ の __目的を達成__ することが __できます__。そこで、__次__ の __自分の手番__ の __局面__ で __必ず勝利できる__ ための __条件__ を __考える__ ことができれば、それを __十分条件__ と __みなす__ ことが __できます__。〇×ゲームにある程度慣れている方には当たり前の事かもしれませんが、それがどのような条件であるかについて少し考えてみて下さい。

まず、__前提条件__ を __明確__ にすることにします。__次__ の __自分の手番__ で __勝利できる__ ということから、「__現在の局面__ で __勝利できない__」という __条件が必要__ です。また、__次__ の __相手の手番__ で __相手が勝利__ すると __目的__ が __達成できない__ ので、「__次__ の __相手の手番__ で __相手__ が __勝利しない__」という __条件が必要__ になります。これらは、__ルール 8__ の __条件 2__ と __3__ によって __判定できます__。

__ルール 8__ の __条件 4__ の __着手__ を行っても、__必ず勝利__ できるとは __限らない理由__ は、__〇×ゲーム__ は、__自分と相手__ が __交互__ に __着手を行う__ ゲームなので、「__自 2 敵 0 空 1__」が __存在する着手__ を行っても、__次__ の __相手の手番__ で「__自 2 敵 0 空 1__」の __ライン__ の __空いているマス__ に __相手__ が __着手を行う__ ことで、__邪魔__ をされてしまう __可能性がある__ からです。

しかし、「__自 2 敵 0 空 1__」の __ライン__ が __2 つ以上__ ある場合は、__次__ の __相手の手番__ で __邪魔をできる__ のは、そのうちの __1 つ__ の __ラインだけ__ です。例えば、__下図__ の局面の場合、__×__  が __次__ の __〇 の手番__ での __勝利を阻止__ するためには、__2 つ__ ある __黄色のマス__ に __着手__ を __行う必要__ がありますが、__自分の手番__ で行える __着手__ は __1 マスだけ__ なので、__どちらか__ の __黄色のマス__ は必ず __空のまま__ 残ってしまうので、__次__ の __〇 の手番__ で __〇__  が __必ず勝利__ することが __できます__。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/bd4f3eb6-4493-b7b5-d324-7f1dec84ee81.png" width="100">

従って、__次__ の __自分の手番__ の局面で __必ず勝利できる__ ための __条件__ は、__以下__ のようになることが分かります。

* __ルール 8__ の __条件 2__ を __満たさない__
* __ルール 8__ の __条件 3__ を __満たさない__
* 「__自 2 敵 0 空 1__」が __2 つ以上__ 存在する __着手__ を行う

:::note
同様に、__2 手先__ や __3 手先__ の __自分の手番__ で自分が __必ず勝利できる条件__ を考え、それを __十分条件__ と __みなす__ ことが __できます__ が、でそのような __条件__ を __みつける__ のは __簡単ではありません__。__数手先__ の __未来の局面__ を __本格的__ に __考慮に入れる__ AI については、ルールベースの次の、__探索型の AI__ の所で __説明__ します。
:::

## ルール 9 の定義

__上記の条件__ を __ルール 8__ に __加えた__、__ルール 9__ を下記のように __定義__ する事にします。__新しい条件__ は、__十分条件__ なので、十分条件でない __ルール 8__ の __条件 4__ よりも __優先順位__ を __高くする__ 必要があります。下記が __ルール 9__ の __定義___ です。なお、__新しい条件__ の __内容__ に __合わせて__、__ルール 8__ の __条件 4__ を、『「__自 2 敵 0 空 1__」が __存在する着手__ を行う』から『「__自 2 敵 0 空 1__」が __1 つ存在__ する __着手__ を行う』のように修正しました。


今回から、それぞれの __条件の種類__ を __表で示す__ ようにしました。__種類__ の列が __空欄__ になっている __条件__ は、__必要条件__ でも、__十分条件__ でも __ない__ ことを表します。また、__条件__ は __優先順位__ の __高い順__ に __並んでいる__ ので、「__そうでない場合は__」は __削除__ しました。

| 条件 | 種類 |
|:--|:--|
| __真ん中__ のマスに __優先的__ に __着手__ する  |   |
| __勝てる場合__ に __勝つ__  | 十分条件  |
| __相手__ が __勝利できる__ 着手を __行わない__  | 必要条件  |
| __次__ の __自分の手番__ で __必ず勝利できる__ ように、__「自 2 敵 0 空 1」が 2 つ以上存在する着手__ を行う  | 十分条件  |
| __次__ の __自分の手番__ で __勝利できる__ ように、__「自 2 敵 0 空 1」が 1 つ存在する着手__ を行う  |   |
| __ランダム__ なマスに __着手__ する  |   |

__ルール 9__ の __評価値__ をどのように __設定__  すればよいかについて少し考えてみて下さい。

## 評価値の設定

下記は、__ルール 8__ の __評価値__ の __設定__ です。

| 優先順位 | 局面の状況 | 評価値 |
|:-:|:--|:-:|
| 1 | __真ん中のマスに着手している__ | `3`  |
| 2 | __自分が勝利している__  | `2`  |
| 4 | __「自 2 敵 0 空 1」が 1 つ以上存在する__  | `1`  |
| 5 | __「自 2 敵 0 空 1」が存在しない__  | `0`  |
| 3 | __相手が勝利できる__  | `-1`  |

上記の表に、__ルール 9__ の『__「自 2 敵 0 空 1」が 2 つ以上存在する__』 という __新しい条件__ を __追加__ した状態の __評価値を設定__ する必要があります。

下記の表は、__ルール 9__ の __条件の優先順位__ に注意しながら __評価値__ を __設定__ した例です。__上 2 つ__ の __評価値__ は、新しい条件の追加に合わせて __修正__ しました。

| 優先順位 | 局面の状況 | 評価値 |
|:-:|:--|:-:|
| 1 | __真ん中のマスに着手している__ | `4`  |
| 2 | __自分が勝利している__  | `3`  |
| 4 | __「自 2 敵 0 空 1」が 2 つ以上存在する__  | `2`  |
| 5 | __「自 2 敵 0 空 1」が 1 つ存在する__  | `1`  |
| 6 | __「自 2 敵 0 空 1」が存在しない__  | `0`  |
| 3 | __相手が勝利できる__  | `-1`  |

## マークのパターンを数える関数の定義

『__「自 2 敵 0 空 1」が 2 つ以上存在する__』 という __条件__ を __判定__ するためには、__マークパターン__ の数を __数える__ 必要がありますが、__`enum_markpats`__ は、__マークのパターン__ が __存在するかどうか__ を計算するだけで、数を __数える機能__ を __持たない__ ので、__利用__ することは __できません__。そこで、__マークのパターン__ の数を __数える__ 下記の __メソッド__ を __定義__ する事にします。

* __名前__：__マーク__（mark）の __パターン__（pattern）を __数える__（count）ので、__`count_markpats`__ という __名前__ にする
* 処理：インスタンスの __局面__ の __マークのパターン__ を __数える__
* 入力：なし
* 出力：__マークのパターン__ を __数えた__ データを __返り値__ として __返す__

:::note
__`enum_markpats`__ を __修正__ し、__マークのパターン__ を __数える機能__ を __追加__ するという方法も __考えられます__ が、__名前__ に __列挙__（enumerate）の単語が入ったメソッドで、__列挙__ とは __異なる__ 数える（count）という __処理__ を __行うようにする__ と、プログラムが __わかりづらくなる__ ので、本記事では __別の名前__ の __メソッドを定義__ する事にします。
:::

__数__ を __数える処理__ は、[`count_marks`](https://qiita.com/ysgeso/items/6c0d73cc9ef4efd62d48#count_marks-%E3%81%AE%E5%AE%9A%E7%BE%A9) と同様に、[以前の記事](https://qiita.com/ysgeso/items/5725504c97a4490fbadb#defaultdict-%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%9F%E9%9B%86%E8%A8%88)で紹介した、__defaultdict__ を __利用__ する方法が __考えられます__。__`count_marks`__ では、__defaultdict__ の __キー__ に __マーク__ を表す __文字列__ を __利用__ しましたが、__`count_markpats`__ では、__マークのパターン__ を __数える__ 必要があるので、__defaultdict__ が __使えない__ と __思う人__ が __いるかもしれません__。__実際__ には、__dict__ や __defaultdict__ は、__キー__ に __ハッシュ可能なオブジェクト__ を __利用__ でき、[前回の記事](https://qiita.com/ysgeso/items/085fd15fbd91d5cdf770#namedtuple)で、__マークのパターン__ を表すデータを __ハッシュ可能なオブジェクト__ である __NamedTuple__ に __変更__ したので、__マークのパターン__ を __defaultdict__ を使って __数える__ ことが __可能__ です。下記は、__`count_markpats`__ の __定義__ で、__[`enum_markpats`](https://qiita.com/ysgeso/items/af8b5dda0ddffd5c6d13#enum_markpats-%E3%81%AE%E4%BF%AE%E6%AD%A3%E3%81%A8%E7%A2%BA%E8%AA%8D)__ を __修正__ することで、__簡単に定義__ することが __できます__。

なお、__`enum_markpats`__ は削除せず、__そのまま残しています__。

* __5 行目__：__マークのパターン__ を __数える__ 変数を __初期化__ する際に __代入する値__ を、__空の set__ から __空の defaultdict__ に __修正__ する
* __10、12、15、18 行目__：__マークのパターン__ の __キーの値__ に __1 を足して__ 数を __数える__

```python
 1  from marubatsu import Marubatsu, Markpat
 2  from collections import defaultdict
 3
 4  def count_markpats(self):
 5      markpats = defaultdict(int)
 6 
 7      # 横方向と縦方向の判定
 8      for i in range(self.BOARD_SIZE):
 9          count = self.count_marks(coord=[0, i], dx=1, dy=0, datatype="tuple")
10          markpats[count] += 1
11          count = self.count_marks(coord=[i, 0], dx=0, dy=1, datatype="tuple")
12          markpats[count] += 1
13      # 左上から右下方向の判定
14      count = self.count_marks(coord=[0, 0], dx=1, dy=1, datatype="tuple")
15      markpats[count] += 1
16      # 右上から左下方向の判定
17      count = self.count_marks(coord=[2, 0], dx=-1, dy=1, datatype="tuple")
18      markpats[count] += 1
19
20      return markpats
21
22  Marubatsu.count_markpats = count_markpats
```

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

```python
from marubatsu import Marubatsu, Markpat
from collections import defaultdict

def count_markpats(self):
    markpats = defaultdict(int)
 
    # 横方向と縦方向の判定
    for i in range(self.BOARD_SIZE):
        count = self.count_marks(coord=[0, i], dx=1, dy=0, datatype="tuple")
        markpats[count] += 1
        count = self.count_marks(coord=[i, 0], dx=0, dy=1, datatype="tuple")
        markpats[count] += 1
    # 左上から右下方向の判定
    count = self.count_marks(coord=[0, 0], dx=1, dy=1, datatype="tuple")
    markpats[count] += 1
    # 右上から左下方向の判定
    count = self.count_marks(coord=[2, 0], dx=-1, dy=1, datatype="tuple")
    markpats[count] += 1

    return markpats

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

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

```diff_python
from marubatsu import Marubatsu, Markpat
from collections import defaultdict

-def enum_markpats(self):
+def count_markpats(self):
-   markpats = set()
+   markpats = defaultdict(int)
 
    # 横方向と縦方向の判定
    for i in range(self.BOARD_SIZE):
        count = self.count_marks(coord=[0, i], dx=1, dy=0, datatype="tuple")
-       markpats.add(count)
+       markpats[count] += 1
        count = self.count_marks(coord=[i, 0], dx=0, dy=1, datatype="tuple")
-       markpats.add(count)
+       markpats[count] += 1
    # 左上から右下方向の判定
    count = self.count_marks(coord=[0, 0], dx=1, dy=1, datatype="tuple")
-   markpats.add(count)
+   markpats[count] += 1
    # 右上から左下方向の判定
    count = self.count_marks(coord=[2, 0], dx=-1, dy=1, datatype="tuple")
-   markpats.add(count)
+   markpats[count] += 1

    return markpats

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


下記は、__いくつか__ の __局面__ で、__`count_markpats`__ を使って __マークのパターン__ の数を __数える__ プログラムです。__実行結果__ から、__正しく動作__ することが __確認__ できます。

```python
from pprint import pprint
mb = Marubatsu()

print(mb)
pprint(mb.count_markpats())

mb.move(1, 1)
print(mb)
pprint(mb.count_markpats())

mb.move(0, 0)
print(mb)
pprint(mb.count_markpats())

mb.move(1, 0)
print(mb)
pprint(mb.count_markpats())
```

実行結果

```
Turn o
...
...
...

defaultdict(<class 'int'>, {Markpat(last_turn=0, turn=0, empty=3): 8})
Turn x
...
.O.
...

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

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

defaultdict(<class 'int'>,
            {Markpat(last_turn=0, turn=0, empty=3): 2,
             Markpat(last_turn=0, turn=1, empty=2): 1,
             Markpat(last_turn=1, turn=0, empty=2): 2,
             Markpat(last_turn=1, turn=1, empty=1): 2,
             Markpat(last_turn=2, turn=0, empty=1): 1})
```

## `ai9s` の定義

__`count_markpats`__ を __利用__ することで、__`ai9s`__ は __下記__ のプログラムのように __定義__ できます。__修正点__ は、__評価値の修正__ と、__`markpats`__ の __キーの値__ を使って __判定を行う__ 点です。

* __7、11 行目__：__評価値__ を __評価値の表__ に合わせて __修正__ する
* __15 行目__：__`markpats`__ の「__自 0 敵 2 空 1__」の __キーの数__ が __正の場合__ に、__相手__ が __勝利できる__ と __判定__ するように __修正__ する
* __18 行目__：__`markpats`__ の「__自 2 敵 0 空 1__」の __キーの数__ が __2 以上__ の場合は、__次__ の __自分の手番__ で自分が __必ず勝利できる__ と __判定__ して、__評価値__ として __`2`__ を __返す__
* __21 行目__：__`markpats`__ の「__自 0 敵 2 空 1__」の __キーの数__ が __1 の場合__ に、__次__ の __自分の手番__ で自分が __勝利できる__ と __判定__ するように __修正__ する

```python
 1  from ai import ai_by_score
 2
 3  def ai9s(mb, debug=False):
 4     def eval_func(mb):
 5          # 真ん中のマスに着手している場合は、評価値として 4 を返す
 6          if mb.last_move == (1, 1):
 7              return 4
 8   
 9          # 自分が勝利している場合は、評価値として 3 を返す
10          if mb.status == mb.last_turn:
11              return 3
12
13          markpats = mb.count_markpats()
14          # 相手が勝利できる場合は評価値として -1 を返す
15          if markpats[Markpat(last_turn=0, turn=2, empty=1)] > 0:
16              return -1
17          # 次の自分の手番で自分が必ず勝利できる場合は評価値として 2 を返す
18          elif markpats[Markpat(last_turn=2, turn=0, empty=1)] >= 2:
19              return 2
20          # 次の自分の手番で自分が勝利できる場合は評価値として 1 を返す
21          elif markpats[Markpat(last_turn=2, turn=0, empty=1)] == 1:
22              return 1
23          # それ以外の場合は評価値として 0 を返す
24          else:
25              return 0
26
27      return ai_by_score(mb, eval_func, debug=debug) 
```

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

```python
from ai import ai_by_score

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

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

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

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

```diff_python
from ai import ai_by_score

-def ai8s(mb, debug=False):
+def ai9s(mb, debug=False):
    def eval_func(mb):
        # 真ん中のマスに着手している場合は、評価値として 4 を返す
        if mb.last_move == (1, 1):
-           return 3
+           return 4
    
        # 自分が勝利している場合は、評価値として 3 を返す
        if mb.status == mb.last_turn:
-           return 2
+           return 3

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

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


## `ai2` と `ai8s` との対戦

基準となる __`ai2`__ と、__これまで__ に作成した __最強の AI__ である __`ai8s`__ と対戦することにします。まず、下記のプログラムで __`ai2`__ と対戦します。

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

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

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

```
ai9s VS ai2
count     win    lose    draw
o        9867      15     118
x        8961     237     802
total   18828     252     920

ratio     win    lose    draw
o       98.7%    0.1%    1.2%
x       89.6%    2.4%    8.0%
total   94.1%    1.3%    4.6%
```

下記は、__`ai8s` VS `ai2`__ と __`ai9s` VS `ai2`__ の対戦結果の表です。__`ai2` に対して__、__`ai9s`__ のほうが、__`ai8s`__ より __勝率が高くなる__ ことが確認できます。

| 関数名 | o 勝 | o 負 | o 分 | x 勝 | x 負 | x 分 | 勝 | 負 | 分 |
|:-:|--:|--:|--:|--:|--:|--:|--:|--:|--:|
| __[`ai8s`](https://qiita.com/ysgeso/items/085fd15fbd91d5cdf770#ai8s-%E3%81%AE%E4%BF%AE%E6%AD%A3)__  | __97.3__ | __1.5__ | 1.3 | __86.1__ | __8.5__ | 5.4 | __91.7__ | __5.0__ | 3.3 |
| __`ai9s`__  | __98.7__ | __0.1__ | 1.2 | __89.6__ | __2.4__ | 8.0 | __94.1__ | __1.3__ | 4.6 |

次に、__`ai8s`__ と __対戦__ を行います。__実行結果__ の __通算成績__ から、__`ai9s`__ が __`ai8s` に__ 対して強くなっており、__新しい条件__ の __有効性__ が __確認__ できました。

```python
ai_match(ai=[ai9s, ai8s])
```

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

```
ai9s VS ai8s
count     win    lose    draw
o        5019     397    4584
x         409    3365    6226
total    5428    3762   10810

ratio     win    lose    draw
o       50.2%    4.0%   45.8%
x        4.1%   33.7%   62.3%
total   27.1%   18.8%   54.0%
```

# マークのパターンの考察

__これまでのルール__ では、__マークのパターン__ のうち、「__自 0 敵 2 空 1__」と「__自 2 敵 0 空 1__」__のみを利用__ してきましたが、__他__ の __マークのパターン__ も __局面の状況__ を __表している__ と __考えられる__ ので、__それぞれ__ の __マークのパターン__ の __考察__ を行うことにします。

__下記__ の __表__ は、__マークのパターン__ の __一覧__ で、__10 種__ 類ある事が分かります。__それぞれ__ の __マークのパターン__ の __性質__ がどのようなものであるかについて、少し考えてみて下さい。

| 自 | 敵 | 空 |
|:-:|:-:|:-:|
| 3  | 0  | 0  |
| 2  | 1  | 0  |
| 2  | 0  | 1  |
| 1  | 2  | 0  |
| 1  | 1  | 1  |
| 1  | 0  | 2  |
| 0  | 3  | 0  |
| 0  | 2  | 1  |
| 0  | 1  | 2  |
| 0  | 0  | 3  |

## 「自 3 敵 0 空 0」

このマークのパターンは、__自分が勝利__ していることを __表します__ が、__自分の勝利__ は、__`status` 属性__ で __判定済__ なので、このマークのパターンを __考慮__ する __必要__ は __ありません__。

## 「自 0 敵 3 空 0」

このマークのパターンは、__相手が勝利__ していることを __表します__ が、__〇×ゲーム__ は、__自分の着手__ によって __相手が勝利__ することは __あり得ない__ ので、このマークのパターンが __出現__ することは __ありません__。従って、このマークのパターンを __考慮__ する __必要__ は __ありません__。

## 「自 2 敵 0 空 1」

このマークのパターンは、__今回の記事__ で __考察済__ です。なお、このマークのパターンが __2 つ以上__ ある __場合__ に、その __数を区別__ する __必要があるか__ どうかが気になる人がいるかもしれないので補足します。このマークのパターンが __2 つ以上__ ある場合は、__いくつであっても__ 次の自分の手番で __自分が勝利できる__ ことに __変わりはありません__ ので、このマークのパターンが __2 つ以上__ の場合の __数の違い__ を __区別しなくても__、__AI の強さ__ に __影響__ は __与えません__。

## 「自 0 敵 2 空 1」

このマークのパターンは既に __ルールの条件__ に __組み込まれています__。このマークのパターンが __1 つ以上__ ある場合も、上記と同様に、__いくつであっても__、次の相手の手番で __相手が勝利できる__ ことに __変わりがない__ と思う人が多いのではないかと思います。__実際__ には __AI の強さ__ に __少し__ だけ __影響を与える__ のですが、その点については次回の記事で説明することにします。興味がある方は、どのような影響を与えるかについて少し考えてみて下さい。

## 自と敵がのマークが両方あるパターン

「__自 2 敵 1 空 0__」、「__自 1 敵 1 空 1__」、「__自 1 敵 2 空 0__」の __3 種類__ のマークのパターンは、その __ライン__ に __自分__ と __敵__ の __両方のマーク__ が __配置されている__ ことを表します。__〇×ゲーム__ は、__一度配置__ した __マーク__ は __ゲームが終了__ するまで __変化しない__ ので、__これらの__ マークのパターンの __ライン__ は、__今後__ のゲームの __勝敗__ に __影響を与えない可能性が高い__ ことが __考察__ できます。従って、このマークのパターンの __数__ は __AIの強さ__ に __影響を及ぼさない可能性が高い__ ことが __推測__ できます。なお、__絶対__ に __影響を及ぼさない__ という __証明__ は __行っていない__ ので、あくまで __可能性が高い__ に __過ぎない__ 点に __注意__ して下さい。

## 「自 0 敵 0 空 3」

このマークのパターンの __ライン__ は、__自分__ と __相手__ の __両方__ が、今後 __勝利できる可能性がある__ ラインです。従って、このマークのパターンの __数__ が __どちらか__ に __有利に働く__ ということは __なさそう__ なので、__AI の強さ__ に __影響__ を __及ぼさない可能性が高い__ ことが __推測__ できます。

## 「自 1 敵 0 空 2」

__〇×ゲーム__ に __勝利__ するためには、「__自 2 敵 0 空 1__」が __存在する着手__ を行う __必要__ がありますが、__そのため__ には、自分の __前の手番__ で「__自 1 敵 0 空 2__」が __存在する着手__ を __行う必要__ があります。従って、__ゲームが終了するまでの間__ に __必ず__「__自 1 敵 0 空 2__」が __存在する着手__ を行うことが、__〇×ゲーム__ に __勝利するため__ の __必要条件__ であることが分かります。

ただし、「__自 2 敵 0 空 1__」の場合と同様に、__どのタイミング__ で「__自 1 敵 0 空 2__」が __存在する着手__ を __行う必要__ があるかまでは __わかりません__ ので、__必要条件__ では __ありません__ が、「__自 1 敵 0 空 2__」が __存在する着手__ を __すぐに行う__ という条件を考えることにします。

次に、「__自 1 敵 0 空 2__」の __数__ を __考察__ することにします。「__自 1 敵 0 空 2__」の __数__ が多ければ __多い程__、__次__ の __自分の手番__ で、「__自 1 敵 0 空 2__」が __残っている可能性が高くなる__ ので、「__自 1 敵 0 空 2__」の __数__ が __多い__ ことが __望ましい__ ことが __推測__ できます。

また、「__自 1 敵 0 空 2__」の __数を増やす__ ということは、__相手が勝つ__ ことが __できるライン__ を __減らす__ ことにも __つながります__。__そのような理由__ からも、「__自 1 敵 0 空 2__」の __数__ が __多い__ ことが __望ましい__ ことが __推測__ できます。

## 「自 0 敵 1 空 2」

__相手の立場__ から考えると、このマークのパターンは「__自 1 敵 0 空 2__」になるので、「__自 0 敵 1 空 2__」の __数が多い__ ということは、__相手の勝利__ の __可能性が高くなる__ ことが __推測__ できます。従って、「__自 0 敵 1 空 2__」の __数__ が __少ない__ ことが __望ましい__ ことが __推測__ できます。

## マークのパターンの考察のまとめ

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

| 自 | 敵 | 空 | 考察 |
|:-:|:-:|:-:|:-- |
| 3  | 0  | 0  | __`status` 属性__ で __確認済__ なので、__考慮する必要はない__ |
| 0  | 3  | 0  | __あり得ない__ ので __考慮する必要はない__
| 2  | 0  | 1  | __既__ に __ルール__ に __組み込まれている__ ので新しく __考慮する必要はない__ |
| 0  | 2  | 1  | __既__ に __ルール__ に __組み込まれている__ ので新しく __考慮する必要はない__ |
| 2  | 1  | 0  | どちらの __勝利__ にも __影響しない可能性が高い__ ので __考慮する必要はない__ |
| 1  | 2  | 0  | どちらの __勝利__ にも __影響しない可能性が高い__ ので __考慮する必要はない__ |
| 1  | 1  | 1  | どちらの __勝利__ にも __影響しない可能性が高い__ ので __考慮する必要はない__ |
| 0  | 0  | 3  | どちらの __勝利__ にも __影響しない可能性が高い__ ので __考慮する必要はない__ |
| 1  | 0  | 2  | __多いほう__ が __有利__ になる | 
| 0  | 1  | 2  | __少ないほう__ が __有利__ になる |

__上記の表__ から、__以下__ の __2 つ__ の __条件__ を __ルールに組み込む__ ことで __AI__ が __強くなる可能性が高い__ ことが __推測される__ ので、__条件に組み込む__ ことにします。なお、__いずれの条件__ も、__必要条件__ でも __十分条件__ でも __ありません__ ので、__組み込む__ ことで __弱くなる可能性__ があります。

* 「__自 1 敵 0 空 2__」の __数__ が __最も多い__ 着手を行う
* 「__自 0 敵 1 空 2__」の __数__ が __最も少ない__ 着手を行う

# ルール 10

まず、『「__自 1 敵 0 空 2__」の __数__ が __最も多い__ 着手を行う』という __条件__ を __ルール 9__ に __追加__ した __ルール 10__ を __定義__ する事にします。どのように定義すればよいかについて少し考えてみて下さい。

## 複数の条件が同時に満たされる場合のルール

__ルール 9 まで__ は、__すべての条件__ には __明確な理由__ を元にした __優先順位__ があり、__上位の条件__ が __満たされた時点__ で __評価値が決定__ され、それより __下位の条件__ は __考慮されません__ でした。

一方、__ルール 9__ の『「__自 2 敵 0 空 1__」が __1 つ存在__ する』と、『「__自 1 敵 0 空 2__」の __数__ が __最も多い__』という __条件__ が __同時__ に __満たされた場合__ に、__どちらを優先すればよいか__ については、__明確__ では __ありません__。従って、__ルール 10__ は、下記のように、__同じ優先順位__ に __複数の条件__ が __存在する__ ように __定義__ する __必要__ があります。このような場合に、__どのように評価値__ を __設定__ すればよいかについて少し考えてみて下さい。なお、長くて表が見づらいので、条件の内容を簡略化しました。

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

## 同じ優先順位の条件に対する評価値の設定方法

__同じ優先順位__ の __条件__ が __複数存在__ する場合に、どのような __評価値__ を __設定するか__ については、下記のように、__それぞれの条件__ が __満たされる場合__ と __満たされない場合__ の __すべての組み合わせ__ を __表__ にして __考察__ すると良いでしょう。

|  |「自 2 敵 0 空 1」が 1 つ | 「自 1 敵 0 空 2」が最も多い |
|:-:|:-:|:-:|
| __組み合わせ 1__  | ×  | ×  |
| __組み合わせ 2__  | 〇  | ×  |
| __組み合わせ 3__  | ×  | 〇  |
| __組み合わせ 4__  | 〇  | 〇  |

__片方の条件__ が __同じ__ であれば、明らかに __もう片方の条件__ が __満たされる方__ の __評価値__ を __高くする必要__ があります。そのため、__上記の表__ から、__それぞれの組み合わせ__ に対する __評価値__ には、__下記__ のような __関係がある__ ことが __分かります__。

* 組み合わせ 1 の評価値 < 組み合わせ 2 の評価値 < 組み合わせ 4 の評価値
* 組み合わせ 1 の評価値 < 組み合わせ 3 の評価値 < 組み合わせ 4 の評価値

__上記の条件__ を __満たす__ ことができれば、__それぞれ__ の __組み合わせ__ の __評価値__ を __どのように設定__ しても __構いません__。__評価値の設定方法__ について少し考えてみて下さい。

__最も簡単__ な __方法__ は、__それぞれ__ の __条件__ が __単独__ で __満たされる場合__ の __正の評価値__ を __個別に設定__ し、__組み合わせ__ の __評価値を計算__ する際に、__合計を計算__ するという __方法__ でしょう。

:::note
よりふさわしい方法があれば、そちらを採用しても構いません。
:::

例えば、『「__自 2 敵 0 空 1__」が __1 つ存在__ する』が __満たされる場合__ の __評価値__ を __A__、『「__自 1 敵 0 空 2__」の __数__ が __最も多い__』が __満たされる場合__ の __評価値__ を __B__ と __設定__ した場合は、__それぞれ__ の __組み合わせ__ の __評価値__ は __下記の表__ のように、__簡単な方法__ で __計算__ できます。なお、__A__ と __B__ は __正の数__ とし、__具体的な値__ は __この後__ で __設定__ します。

|  |「自 2 敵 0 空 1」が 1 つ | 「自 1 敵 0 空 2」が最も多い | 評価値 |
|:-:|:-:|:-:|:-:|
| __組み合わせ 1__  | ×  | ×  | 0 |
| __組み合わせ 2__  | 〇  | ×  | A |
| __組み合わせ 3__  | ×  | 〇  | B |
| __組み合わせ 4__  | 〇  | 〇  | A + B |

__先程__ の、__それぞれ__ の __組み合わせ__ の __評価値__ の __関係__ を表す __式__ に、__上記__ の __評価値__ を __当てはめる__ と __下記__ の __式__ になります。__A__ と __B__ は __正の値__ なので、__下記__ の __2 つ__ の __式__ が __常に正しくなる__ ことは __明らか__ でしょう。

* 0 < A < A + B
* 0 < B < A + B

## 具体的な評価値の設定方法

__上記の方針__ で __評価値を設定__ する場合は、__A__ と __B__ に __どのような値__ を __設定__ すればよいかについて __考える必要__ があります。__A__ と __B__ は __それぞれ__ の __条件__ の __評価値__ を表すので、__下記の表__ のように、__それぞれの条件__ の __重要度__ を __考慮した評価値__ を __設定__ する __必要__ があります。

| A と B の関係 | A と B の重要度|
|:-:|:--|
| __A > B__  | __A の条件__ のほうが、__B の条件より__ も __重要度__ が __高い__  |
| __A = B__  | __A の条件__ と __B の条件__ の __重要度__ が __同程度__ か、__関係__ が __不明__ である  |
| __A < B__  | __A の条件__ のほうが、__B の条件より__ も __重要度__ が __低い__  |

今回の場合、『「__自 2 敵 0 空 1__」が __1 つ存在__ する』と『「__自 1 敵 0 空 2__」が __最も多い__』の __どちら__ の条件が __重要であるか__ は __わかっていません__。また、仮に __片方が重要__ だとしても、__どれくらい重要__ であるかは __わからない__ ことが __良くあります__。__そのような場合__ は、__以下__ のような __試行錯誤__ による方法で __評価値__ を __設定__ するのが __一般的__ です。

1. __とりあえず__ おおざっぱに __評価値__ を __設定__ して __AI を実装__ する
2. __実装した AI__ と __他の AI__  を __対戦__ させて、__強さを評価__ する
3. __A__ または __B__ の __評価値__ を __少し変化__ させて、__対戦__ させる
4. __手順 2__ と __3__ を __何度も繰り返し__、__最も成績が良い__ 評価値の __組み合わせ__ を __採用__ する

__試行錯誤__ の __回数__ が __多くなればなるほど__、__手間__ と __時間__ が __増大__ します。そのため、上記の __手順 3__ で、__効率よく__ A や B の __評価値__ を __変化させながら__、__上記の手順__ を __行う必要__ があります。その方法については、機械学習の手法を紹介する際に説明する予定です。

__今回の条件__ の場合は、__上記の試行錯誤__ は __数回だけ行う__ ことにします。

まず、__A__ の __評価値__ として、__とりあえず 1 を設定__ することにします。次に __B の評価値__ を __決める必要__ がありますが、これまでのように、__特定の数__ を __設定__ することは __できません__。その理由について少し考えてみて下さい。

## 数を考慮する評価値の設定方法

__ルール 9 まで__ の __条件__ では、その __条件が満たされるかどうか__ で __評価値が決まりました__。一方、__ルール 10__ で __追加__ する『「__自 1 敵 0 空 2__」が __最も多い__』という __条件__ は、__マークのパターン__ の __数__ が __多いほど__ 評価値が __大きくなる__ ように __評価値を設定__ する __必要__ があります。

そのような場合に __最も簡単__ な __評価値__ の __計算方法__ は、$a$ を __正の数__、$x$ を __マークのパターン__ の __数__ とした場合に、__下記の式__ で __計算__ するという __方法__ です。

$評価値 = a × x$

__最もわかりやすい__ のは、$a$ に __1__ を __設定__ する場合で、その場合の __評価値__ は、__マークのパターンの数__ を表す $x$ になります。$x$ が __増えれば__、__評価値も増える__ ので、「__自 1 敵 0 空 2__」が __最も多い場合__ の __評価値__ が __最も高くなる__ ことは __明らか__ でしょう。

今回は、__B__ の __評価値__ として、この $x$ を __採用__ することにします。

:::note
$x$ が __増えれば__、__評価値__ が __増える__ という __条件が満たされる__ のであれば、__評価値__ の __式__ は __どのような式__ でも __構いません__。例えば、__下記__ の __式__ でも __かまいません__ が、特に理由がない限り、先程紹介した __最も簡単な式__ を __採用__ するのが __一般的__ でしょう。

$評価値 = a × x^2$
:::


## ルール 10 の評価値の設定

__下記__ は、__上記を元__ に、__ルール 10__ の __評価値を設定__ した表です。「__個別__」の列には、__同じ優先順位__ の __条件__ の __個別の評価値__ を記述します。「__評価値__」の列が __？__ になっているのは、__新しい条件__ を __追加__ したため、__評価値__ を __設定し直す__ 必要があるためです。__？__ に __評価値__ を __設定する方法__ について少し考えてみて下さい。

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

### 『「自 2 敵 0 空 1」が存在しない』場合の評価値

まず、__優先順位__ が __6__ の『「__自 2 敵 0 空 1__」が __存在しない__』場合の __評価値__ について考えることにします。__この条件__ は、__優先順位__ が __6__ の所に __記述__ されていますが、__実際__ には、__優先順位__ が __5__ の『「__自 1 敵 0 空 2__」が __x 個存在__ する』という __条件__ と __同時に満たされる__ 条件で、__どちら__ の条件が __優先度が高いか__ は __わかっていません__。従って、__この条件__ は __優先順位__ が __5__ の __条件の中__ に __入れる必要__ があります。

『「__自 2 敵 0 空 1__」が __存在しない__』という条件は、『「__自 2 敵 0 空 1__」が __1 つ存在__ する』という __条件__ が __満たされなかった場合__ の __条件__ なので、この条件が満たされる場合の __評価値__ は __`0`__ になります。__下記__ は、__そのように修正__ した __表__ です。

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

__個別__ の __評価値__ が __`0`__ の __条件__ は、__評価値の計算__ に __影響を与えない__ ので、__表に記述__ する __意味__ が __ありません__。そのため、__上記の表__ は、__下記__ のように __まとめる__ ことが __できます__。

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

### 評価値の範囲の計算

表の __評価値__ の __列__ の __値__ は 、__上から大きい順__ に __並べる必要__ があります。__現時点__ では、__優先順位__ が __5__ の __個別の評価値のみ__ が __決まっている__ ので、その __評価値__ について __考察__ します。

__優先順位__ が __5__ の __評価値__ は、__個別__ の __評価値__ の __合計__ になので、__評価値の値__ は __状況__ によって __変化します__ が、その __範囲__ を __計算__ することは __可能__ です。

下記の、__それぞれ__ の __組み合わせ__ の __評価値__ の __関係__ を表す __式__ から、__評価値__ の __最小値__ が __0__ で、__最大値__ が __A + B__ であることが分かります。

* 0 < A < A + B
* 0 < B < A + B

今回の場合は、__A__ が __`1`__ で、__B__ が __`x`__ なので、__優先順位__ が __5__ の __評価値__ の  __範囲__ は __`0 ～ 1 + x`__ になります。下記は、そのことを __表に加えた__ ものです。



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

次に、__範囲の中__ に __`x`__ という __変数__ が __入っている__ ので、`x` が __とる値__ の __範囲__ を __考える必要__ があります。__`x`__ は、「__自 1 敵 0 空 2__」の __マークのパターン__ の __数__ を表し、その __最大値__ は、__ラインの数__ である __8__ なので、__`x`__ の __最大値__ は __8__  であることが __わかります__。

:::note
__実際__ には、__相手も着手__ を __行う__ ので、「__自 1 敵 0 空 2__」の __数__ が __8 になる__ ことは __ありません__ が、__？__ の __値を設定__ するために、__厳密__ な __`x` の範囲__ を __計算__ する __必要はない__ ので、__8 として計算する__ ことにします。
:::

下記は、__優先順位__ が __5__ の __評価値の範囲__ を __計算しやすい__ ように、__個別の条件__ の __評価値__ の横に、それぞれの __範囲__ を追加し、それを元に __評価値の範囲__ を計算したものです。

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

__評価値の範囲__ が __分かった__ ので、__？__ を __下記の表__ のように __決める__ ことができます。

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

## `ai10s` の定義

__評価値__ が __決まった__ ので、__`ai10s`__ を __定義__ します。

__ai9s__ では、それぞれの __優先順位__ の __条件の判定__ を __1 つの if 文__ で __行っていました__ が、__`ai10s`__ では、__優先順位__ が __5__ の __評価値__ を __計算する前__ にその __if 文__ を __終了する必要__ があります。

その後で __同じ優先順位__ が __5__ の __条件__ の __評価値__ を __加算__ する __計算を行う__ 際には、下記のプログラムのように、__評価値__ を __計算する変数__ を __用意__ し、__条件が満たされる__ たびに __加算__ し、最後に __計算した評価値__ を __return 文で返す__ という処理を行います。


* __5、9、17 行目__：__評価値__ を表に合わせて __修正__ する
* __20 行目__：__評価値の合計__ を __計算__ する局所変数 __`score`__ を __`0`__ で __初期化__ する
* __22、23 行目__：「__自 2 敵 0 空 1__」が __1 つ存在__ する場合に __評価値__ に __1 を加算__ する
* __25 行目__：__評価値__ に「__自 1 敵 0 空 2__」の __個数__ を __加算__ する
* __28 行目__：__計算した評価値__ を __返り値__ として __返す__

```python
 1  def ai10s(mb, debug=False):
 2      def eval_func(mb):
 3          # 真ん中のマスに着手している場合は、評価値として 12 を返す
 4          if mb.last_move == (1, 1):
 5              return 12
 6   
 7          # 自分が勝利している場合は、評価値として 11 を返す
 8          if mb.status == mb.last_turn:
 9              return 11
10
11          markpats = mb.count_markpats()
12          # 相手が勝利できる場合は評価値として -1 を返す
13          if markpats[Markpat(last_turn=0, turn=2, empty=1)] > 0:
14              return -1
15          # 次の自分の手番で自分が必ず勝利できる場合は評価値として 10 を返す
16          elif markpats[Markpat(last_turn=2, turn=0, empty=1)] >= 2:
17              return 10
18
19          # 評価値の合計を計算する変数を 0 で初期化する
20          score = 0
21          # 次の自分の手番で自分が勝利できる場合は評価値に 1 を加算する
22          if markpats[Markpat(last_turn=2, turn=0, empty=1)] == 1:
23              score += 1
24          # 「自 1 敵 0 空 2」の数だけ、評価値を加算する
25          score += markpats[Markpat(last_turn=1, turn=0, empty=2)]
26        
27          # 計算した評価値を返す
28          return score
29
30      return ai_by_score(mb, eval_func, debug=debug) 
```

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

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

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

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

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

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

```diff_python
-def ai9s(mb, debug=False):
+def ai10s(mb, debug=False):
    def eval_func(mb):       
        # 真ん中のマスに着手している場合は、評価値として 12 を返す
        if mb.last_move == (1, 1):
-           return 4
+           return 12
    
        # 自分が勝利している場合は、評価値として 11 を返す
        if mb.status == mb.last_turn:
-           return 3
+           return 11

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

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

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


## `ai2` と `ai9s` との対戦

基準となる __`ai2`__ と、__これまで__ に作成した __最強の AI__ である __`ai9s`__ と対戦することにします。まず、下記のプログラムで __`ai2`__ と対戦します。

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

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

```
ai10s VS ai2
count     win    lose    draw
o        9737       0     263
x        8564     263    1173
total   18301     263    1436

ratio     win    lose    draw
o       97.4%    0.0%    2.6%
x       85.6%    2.6%   11.7%
total   91.5%    1.3%    7.2%
```

下記は、__`ai9s` VS `ai2`__ と __`ai10s` VS `ai2`__ の __対戦結果__ の __表__ です。__`ai2` に対して__、__`ai10s`__ のほうが、__`ai9s`__ より __勝率が若干低くなる__ ことが __確認__ できます。このことから、__新しい条件__ が __`ai2`__ に対して __何らかの理由__ で __若干不利__ になる __可能性__ があります。

| 関数名 | o 勝 | o 負 | o 分 | x 勝 | x 負 | x 分 | 勝 | 負 | 分 |
|:-:|--:|--:|--:|--:|--:|--:|--:|--:|--:|
| __`ai9s`__  | __98.7__ | __0.1__ | 1.2 | __89.6__ | __2.4__ | 8.0 | __94.1__ | __1.3__ | 4.6 |
| __`ai10s`__  | __97.4__ | __0.0__ | 2.6 | __85.6__ | __2.6__ | 11.7 | __91.5__ | __1.3__ | 7.2 |


次に、__`ai9s`__ と __対戦__ を行います。__実行結果__ の __通算成績__ の __勝率__ が __約 40 %__、__敗率__ が __0 %__ となることから、__`ai10s`__ が __`ai9s` に__ 対して __大幅に強くなっている__ ことが確認できます。特に、__`ai9s`__ に対する __敗率__ が __0%__ は、これまでにない __大きな成果__ であると言えるでしょう。
```python
ai_match(ai=[ai10s, ai9s])
```

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

```
ai10s VS ai9s
count     win    lose    draw
o        7478       0    2522
x         791       0    9209
total    8269       0   11731

ratio     win    lose    draw
o       74.8%    0.0%   25.2%
x        7.9%    0.0%   92.1%
total   41.3%    0.0%   58.7%
```

## 評価値の調整

先程、__評価値__ の __設定方法__ として、__下記__ のような __方法を紹介__ しました。

1. __とりあえず__ おおざっぱに __評価値__ を __設定__ して __AI を実装__ する
2. __実装した AI__ と __他の AI__  を __対戦__ させて、__強さを評価__ する
3. __A__ または __B__ の __評価値__ を __少し変化__ させて、__対戦__ させる
4. __手順 2__ と __3__ を __何度も繰り返し__、__最も成績が良い__ 評価値の __組み合わせ__ を __採用__ する

そこで、「__自 2 敵 0 空 1__」が __1 つ存在__ する場合の __評価値__ を __1__ から __5__ に __変えてみる__ ことにします。その際に、__`ai10s`__ の下記の __3 行目__ の __`score += 1`__ を __`score += 5`__ に __変えるだけで良い__ のではないかと __思う人がいるかもしれません__ が、__それだけ__ では __バグが発生__ してしまいます。どのようなバグが発生するかについて少し考えてみて下さい。

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

下記は、「__自 2 敵 0 空 1__」が __1 つ存在__ する場合の __評価値__ を __5 に設定__ した場合の __表__ です。

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

表から、__優先順位__ が __5__ の __評価値__ の __範囲__ が __`0 ~ 9`__ から、__`0 ~ 13`__ に __変化__ したことがわかりますが、__それ以外__ の __優先順位__ の __評価値__ を __変えていない__ ので、__優先順位__ が __5__ の __評価値__ が、それより __上の行__ の __評価値より__ も __高く__ なってしまう __可能性が生じています__。

下記は、__新しい評価値__ の __範囲__ に __応じて__、__他の評価値__ を __修正__ した __表__ です。__修正する際__ に、下記のように __それぞれ__ の __優先順位__ の __評価値__ の __間隔__ を __100__ のように __大きな値にする__ ことで、__評価値__ の __計算式__ を __多少修正__ した場合でも、__上下の行__ の __評価値__ の __大小関係__ が __壊れにくくなる__ という __工夫__ を行っています。

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

下記のプログラムは、__上記の評価値__ を計算するように __修正__ した __`ai10s`__ です。

* __5、9、14，17 行目__：__評価値__ を __修正__ した
* __23 行目__：__評価値__ の __加算__ を __1 から 5__ に __修正__ した
 
```python
 1  def ai10s(mb, debug=False):
 2      def eval_func(mb):      
 3          # 真ん中のマスに着手している場合は、評価値として 300 を返す
 4          if mb.last_move == (1, 1):
 5              return 300
 6   
 7          # 自分が勝利している場合は、評価値として 200 を返す
 8          if mb.status == mb.last_turn:
 9              return 200
10
11          markpats = mb.count_markpats()
12          # 相手が勝利できる場合は評価値として -100 を返す
13          if markpats[Markpat(last_turn=0, turn=2, empty=1)] > 0:
14              return -100
15          # 次の自分の手番で自分が必ず勝利できる場合は評価値として 100 を返す
16          elif markpats[Markpat(last_turn=2, turn=0, empty=1)] >= 2:
17              return 100
18
19          # 評価値の合計を計算する変数を 0 で初期化する
20          score = 0        
21          # 次の自分の手番で自分が勝利できる場合は評価値に 5 を加算する
22          if markpats[Markpat(last_turn=2, turn=0, empty=1)] == 1:
23              score += 5
24          # 「自 1 敵 0 空 2」の数だけ、評価値を加算する
25          score += markpats[Markpat(last_turn=1, turn=0, empty=2)]
26        
27          # 計算した評価値を返す
28          return score
29
30      return ai_by_score(mb, eval_func, debug=debug)
```

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

```python
def ai10s(mb, 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()
        # 相手が勝利できる場合は評価値として -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        
        # 次の自分の手番で自分が勝利できる場合は評価値に 5 を加算する
        if markpats[Markpat(last_turn=2, turn=0, empty=1)] == 1:
            score += 5
        # 「自 1 敵 0 空 2」の数だけ、評価値を加算する
        score += markpats[Markpat(last_turn=1, turn=0, empty=2)]
        
        # 計算した評価値を返す
        return score

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

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

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

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

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

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


## 修正した `ai10s` と `ai2`、`ai9s` との対戦

修正した __`ai10s`__ で __`ai2`__、__`ai9s`__ と __対戦__ することにします。まず、下記のプログラムで __`ai2`__ と対戦します。

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

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

```
ai10s VS ai2
count     win    lose    draw
o        9904       0      96
x        9002     305     693
total   18906     305     789

ratio     win    lose    draw
o       99.0%    0.0%    1.0%
x       90.0%    3.0%    6.9%
total   94.5%    1.5%    3.9%
```

下記は、先程の __`ai9s` VS `ai2`__ と __`ai10s` VS `ai2`__ の対戦結果に、__今回の対戦結果__ を __加えた表__ です。今回の対戦結果では、__`ai9s` VS `ai2`__ とほぼ同じ成績になることが分かります。

| 関数名 | o 勝 | o 負 | o 分 | x 勝 | x 負 | x 分 | 勝 | 負 | 分 |
|:-:|--:|--:|--:|--:|--:|--:|--:|--:|--:|
| __`ai9s`__  | __98.7__ | __0.1__ | 1.2 | __89.6__ | __2.4__ | 8.0 | __94.1__ | __1.3__ | 4.6 |
| __`ai10s`__(評価値 1)  | __97.4__ | __0.0__ | 2.6 | __85.6__ | __2.6__ | 11.7 | __91.5__ | __1.3__ | 7.2 |
| __`ai10s`__(評価値 5)  | __99.0__ | __0.0__ | 1.0 | __90.0__ | __3.0__ | 6.9 | __94.5__ | __1.5__ | 3.9 |

次に、__`ai9s`__ と __対戦__ を行います。

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

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

```
ai10s VS ai9s
count     win    lose    draw
o        4878       0    5122
x         802       0    9198
total    5680       0   14320

ratio     win    lose    draw
o       48.8%    0.0%   51.2%
x        8.0%    0.0%   92.0%
total   28.4%    0.0%   71.6%
```

下記は、先程の __`ai10s` VS `ai9s`__ 対戦結果に、__今回の対戦結果__ を __加えた表__ です。表から、相変わらず __`ai9s`__ に対しては __`ai10s`__ は __強いものの__、__評価値__ を __5__ とすることで、__〇 を担当__ した場合の __勝率__ が __かなり低くなる__ ことがわかります。このようなことが起きるのは、『__「自 2 敵 0 空 1」が 1 つ存在する__』場合の __評価値__ を __高く設定しすぎた__ ことによって、『__「自 2 敵 0 空 1」が 1 つ存在する__』場合の __重要度__ を、__本来の重要度より__ も __高く見積もりすぎる__ という、__過大評価__ が行われてしまったせいです。

| 評価値 | o 勝 | o 負 | o 分 | x 勝 | x 負 | x 分 | 勝 | 負 | 分 |
|:-:|--:|--:|--:|--:|--:|--:|--:|--:|--:|
| __1__  | 74.8 | 0.0 | 25.2 | 7.9 | 0.0 | 92.1 | 41.3 | 0.0 | 58.7 |
| __5__  | 48.8 | 0.0 | 51.2 | 8.0 | 0.0 | 92.0 | 28.4 | 0.0 | 71.6 |

__適切__ な __評価値__ を __求める__ ために、__評価値__ を __2__、__3__、__4__ に __設定__ して __`ai2`__、__`ai9s`__ と __対戦__ を __行う__ ことにします。なお、プログラムと実行結果は長くなるので省略し、__結果の表__ だけを __示します__。興味がある方は、自分でプログラムを修正して対戦を行ってみて下さい。

__`ai2`__ との __対戦結果__

| 評価値 | o 勝 | o 負 | o 分 | x 勝 | x 負 | x 分 | 勝 | 負 | 分 |
|:-:|--:|--:|--:|--:|--:|--:|--:|--:|--:|
| __1__ | __97.4__ | __0.0__ | 2.6 | __85.6__ | __2.6__ | 11.7 | __91.5__ | __1.3__ | 7.2 |
| __2__ | __98.7__ | __0.0__ | 1.3 | __90.8__ | __2.8__ | 6.4 | __94.7__ | __1.4__ | 3.8 |
| __3__ | __98.8__ | __0.0__ | 1.2 | __90.1__ | __3.1__ | 6.8 | __94.5__ | __1.5__ | 4.0 |
| __4__ | __98.9__ | __0.0__ | 1.1 | __90.3__ | __2.9__ | 6.8 | __94.6__ | __1.5__ | 3.9 |
| __5__  | __99.0__ | __0.0__ | 1.0 | __90.0__ | __3.0__ | 6.9 | __94.5__ | __1.5__ | 3.9 |

__`ai9s`__ との __対戦結果__

| 評価値 | o 勝 | o 負 | o 分 | x 勝 | x 負 | x 分 | 勝 | 負 | 分 |
|:-:|--:|--:|--:|--:|--:|--:|--:|--:|--:|
| __1__  | 74.8 | 0.0 | 25.2 | 7.9 | 0.0 | 92.1 | 41.3 | 0.0 | 58.7 |
| __2__  | 55.6 | 0.0 | 44.4 | 8.2 | 0.0 | 91.8 | 31.9 | 0.0 | 68.1 |
| __3__  | 49.5 | 0.0 | 50.5 | 8.8 | 0.0 | 91.2| 29.1 | 0.0 | 70.9 |
| __4__  | 49.4 | 0.0 | 50.6 | 7.9 | 0.0 | 92.1 | 28.7 | 0.0 | 71.3 |
| __5__  | 48.8 | 0.0 | 51.2 | 8.0 | 0.0 | 92.0 | 28.4 | 0.0 | 71.6 |

上記の結果から、__`ai2`__ に対しては、__評価値__ が __2 以上__ であれば __どれもほぼ同じ__ で、__`ai9s`__ に対しては __評価値__ が __1__ の場合が __最も成績が良い__ ことが分かります。__`ai2`__ と __`ai9s`__ では、__最も成績が良い__ 評価値が __異なります__ が、このような場合は __どちらかを選択__ する __必要__ があります。__本記事__ では、__下記__ のような __理由__ で、__評価値__ として __1 を採用する__ ことにします。

* __`ai2`__ より __かなり強い `ai9s`__ に対する __成績__ の方が __重要__ と __考えられる__
* __評価値__ を __2 以上__ にすると、__`ai9s`__ に対する __対戦成績__ が __急激__ に __悪くなる__

:::note
__評価値__ は __整数__ である __必要__ は __ありません__。興味と余裕がある方は、__評価値__ に __1.5__ や __2.5__ などを __設定__ して __対戦__ させてみて下さい。__さらに良い評価値__ を __見つける__ ことが __できるかも__ しれません。
:::

# ルール 11

次に、『「__自 0 敵 1 空 2__」が __最も少ない__ 着手を行う』という __条件__ を __ルール 10__ に __追加__ した __ルール 11__ を __定義__ する事にします。この条件は、__ルール 10__ の __優先順位__ が __5__ の __条件の一つ__ として __追加__ することができるので、__ルール 11__ は __下記の表__ のように __定義__ できます。

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

## 評価値の設定

「__自 0 敵 1 空 2__」が __最も少ない__ 着手を __行うようにする__ ためには、「__自 0 敵 1 空 2__」の __数が多い程__、__評価値__ を __低く計算__ する必要があります。その方法は、簡単で「__自 0 敵 1 空 2__」の __数__ を $x$ とした場合に、$評価値 = -x$ のように、__負の評価値__ を __計算__ するように __式を設定__ します。下記の表は、新しい __条件と評価値__ を __ルール 10__ の __表に加えた__ ものです。

:::note
__一般的__ に __評価値__ には、__自分__ が __有利な場合__ は __正の値__ を、自分が __不利な場合__ は __負の値__ を __設定します__。
:::

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

__優先順位__ ごとの __評価値__ の __間隔__ を __大きくしておいた__ おかげで、__優先順位__ が __5__ の __条件__ に、__新しい条件__ を __追加__ しても、__他の優先順位__ の __評価値__ を __変更する必要__ は __ありません__。

## `ai11s` の定義と、`ai2`、`ai10s` との対戦

__評価値__ の __表__ を元に、__`ai11s`__ は __下記__ のプログラムのように __定義__ できます。なお、『__「自 2 敵 0 空 1」が 1 つ存在する__』場合の __評価値__ は、__1 に戻しました__。

* __5、6 行目__：「__自 0 敵 1 空 2__」が __y 個存在__ する場合に、__評価値__ から __y__ を __減算__ する

```python
1  def ai11s(mb, debug=False):
2      def eval_func(mb):      
元と同じなので省略
3          # 「自 1 敵 0 空 2」の数だけ、評価値を加算する
4          score += markpats[Markpat(last_turn=1, turn=0, empty=2)]
5          # 「自 0 敵 1 空 2」の数だけ、評価値を減算する
6          score -= markpats[Markpat(last_turn=0, turn=1, empty=2)]
元と同じなので省略
```

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

```python
def ai11s(mb, 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()
        # 相手が勝利できる場合は評価値として -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        
        # 次の自分の手番で自分が勝利できる場合は評価値に 1 を加算する
        if markpats[Markpat(last_turn=2, turn=0, empty=1)] == 1:
            score += 1
        # 「自 1 敵 0 空 2」の数だけ、評価値を加算する
        score += markpats[Markpat(last_turn=1, turn=0, empty=2)]
        # 「自 0 敵 1 空 2」の数だけ、評価値を減算する
        score -= markpats[Markpat(last_turn=0, turn=1, empty=2)]
        
        # 計算した評価値を返す
        return score

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

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

```diff_python
-def ai10s(mb, debug=False):
+def ai11s(mb, debug=False):
    def eval_func(mb):      
元と同じなので省略
        # 「自 1 敵 0 空 2」の数だけ、評価値を加算する
        score += markpats[Markpat(last_turn=1, turn=0, empty=2)]
+       # 「自 0 敵 1 空 2」の数だけ、評価値を減算する
+       score -= markpats[Markpat(last_turn=0, turn=1, empty=2)]
元と同じなので省略
```
</details>


__`ai11s`__ の __強さ__ を __確認__ するために、__`ai2`__、__`ai10s`__ と __対戦__ することにします。まず、下記のプログラムで __`ai2`__ と対戦します。

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

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

```
ai11s VS ai2
count     win    lose    draw
o        9813       0     187
x        8251     193    1556
total   18064     193    1743

ratio     win    lose    draw
o       98.1%    0.0%    1.9%
x       82.5%    1.9%   15.6%
total   90.3%    1.0%    8.7%
```

下記は、__`ai11s` VS `ai2`__ と、評価値が 1 の場合の __`ai10s` VS `ai2`__ の __対戦結果__ の表です。__× を担当__ する場合の __成績__ が __若干悪くなっています__ が、__成績__ は __ほぼ同じ__ です。

| 関数名 | o 勝 | o 負 | o 分 | x 勝 | x 負 | x 分 | 勝 | 負 | 分 |
|:-:|--:|--:|--:|--:|--:|--:|--:|--:|--:|
| __`ai10s`__  | __97.4__ | __0.0__ | 2.6 | __85.6__ | __2.6__ | 11.7 | __91.5__ | __1.3__ | 7.2 |
| __`ai11s`__  | __98.1__ | __0.0__ | 1.9 | __82.5__ | __1.9__ | 15.6 | __90.3__ | __1.0__ | 8.7 |


次に、__`ai10s`__ と __対戦__ を行います。
```python
ai_match(ai=[ai10s, ai9s])
```

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

```
ai11s VS ai10s
count     win    lose    draw
o        2186       0    7814
x           0    5014    4986
total    2186    5014   12800

ratio     win    lose    draw
o       21.9%    0.0%   78.1%
x        0.0%   50.1%   49.9%
total   10.9%   25.1%   64.0%
``` 

__実行結果__ の __通算成績__ から、__`ai11s`__ が __`ai10s` に__ 対して __弱くなってしまう__ ことが __確認__ できました。何故このようになってしまったかにつては、次回の記事で説明することにします。


# 今回の記事のまとめ

今回の記事では、__強い AI__ の __ルール__ を作るための __条件の種類__ として、__必要条件__、__十分条件__、その __どちらでもない条件__ について __説明__ しました。

また、__複数の条件__ を __同時に考慮__ する __評価値__ の __計算方法__ について __説明__ しました。__評価値__ を __利用__ することで、[以前の記事](https://qiita.com/ysgeso/items/8f3d4b3d2c02ef51a70e#%E8%A4%87%E6%95%B0%E3%81%AE%E6%9D%A1%E4%BB%B6%E3%82%92%E7%B5%84%E3%81%BF%E5%90%88%E3%82%8F%E3%81%9B%E3%81%A6%E8%80%83%E6%85%AE%E3%81%97%E3%81%9F%E3%81%84%E5%A0%B4%E5%90%88%E3%81%AE%E5%95%8F%E9%A1%8C%E7%82%B9)で説明した、__複数の条件__ を __組み合わせて考慮__ したい場合の __問題点__ を __解決__ することが __できます__。

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

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

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

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

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

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

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

# 次回の記事

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

[^1]:たとえば、__1000 年後の世界を自分の目で見る__ いう __目的__ に対する __必要十分条件__ は「__1000年後まで生きる__」ですが、その __条件__ を __達成__ することは現実的には __不可能__ でしょう。なお、__必要条件__ の一つは「__10 年以上生きる__」、__十分条件__ の一つは「__1100年 後まで生きる__」です
[^2]:既に ルール 8 が分かりづらいと思っている方はいませんか？
[^3]:その際に、__次の条件__ を __考える__ ために、__強くならなかった理由__ を __考察する__ と良いでしょう