1
1

Pythonで〇×ゲームのAIを一から作成する その63 最善手の優劣

Last updated at Posted at 2024-03-17

目次と前回の記事

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

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

これまでに作成した AI

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

ルール アルゴリズム
ルール1 左上から順空いているマス を探し、最初に見つかったマス着手 する
ルール2 ランダム なマスに 着手 する
ルール3 真ん中 のマスに 優先的着手 する
既に 埋まっていた場合ランダム なマスに 着手 する
ルール4 真ん中 のマスの 優先的着手 する
既に 埋まっていた場合ランダム なマスに 着手 する
ルール5 勝てる場合勝つ
そうでない場合は ランダム なマスに 着手 する
ルール6 勝てる場合勝つ
そうでない場合は 相手の勝利阻止 する
そうでない場合は ランダム なマスに 着手 する
ルール6改 勝てる場合勝つ
そうでない場合は 相手勝利できる 着手を 行わない
そうでない場合は ランダム なマスに 着手 する
ルール7 真ん中 のマスに 優先的着手 する
そうでない場合は 勝てる場合勝つ
そうでない場合は 相手の勝利阻止 する
そうでない場合は ランダム なマスに 着手 する
ルール7改 真ん中 のマスに 優先的着手 する
そうでない場合は 勝てる場合勝つ
そうでない場合は 相手勝利できる 着手を 行わない
そうでない場合は ランダム なマスに 着手 する
ルール8 真ん中 のマスに 優先的着手 する
そうでない場合は 勝てる場合勝つ
そうでない場合は 相手勝利できる 着手を 行わない
そうでない場合は、自分の手番勝利できる ように、「自 2 敵 0 空 1」が 1 つ以上 存在する 局面になる着手を行う
そうでない場合は ランダム なマスに 着手 する
ルール9 真ん中 のマスに 優先的着手 する
そうでない場合は 勝てる場合勝つ
そうでない場合は 相手勝利できる 着手を 行わない
そうでない場合は、自分の手番必ず勝利できる ように、「自 2 敵 0 空 1」が 2 つ以上存在する 局面になる着手を行う
そうでない場合は、自分の手番勝利できる ように、「自 2 敵 0 空 1」が 1 つ存在する 局面になる着手を行う
そうでない場合は ランダム なマスに 着手 する
ルール10 真ん中 のマスに 優先的着手 する
そうでない場合は 勝てる場合勝つ
そうでない場合は 相手勝利できる 着手を 行わない
そうでない場合は、自分の手番必ず勝利できる ように、「自 2 敵 0 空 1」が 2 つ以上存在する 局面になる着手を行う
そうでない場合は、以下 の 2 つを 総合的に判断 して着手を行う
  • 自分の手番勝利できる ように、「自 2 敵 0 空 1」が 1 つ存在する 局面になる着手を行う
  • 自分有利になる ように、「自 1 敵 0 空 2」が 最も多い 着手を行う
そうでない場合は ランダム なマスに 着手 する
ルール11 真ん中 のマスに 優先的着手 する
そうでない場合は 勝てる場合勝つ
そうでない場合は 相手勝利できる 着手を 行わない
そうでない場合は、自分の手番必ず勝利できる ように、「自 2 敵 0 空 1」が 2 つ以上存在する 局面になる着手を行う
そうでない場合は、以下 の 3 つを 総合的に判断 して着手を行う
  • 自分の手番勝利できる ように、「自 2 敵 0 空 1」が 1 つ存在する 局面になる着手を行う
  • 自分有利になる ように、「自 1 敵 0 空 2」が 最も多い 着手を行う
  • 相手不利になる ように、「自 0 敵 1 空 2」が 最も少ない 着手を行う
そうでない場合は ランダム なマスに 着手 する
ルール12 真ん中 のマスに 優先的着手 する
そうでない場合は 勝てる場合勝つ
そうでない場合は 相手勝利できる 着手を 行わない
そうでない場合は、自分の手番必ず勝利できる ように、「自 2 敵 0 空 1」が 2 つ以上存在する 局面になる着手を行う
そうでない場合は、斜め方向〇×〇並び他の 6 マス空のマス の場合に、いずれか辺のマス に着手を行う
そうでない場合は、以下 の 3 つを 総合的に判断 して着手を行う
  • 自分の手番勝利できる ように、「自 2 敵 0 空 1」が 1 つ存在する 局面になる着手を行う
  • 自分有利になる ように、「自 1 敵 0 空 2」が 最も多い 着手を行う
  • 相手不利になる ように、「自 0 敵 1 空 2」が 最も少ない 着手を行う
そうでない場合は ランダム なマスに 着手 する
ルール12 改 相手勝利できる 着手を 行わない
そうでない場合は、自分の手番必ず勝利できる ように、「自 2 敵 0 空 1」が 2 つ以上存在する 局面になる着手を行う
そうでない場合は、斜め方向〇×〇並び他の 6 マス空のマス の場合に、いずれか辺のマス に着手を行う
そうでない場合は、以下 の 3 つを 総合的に判断 して着手を行う
  • 自分の手番勝利できる ように、「自 2 敵 0 空 1」が 1 つ存在する 局面になる着手を行う
  • 自分有利になる ように、「自 1 敵 0 空 2」が 最も多い 着手を行う
  • 相手不利になる ように、「自 0 敵 1 空 2」が 最も少ない 着手を行う
そうでない場合は ランダム なマスに 着手 する

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

ai11s
ver 1
ai11s
ver 2
ai11s ver 3
ai12s
「自 2 敵 0 空 1」が 1 つの場合の評価値 1 2 2
「自 1 敵 0 空 2」が 1 つあたりの評価値 1 1 0.5
「自 0 敵 1 空 2」が 1 つあたりの評価値 -1 -1 1

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

関数名 o 勝 o 負 o 分 x 勝 x 負 x 分 欠陥
ai1
ai1s
78.1 17.5 4.4 44.7 51.6 3.8 61.4 34.5 4.1 あり
ai2
ai2s
58.7 28.8 12.6 29.1 58.6 12.3 43.9 43.7 12.5
ai3
ai3s
69.3 19.2 11.5 38.9 47.6 13.5 54.1 33.4 12.5
ai4
ai4s
83.0 9.5 7.4 57.2 33.0 9.7 70.1 21.3 8.6 あり
ai5
ai5s
81.2 12.3 6.5 51.8 39.8 8.4 66.5 26.0 7.4
ai6 88.9 2.2 8.9 70.3 6.2 23.5 79.6 4.2 16.2
ai6s 88.6 1.9 9.5 69.4 9.1 21.5 79.0 5.5 15.5
ai7
ai7s
95.8 0.2 4.0 82.3 2.4 15.3 89.0 1.3 9.7
ai8s 98.2 0.1 1.6 89.4 2.5 8.1 93.8 1.3 4.9
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
ai11s ver 1 98.1 0.0 1.9 82.5 1.9 15.6 90.3 1.0 8.7 あり
ai11s ver 2 98.8 0.0 1.2 87.7 2.4 10.0 93.2 1.2 5.6
ai11s ver 3 99.1 0.0 0.9 87.7 0.8 11.5 93.4 0.4 6.2
ai12s ver 2 98.9 0.0 1.1 88.2 0.0 11.8 93.5 0.0 6.5
ai12s ver 3 95.8 0.0 4.2 86.9 0.0 13.1 91.3 0.0 8.7 あり

ai12s ver 2 VS ai2ai12s ver 3 VS ai2 の違いの検証

前回の記事では、「勝てる場合勝つ」と、『「自 2 敵 0 空 1」が 2 つ以上存在 する着手を行う』に対する 評価値どちらも 200設定 した ai12s ver 3 を作成しましたが、その際に、下記の表のように、ai12s ver 2 VS ai2比較 して、ai12s ver 3 VS ai2 のほうが 引き分け率増えてしまう という バグが発生 することが わかりました

関数名 o 勝 o 負 o 分 x 勝 x 負 x 分
ai12s ver 2 98.9 0.0 1.1 88.2 0.0 11.8 93.5 0.0 6.5
ai12s ver 3 95.8 0.0 4.2 86.9 0.0 13.1 91.3 0.0 8.7

また、対戦を検証 することで、ai12s ver 3バグの原因 となっている 局面を発見 しました。今回の記事では、最初にその バグの原因検証 します。

下図は、前回の記事で発見した、結果異なる ai12s ver 2 VS ai2ai12s ver 3 VS ai2試合経過上下に並べた ものです。

図から、同じ乱数の種 を使っているため、4 手目まで同じ着手選択 されていることが わかります。そこで、5 手目以降ai12s ver2ver 3着手を検討 することにします。なお、ai2ランダムな着手 を行う AI なので、ai2着手の検討省略 します。

5 手目の検証

ai12s ver 2 は、「勝てる場合勝つ」という 条件評価値最も高い ので、上記の局面(0, 2)着手 して 勝利 します。特に難しい点はないでしょう。

次に、ai12s ver 3上記の局面計算 する 評価値 を下記のプログラムで 表示 します。

from marubatsu import Marubatsu
from ai import ai12s

mb = Marubatsu()
mb.move(1, 1)
mb.move(2, 1)
mb.move(2, 0)
mb.move(2, 2)
ai12s(mb, score_victory=200, debug=True)
実行結果(長いのでクリックして開いてください)
Start ai_by_score
Turn o
..o
.ox
..X

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

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

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

defaultdict(<class 'int'>,
            {Markpat(last_turn=0, turn=1, empty=2): 1,
             Markpat(last_turn=1, turn=0, empty=2): 3,
             Markpat(last_turn=1, turn=1, empty=1): 1,
             Markpat(last_turn=1, turn=2, empty=0): 1,
             Markpat(last_turn=2, turn=0, empty=1): 1,
             Markpat(last_turn=2, turn=1, empty=0): 1})
score 2.5 best score 200
====================
move (0, 2)
winner o
..o
.ox
O.x

score 200 best score 200
APPEND
  best moves [(0, 0), (1, 0), (0, 2)]
====================
move (1, 2)
Turn x
..o
.ox
.Ox

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

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

勝利 「201」 「021」 「102」 「012」 評価値
評価値 200 1:+2
2~:200
-100 1 つで
+0.5
1 つで
-1
1 2 2 1 200
2 3 1 200
3 1 3 1 2.5
4 1 3 200
5 2 1 200

ai12s ver 3 は、「勝てる場合勝つ」と、『「自 2 敵 0 空 1」が 2 つ以上存在 する着手を行う』に対する 評価値どちらも 200設定 されており、上記の表 でも そのようになっています。そのため、この局面 では、勝利 する 合法手 4 だけでなく、合法手 125評価値200 になるため、合法手 1245中から ランダムに 選択 が行われることが わかります。そのことは、下記の 実行結果最後の 2 行 からも 確認 できます。

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

勝てる場合勝つ」と、『「自 2 敵 0 空 1」が 2 つ以上存在 する着手を行う』という 条件評価値 は、ヒューリスティック条件より優先順位が高い ため、ヒューリスティックな条件 による 評価値考慮されない ので 200なります

従って、上記の局面 では、1 / 4 = 25 %確率(1, 2)選択 されたことが わかります

7 手目の検証

ai12s ver 3 が、上記の局面計算 する 評価値下記 のプログラムで 表示 します。

mb.move(1, 2)
mb.move(0, 2)
ai12s(mb, score_victory=200, debug=True)
実行結果(長いのでクリックして開いてください)
Start ai_by_score
Turn o
..o
.ox
Xox

legal_moves [(0, 0), (1, 0), (0, 1)]
====================
move (0, 0)
Turn x
O.o
.ox
xox

defaultdict(<class 'int'>,
            {Markpat(last_turn=1, turn=1, empty=1): 2,
             Markpat(last_turn=1, turn=2, empty=0): 2,
             Markpat(last_turn=2, turn=0, empty=1): 2,
             Markpat(last_turn=2, turn=1, empty=0): 2})
score 200 best score -inf
UPDATE
  best score 200
  best moves [(0, 0)]
====================
move (1, 0)
winner o
.Oo
.ox
xox

score 200 best score 200
APPEND
  best moves [(0, 0), (1, 0)]
====================
move (0, 1)
Turn x
..o
Oox
xox

defaultdict(<class 'int'>,
            {Markpat(last_turn=1, turn=0, empty=2): 1,
             Markpat(last_turn=1, turn=1, empty=1): 2,
             Markpat(last_turn=1, turn=2, empty=0): 2,
             Markpat(last_turn=2, turn=0, empty=1): 1,
             Markpat(last_turn=2, turn=1, empty=0): 2})
score 2.5 best score 200
====================
Finished
best score 200
best moves [(0, 0), (1, 0)]

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

勝利 「201」 「021」 「102」 「012」 評価値
評価値 200 1:+2
2~:200
-100 1 つで
+0.5
1 つで
-1
1 2 200
2 1 1 1 200
3 1 1 2.5

上記 から、合法手 12評価値200最も高い ので、その中 から 合法手が選択 されることが わかります。そのことは、下記の 実行結果最後の 2 行 からも 確認 できます。

best score 200
best moves [(0, 0), (1, 0)]

従って、上記の局面 は、1 / 2 = 50 %確率(0, 0)選択 されたことが わかります

8 手目以降の検証

上記は、8 手目以降 ですが、7 手目ai12s ver 3 が、『「自 2 敵 0 空 1」が 2 つ以上存在 する着手を行う』という 条件を満たす必勝の局面 での 最善手選択 したにもかかわらず、引き分けなっています。何故このようなことが起こったかについて少し考えてみて下さい。

上記の ai28 手目着手 した (1, 0) は、1 回着手 で、2 つ ある「自 2 敵 0 空 1」の マークのパターン同時無くしています。これが、引き分け になる 原因 です。

このことから、『「自 2 敵 0 空 1」が 2 つ以上存在 する着手を行う』という 条件 は、実は 必勝の局面 での 最善手選択 するための 十分条件 ではなかった ことが わかります

この条件問題点 は、一度相手の着手 で、複数 の「自 2 敵 0 空 1」の マークのパターン同時になくなる 場合があることですが、そのようなことが起きるのは、複数 の「自 2 敵 0 空 1」の マークのパターン の中の 空のマス1 つ共通している ような 場合 です。

図で説明 すると、下図7 手目 の局面では、「自 2 敵 0 空 1」の マークのパターン黄色い行オレンジの列2 つあります が、それぞれの マークのパターン の中の 空のマス は、(0, 1)マス共通 しており、1 つ しかありません。この場合は、相手緑のマス着手 を行うことで、「自 2 敵 0 空 1」の マークのパターン無くなります

従って、この条件必勝の局面最善手選択 するための 十分条件にする ためには、下記2 つ目条件加える ように 修正 する 必要あります

  • 自 2 敵 0 空 1」が 2 つ以上存在 する着手を行う
  • ただし、それらの マークのパターン の中の 空のマス が、2 つ以上存在 する

ところで、ai12s ver 2ai12s ver 3 は、どちらもルール 12条件合法手選択 していますが、ルール 12 には、上記 の「それらの マークのパターン の中の 空のマス が、2 つ以上存在 する」という 条件存在しません。それにもかかわらず、ai12s ver 2 VS ai2 では、上図のような、問題 のある 局面出現しません

その 理由 について 検証 してみた所、ai12s ver 2 が『「自 2 敵 0 空 1」が 2 つ以上存在 する着手』を 行った場合 に、上記2 つ目 の「それらの マークのパターン の中の 空のマス が、2 つ以上存在 する」という 条件必ず満たす ことが 判明 しました。

次は、そのようなことがおきる 理由 について 説明 します。

ai12s ver 2 が条件を満たす理由

ai12s ver 2 が『「自 2 敵 0 空 1」が 2 つ以上存在 する着手』を 行った場合 に、上記2 つ目の条件必ず満す ことを 示す ための 方法の一つ に、ai12s ver 2上記の条件反する下記 のような 合法手選択しない ことを 示す という 方法あります

  • 自 2 敵 0 空 1」が 2 つ以上存在 する着手を行う
  • ただし、それらの マークのパターン の中の 空のマス が、1 つだけ存在 する

下記は、上記の条件 を満たす ai12s ver 3 VS ai27 手目選択 した 局面 です。緑色 のマスが、「自 2 敵 0 空 1」の マークのパターン で、1 つ だけ 存在 する 空のマス です。

〇×ゲーム は、一度手番 で、一つのマス にしか 着手 を行えない ゲーム なので、上記の局面 の、1 つ前〇 の手番 である 5 手目局面 では、4 つ ある 〇 のマーク の中の、いずれか 1 つ配置されていない状態 になっています。そのため、1 つ前〇 の手番局面 は、以下いずれかなります

  • 黄色マスいずれか 1 つ配置されていない
  • オレンジマスいずれか 1 つ配置されていない

上記いずれの場合 であったとしても、1 つ前〇 の手番局面 では、他の色2 つのマス〇 が配置 されています。また、緑のマス も、必ず 空のマス なので、1 つ前〇 の手番局面 では、必ず「自 2 敵 0 空 1」が 1 つ存在 することになります。

実際 に、先程の ai12s ver 2 VS ai25 手目 の局面では、(0, 0)マス空いています が、オレンジ色 に「自 2 敵 0 空 1」が 1 つ存在 します。

下記 のような 条件 を満たす 合法手を選択 するためには、上図局面(0, 0)着手行う必要あります

  • 自 2 敵 0 空 1」が 2 つ以上存在 する着手を行う
  • ただし、それらの マークのパターン の中の 空のマス が、1 つだけ存在 する

ai12s ver 2 は、「勝てる場合勝つ」という 条件最も優先順位高い ので、上図の局面 では 必ず (0, 1)着手 して 勝利 します。従って、ai12s ver 2合法手選択 する場合に、上記 のような 条件 を満たす 合法手選択 することは ありません

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

  • 『「自 2 敵 0 空 1」が 2 つ以上存在 する着手を行う』という 条件だけ では、必勝の局面最善手選択 するための 十分条件 には ならない
  • 勝てる場合勝つ」という 条件 を、上記の条件 よりも 優先順位高くする ことで、上記の条件十分条件なる

ai12s ver 3 は、上記条件満たさない ため、先程の 7 手目 の局面のように、ai12s ver 2最善手必ず選択 する 局面 で、最善手選択できず引き分けなる場合あります。これが、ai12s ver 2 VS ai2比較 して、ai12s ver 3 VS ai2 のほうが 引き分け率増えてしまう という バグ原因 です。

なお、ai12s ver 2 が、上記説明 した 理由 から、必勝の局面最善手選択 するための 十分条件 になっていることは、筆者想定しておらず、前回の記事で ai12s ver 3問題見つかるまで は、『「自 2 敵 0 空 1」が 2 つ以上存在 する着手を行う』という 条件だけ で、十分条件なっている勘違い していました。そのため、ai12s ver 2必勝の局面最善手必ず選択 するように なっていた のは、単なる偶然すぎません

このように、ルールベースの AI では、正しい思っていた条件 が 実は 正しくなかった ということが 良くあります。そのため、作成した AI による 対戦意図通り結果ならなかった場合 は、プログラム だけでなく、ルール条件見直す必要 がある 場合がある ことを 忘れない ようにして下さい。

ルール 12 の、「相手勝利できる 着手を 行わない」という 条件 が『「自 2 敵 0 空 1」が 2 つ以上存在 する着手を行う』という 条件より優先順位高い ことは、上記と 同じ ような 意味持ちます。もし、「相手勝利できる 着手を 行わない」という 条件優先順位低くしてしまう と、相手の手番相手勝利 してしまう 可能性が生じる からです。

従って、厳密 には、先程の 必勝の局面最善手選択 するための 十分条件以下 のようになります。

  • 自 2 敵 0 空 1」が 2 つ以上存在 する着手を行う
  • ただし、「勝てる場合勝つ」や「相手勝利できる 着手を 行わない」という 条件優先 する

なお、記述長くなる ので、「相手勝利できる 着手を 行わない」という 条件の記述以後省略 することにします。

2 つの十分条件の違い

下記は、最初に紹介した 必勝の局面最善手選択 するための 十分条件 です。

  • 自 2 敵 0 空 1」が 2 つ以上存在 する着手を行う
  • ただし、それらの マークのパターン の中の 空のマス が、2 つ以上存在 する

一方、下記の条件満たした場合 に、上記の条件満たされる ので、下記の条件 も、必勝の局面最善手選択 するための 十分条件 であると言えます。

  • 自 2 敵 0 空 1」が 2 つ以上存在 する着手を行う
  • ただし、「勝てる場合勝つ」という 条件優先 する1

このように、同じ問題 に対して、その 問題を解決 するための 異なる十分条件考えられる ことが あります が、そのような場合は、どちらの十分条件 を使っても 構わない ので、使いやすい ほうを 利用 すると 良いでしょう

例えば、ai12s ver 2 は、上記の 後者十分条件利用 していますが、「勝てる場合勝つ」という 条件優先する処理 は、簡単記述できます

一方、前者十分条件 の、「それらの マークのパターン の中の 空のマス が、2 つ以上存在 する」という 条件利用 して 最善手を選択 することも でき、そちらの場合には、下記 のような 利点欠点 があります。

利点

  • 勝てる場合勝つ」場合と、『「自 2 敵 0 空 1」が 2 つ以上存在 する着手を行う』場合の 評価値同じ値設定 しても、ai12s ver 3 のような 問題発生しない

欠点

  • 自 2 敵 0 空 1」の マークのパターン の中の 空のマス数を数える プログラムの 記述 はそれほど 簡単ではなく処理複雑になる ため 処理時間長く なる

なお、この後で説明しますが、それぞれの 評価値同じ値設定 すると 別の問題発生する ことになるため、上記の利点 はあまり 有効 では ありません。そのため、本記事 では 前者の十分条件 によるプログラムは 記述しない ことにします。興味と時間がある方は、実際にプログラムを記述してみると良いでしょう。

ai12s ver 2 と ai12s ver 3 の優劣

ai12s ver 3 には、上記説明 したような 問題点 がありますが、ランダムな AI に対する 敗率0 % であることから、弱解決の AI であることには 変わりはありません

このような 問題点 があるにも 関わらずai12s ver 3弱解決の AI である 理由 は、〇×ゲーム引き分けのゲーム だからです。以前の記事で説明したように、引き分けのゲーム での 弱解決の AI は、必勝の局面 では、必敗の局面 につながる 合法手選択しない という 制約しかない ので、最善手ではない引き分けつながる合法手選択 しても かまいません。先ほどの 7 手目局面 では、ai12s ver3 は、必勝の局面 で、最善手ではない引き分け につながる 合法手選択 しているので、弱解決の AI条件満たしています

弱解決の AI自分の必勝のゲーム では、最強の AI同じ強さ を持ちますが、そうでない 場合は、このように、弱解決の AI どうし の中で、優劣存在 します。実際に、ai12s ver 2 より も、ai12s ver 3 のほう が、最強の AI近い AI です。従って、本記事 では ai12s ver 3パラメータ採用しない ことにします。

必勝の局面での最善手の優劣

下記の 2 つの条件 は、いずれも必勝の局面最善手選択 するための 十分条件 です。

  • 勝てる場合勝つ
  • 自 2 敵 0 空 1」の マークのパターン2 つ以上存在 し、それらの マークのパターン の中の 空のマス が、2 つ以上存在 する 合法手選択 する

従って、それらの 条件 を満たす 合法手両方存在 する場合は、その どちらを選択 しても、AI の強さ という 観点 からは、強さ変わりません

しかし、AI人間対戦 する場合に、人間にとって一般的どちらの最善手好ましく思えるか という 観点 では、この 2 種類最善手異なります

人間にとって最善手に違いが生じる例

具体例 を示します。下図〇 の手番局面 では、黄色(2, 2)着手 すると 〇 が勝利 するので、(2, 2)最善手 です。また、オレンジ4 つ のマスのどこに 着手 を行っても、『「自 2 敵 0 空 1」の マークのパターン の中の 空のマス が、2 つ以上存在 する』ので、それらも 最善手 です。つまり、この局面 では すべて合法手最善手 です。

ai12s ver 2 は、「勝てる場合勝つ」という 条件優先される ので、(2, 2) のみ選択 します。そのことは、下記 のプログラムの 実行結果最後の行 からも 確認できます

mb = Marubatsu()
mb.move(1, 1)
mb.move(1, 0)
mb.move(0, 0)
mb.move(1, 2)
ai12s(mb, debug=True)

実行結果の 最後の行

best moves [(2, 2)]
実行結果全体(長いのでクリックして開いてください)
Start ai_by_score
Turn o
ox.
.o.
.X.

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

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

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

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

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

score 300 best score 200
UPDATE
  best score 300
  best moves [(2, 2)]
====================
Finished
best score 300
best moves [(2, 2)]

一方、2 種類最善手評価値同じ である ai12s ver 3 の場合は、すべての合法手評価値同じ値 になる2ので、すべての合法手 の中から ランダム選択 が行われます。そのことは、下記 のプログラムの 実行結果最後の行 からも 確認できます

ai12s(mb, score_victory=200, debug=True)
from ai import ai12s
from marubatsu import Marubatsu

mb = Marubatsu()
mb.move(1, 1)
mb.move(1, 0)
mb.move(0, 0)
mb.move(1, 2)
ai12s(mb, debug=True)

実行結果の 最後の行

best moves [(2, 0), (0, 1), (2, 1), (0, 2), (2, 2)]
実行結果全体(長いのでクリックして開いてください)
Start ai_by_score
Turn o
ox.
.o.
.X.

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

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

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

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

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

score 200 best score 200
APPEND
  best moves [(2, 0), (0, 1), (2, 1), (0, 2), (2, 2)]
====================
Finished
best score 200
best moves [(2, 0), (0, 1), (2, 1), (0, 2), (2, 2)]

人間の心理を考慮に入れた最善手の優劣

ところで、人間 どうし〇×ゲーム対戦 する際に、下図の局面 で、相手オレンジ色 のマスに 着手 を行った場合、おそらく 多くの人 は、その場で すぐに勝てる局面なのに、わざわざ別の合法手を選択して 勝負を引き延ばされた と思い、気分が悪くなる人多い のではないでしょうか3人間どうし の対戦では、このような、勝利が確定 している局面で 勝負を引き延ばす ような 行為 の事を、なぶり殺し や、舐めプ(相手を 舐めたプレイ)などと呼び、一般的 には マナーが悪い行為 だと考えられています4。また、そのような行為を行うと、場合によっては 喧嘩に発展する ことも あります

先程示したように、ai12s ver 2 は、上記の局面必ず黄色 のマスに 着手 を行って 勝利します が、ai12s ver 3オレンジ色 のマスに 着手 をする 可能性ありますai12s ver 3 は、決められたルール に従って 着手を選択 しているだけなので、相手侮辱する意図ありません し、先程も述べたように、相手に勝利する という 意味 では、どの最善手選択 しても 同じ結果 になりますが、相手AI であると わかっていてもそのような着手 を行う AI対戦 した際に、多くの人間気分を害する のではないかと思います。

このように、人間 には 感情がある ため、人間と対戦 することを 想定した AI作成する場合 は、勝負を引き延ばす ような 最善手選択しない ようにしたほうが 良いでしょう。そのためには、勝負を長引かせない ような 最善手高い評価値設定する ことに 大きな意味生じます。なお、AI どうし でしか 対戦行わない のであれば、そのようなことを 気にする必要ありません

本記事では、作成した AI人間対戦 することも 想定 しているので、そのような意味でも、ai12s ver 3 を採用せずに、ai12s ver 2 のほうを 採用する ことにします。

必敗の局面での最善手の優劣

以前の記事で説明したように、必敗の局面 では、すべての合法手最善手 になりますが、それらの合法手優劣存在するでしょうか?その点について少し考えてみて下さい。

相手最強の AI の場合は、必敗の局面合法手優劣ありません が、相手最強の AI でない 場合は、優劣存在 します。例えば、下図× の手番局面 は、相手の手番オレンジ色いずれか着手 を行うと 相手が勝利 するので、必敗の局面 です。

従って、相手最強の AI の場合は、この局面×どの合法手選択 しても、敗北する ので、結果変わりません実際ai12s ver 2 は、下記 のプログラムの 実行結果最後の行 からわかるように、すべての合法手 の中から ランダム選択行います

mb.restart()
mb.move(1, 1)
mb.move(0, 0)
mb.move(2, 2)
mb.move(2, 1)
mb.move(1, 2)
ai12s(mb, debug=True)

実行結果の 最後の行

best moves [(1, 0), (2, 0), (0, 1), (0, 2)]
実行結果全体(長いのでクリックして開いてください)
Start ai_by_score
Turn x
x..
.ox
.Oo

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

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

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

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

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

ランダムな AI と対戦する場合の、必敗の局面での最善手の優劣の検証

一方、相手最強の AI でない 場合は、×下図5 手目局面 で、それぞれ合法手選択 した場合の 勝率異なります実際 に、それぞれ合法手選択 した際に、ランダムな AI である ai2s対戦 した場合の 勝率検証する ことにします。ただし、7 手目局面 では、×最善手を選択する ものとします。

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

上図左 は、5 手目×(1, 0)着手 した場合の 局面 です。この局面 で、〇 を担当 する ai2 は、1/3確率上図の合法手選択 します。その後×最善手選択 すると、上図 のような 結果なります

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

上図左 は、5 手目×(2, 0)着手 した場合の 局面 です。この局面 で、〇 を担当 する ai2 は、1/3確率上図の合法手選択 します。その後×最善手選択 すると、上図 のような 結果なります

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

上図左 は、5 手目×(0, 1)着手 した場合の 局面 です。この局面 で、〇 を担当 する ai2 は、1/3確率上図の合法手選択 します。その後×最善手選択 すると、上図 のような 結果なります

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

上図左 は、5 手目×(0, 2)着手 した場合の 局面 です。この局面 で、〇 を担当 する ai2 は、1/3確率上図の合法手選択 します。その後×最善手選択 すると、上図 のような 結果なります

5 手目の合法手のまとめ

下記 は、上記5 手目それぞれの合法手選択 した場合の 勝率まとめた表 です5。表から、選択 した 合法手 によって、敗率引き分け率変化 し、この場合は 合法手 14 の、(1, 0)(0, 2)選択 したほうが 通算成績良い ことが わかります

勝率 敗率 引分率
1 33 % 33 % 33 %
2 33 % 66 % 0 %
3 33 % 66 % 0 %
4 33 % 33 % 33 %

5 手目 のそれぞれの 合法手の違い は、その後相手の選択 によって、出現 する 局面の状況異なる 点にあります。例えば、合法手 14 は、相手選択次第 で、引き分け になる 可能性生じます が、合法手 23 では そのような可能性生じません

人間からみた 5 手目の合法手の違い

上図の局面 は、ai12s ver 2 から見ると 必敗の局面 なので、どの合法手選択 しても 結果変わらない判断 して すべての合法手 の中から ランダム選択 します。しかし、〇×ゲーム にある程度以上 親しんだ人 であれば、上記必敗の局面 でも あきらめず に、相手勝利 できる 合法手を減らす ことができる、(1, 0)(0, 2)選択する のではないかと思います。また、この局面ai12s ver 2 が例えば (2, 0)着手行った場合 に、人間から見る と、AIゲームを投げ出したよう感じられる人 が多いかもしれません。

上記局面 で、人間から みて 最善を尽くす ように 見える(1, 0)(0, 2)AI選択する ようにするための 方法 について少し考えてみて下さい。

最善手の中の優劣の計算方法

評価値利用 する アルゴリズム で、必敗の局面 での、最善手優劣をつける 方法は、もちろん、それぞれ合法手評価値異なる値にする ことです。そのため には、必敗の局面合法手評価値どのような計算異なる値にするか考える必要 があるので、少し考えてみて下さい。

ルール 13 の定義

現状ai12s ver 2 は、「自 0 敵 2 空 1」の マークのパターン1 つ以上 存在する 局面 に対して 必敗の局面 であると 判断 し、-100 という 評価値計算 していますが、「自 0 敵 2 空 1」の マークのパターン多い程相手勝ちやすい考えることできます。そこで、「自 0 敵 2 空 1」の マークのパターン に対して、その数大きい ほうが 評価値小さくなる ように 評価値計算 するという 方法考えられます

そのような方法評価値計算 することで、「自 0 敵 2 空 1」の マークのパターン が多ければ 多い程評価値低くなる ため、選択されづらく なります。

下記は、上記ルール 12 改加えたルール 13定義 です。具体的には、優先順位2条件 に『ただし、「自 0 敵 2 空 1」の 少ない着手優先 する』を 加えました

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

評価値の設定

次に、ルール 13 の、相手勝利できる場合評価値計算する方法考える必要 があります。その方法としては、下記 のような 方法考えられます

  • 自 0 敵 2 空 1」の マークのパターン を $z$ とした場合で、$z$ が 1 以上 の場合に 評価値 を $-100 × z$ という 計算 する

にも、$-100 - 10 × z$ など、さまざまな式考えられます本記事採用する式よりふさわしい と思われる 式があればそちらを採用 しても 構いません

なお、上記 では $-100 × z$ という 評価値を計算 しましたが、$z$ に 乗算 する パラメータ には、下記 のような 条件 があります。

  • 負の値 である
  • 計算 された 評価値範囲 が、優先順位評価値範囲重ならない

下記は、そのこと確認 するために、それぞれ優先順位 に対する 評価値範囲まとめた表 です。 から、それぞれ優先順位計算 される 評価値範囲重複していない ことが 確認 できます。

順位 局面の状況 個別 評価値
1 自分が勝利している 300
3 「自 2 敵 0 空 1」が 2 つ以上存在する 200
4 片方斜め方向〇×〇並び
いずれか1 つのマスのみ
× が配置 されている
100
5 「自 2 敵 0 空 1」が 1 つ存在する
「自 1 敵 0 空 2」が x 個存在する
「自 0 敵 1 空 2」が y 個存在する
2 (0~2)
0.5 * x (0~4)
-y (-8~0)
-8~6
2 「自 0 敵 2 空 1」が z 個存在する
ただし、z > 0 とする
-100 * z
(-800 ~ -100)
-800 ~ -100

ai13s の定義

ai12s上記の表方法評価値を計算 するように、修正 すると、これまでai12s ver 23処理行えなくなってしまう ので、新しく ai13s という 関数下記 のように 定義 する事にします。また、上記 の $z$ に 乗算 する パラメータ実引数変更できる ように、ai12s にあった 仮引数 score_defeatそのまま利用 することにします。

  • 10 行目:「自 0 敵 2 空 1」の マークのパターン存在する場合評価値 を、上記計算式計算する ように 修正 する
 1  from ai import ai_by_score
 2  from marubatsu import Markpat
 3  from pprint import pprint
 4
 5  def ai13s(mb, score_victory=300, score_sure_victory=200, score_defeat=-100,
 6            score_special=100, score_201=2, score_102=0.5, score_012=-1, debug=False):  
 7      def eval_func(mb):         
同じなので略
 8          # 相手が勝利できる場合
 9          if markpats[Markpat(last_turn=0, turn=2, empty=1)] > 0:
10              return score_defeat * markpats[Markpat(last_turn=0, turn=2, empty=1)]
同じなので略
行番号のないプログラム
from ai import ai_by_score
from marubatsu import Markpat
from pprint import pprint

def ai13s(mb, score_victory=300, score_sure_victory=200, score_defeat=-100,
          score_special=100, score_201=2, score_102=0.5, score_012=-1, debug=False):    
    def eval_func(mb):         
        # 自分が勝利している場合
        if mb.status == mb.last_turn:
            return score_victory

        markpats = mb.count_markpats()
        if debug:
            pprint(markpats)
        # 相手が勝利できる場合
        if markpats[Markpat(last_turn=0, turn=2, empty=1)] > 0:
            return score_defeat * markpats[Markpat(last_turn=0, turn=2, empty=1)]
        # 次の自分の手番で自分が必ず勝利できる場合
        elif markpats[Markpat(last_turn=2, turn=0, empty=1)] >= 2:
            return score_sure_victory
        
        # 斜め方向に 〇×〇 が並び、いずれかの辺の 1 つのマスのみに × が配置されている場合
        if mb.board[1][1] == Marubatsu.CROSS and \
           (mb.board[0][0] == mb.board[2][2] == Marubatsu.CIRCLE or \
            mb.board[2][0] == mb.board[0][2] == Marubatsu.CIRCLE) and \
           (mb.board[1][0] == Marubatsu.CROSS or \
            mb.board[0][1] == Marubatsu.CROSS or \
            mb.board[2][1] == Marubatsu.CROSS or \
            mb.board[1][2] == Marubatsu.CROSS) and \
           mb.move_count == 4:
            return score_special    

        # 評価値の合計を計算する変数を 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)
修正箇所
from ai import ai_by_score
from marubatsu import Markpat
from pprint import pprint

-def ai13s(mb, score_victory=300, score_sure_victory=200, score_defeat=-100,
-          score_special=100, score_201=2, score_102=0.5, score_012=-1, debug=False):  
+def ai14s(mb, score_victory=300, score_sure_victory=200, score_defeat=-100,
+          score_special=100, score_201=2, score_102=0.5, score_012=-1, debug=False):  
    def eval_func(mb):         
同じなので略
        # 相手が勝利できる場合
        if markpats[Markpat(last_turn=0, turn=2, empty=1)] > 0:
-           return score_defeat
+           return score_defeat * markpats[Markpat(last_turn=0, turn=2, empty=1)]
同じなので略

ai13s が 5 手目で選択する合法手の確認

上図局面 で、ai13s選択 する 合法手 を、下記 のプログラムで 確認します

ai13s(mb, debug=True)
実行結果(長いのでクリックして開いてください)
Start ai_by_score
Turn x
x..
.ox
.Oo

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

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

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

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

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

下記は、先程の表 に、それぞれ合法手 の「自 0 敵 2 他 1」の マークのパターン と、上記実行結果 に表示された 評価値加えた ものです。

勝率 敗率 引分率 「021」 評価値
1 33 % 33 % 33 % 1 -100
2 33 % 66 % 0 % 2 -200
3 33 % 66 % 0 % 2 -200
4 33 % 33 % 33 % 1 -100

から、合法手 14(1, 0)(0, 2)評価値最も高い -100 になり、この 2 つ合法手のみ選択される ようになったことが 確認 できました。また、そのことは、下記の 実行結果最後の 2 行 からも 確認 できます。

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

上記から、ai13s が、ai12s ver 2 より も、上記の局面 で、相手によって成績良くなる可能性高くなる ような 合法手選択するようになった ことが 確認 できました。

ai2 との対戦

下記 のプログラムで、ランダムな AI である ai2 と対戦 してみることにします。

from ai import ai_match, ai2

ai_match(ai=[ai13s, ai2])

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

ai13s VS ai2
count     win    lose    draw
o        9888       0     112
x        8850       0    1150
total   18738       0    1262

ratio     win    lose    draw
o       98.9%    0.0%    1.1%
x       88.5%    0.0%   11.5%
total   93.7%    0.0%    6.3%

実行結果 から、敗率0 % であることが 確認 できたので、ai13s弱解決の AI である 可能性が高い ことが わかりました

下記 は、上記実行結果 に、ai12s ver 2 VS ai2対戦結果加えた ものです。実行結果 から、通算成績 はどちらも ほぼ同じ であることが分かりました。本当は ai13s VS ai2 のほう成績良くなる ことを 期待していた のですが、そうはなりません でした。成績変わらない原因 について少し考えてみて下さい。

関数名 o 勝 o 負 o 分 x 勝 x 負 x 分
ai12s ver 2 98.9 0.0 1.1 88.7 0.0 11.3 93.8 0.0 6.2
ai13s 98.9 0.0 1.1 88.5 0.0 11.5 93.7 0.0 6.3

ai12s ver 2 VS ai2ai13s VS ai2 の違いの検証

先程上記の局面 で、ai12s ver 2ai13s選択 する 合法手異なる ことを 確認 しました。それにもかかわらず、上記の表のように 成績変わらない ということは、ai12s ver 2 VS ai2ai13s VS ai2対戦 で、上記のような、ai12s ver 2ai13s異なる合法手選択する局面出現しない ことが 原因 である 可能性あります

そこで、ai12s ver 2 VS ai2ai13s VS ai2結果差が生じる ような 試合存在するか どうかを、前回の記事同様の方法 で、下記 のプログラムで 検証する ことにします。ただし、前回の記事プログラム では 結果差が生じるまで無限ループ処理行いました が、差が生じる ような 局面存在しない 場合は プログラム終了しない ので、1 万回繰り返し ても 見つからない 場合は、処理を終了する ように 修正 しました。

  • 7 行目無限ループ を、10000 回繰り返す ように 修正 する
  • 9、12 行目ai12 ver 3 VS ai2ai13 VS ai2修正 する
 1  import random
 2  from marubatsu import Marubatsu
 3  from ai import ai2, ai12s
 4
 5  seed = 0
 6  mb = Marubatsu()
 7  while seed < 10000:
 8      winner1 = mb.play(ai=[ai12s, ai2], seed=seed, verbose=False)
 9      winner2 = mb.play(ai=[ai13s, ai2], seed=seed, verbose=False)
10      if winner1 != winner2:
11          mb.play(ai=[ai12s, ai2], seed=seed)
12          mb.play(ai=[ai13s, ai2], seed=seed)
13          break
14      seed += 1
行番号のないプログラム
import random
from marubatsu import Marubatsu
from ai import ai2, ai12s

seed = 0
mb = Marubatsu()
while seed < 10000:
    winner1 = mb.play(ai=[ai12s, ai2], seed=seed, verbose=False)
    winner2 = mb.play(ai=[ai13s, ai2], seed=seed, verbose=False)
    if winner1 != winner2:
        mb.play(ai=[ai12s, ai2], seed=seed)
        mb.play(ai=[ai13s, ai2], seed=seed)
        break
    seed += 1
修正箇所
import random
from marubatsu import Marubatsu
from ai import ai2, ai12s

seed = 0
mb = Marubatsu()
-while True:
+while seed < 10000:
    winner1 = mb.play(ai=[ai12s, ai2], seed=seed, verbose=False)
-   winner2 = mb.play(ai=[ai12s, ai2], params=[{"score_victory": 200}, {}], \
-                     seed=seed, verbose=False)
+   winner2 = mb.play(ai=[ai13s, ai2], seed=seed, verbose=False)
    if winner1 != winner2:
        mb.play(ai=[ai12s, ai2], seed=seed)
-       mb.play(ai=[ai12s, ai2], params=[{"score_victory": 200}, {}], seed=seed)
+       mb.play(ai=[ai13s, ai2], seed=seed)
        break
    seed += 1

実行結果

実行結果 には 何も表示されない ので、想像した通りに、差が生じる ような 局面出現しない ことが わかりました

なお、ゲーム開始時局面 から ai13s VS ai2対戦 を行った場合は、確かに 差が生じる ような 局面出現しません が、例えば 最初人間が着手 を行い、途中から ai13s着手を行う ような場合は、差が生じる局面下図 のように 実際に出現 します。従って、ai13s作成したこと が、無意味 であるというわけでは ありません

他の優先順位の条件を考慮に入れた、必敗の局面での最善手の評価値の計算

ルール 13 では、必敗の局面 での 評価値計算する際 に、「自 0 敵 2 空 1」の マークのパターン数のみ考慮 入れた 評価値計算 しました。

必敗の局面合法手優劣を決める 際に、必敗の局面以外 の場合で 有効であった他の優先順位ヒューリスティックな条件評価値有効 である 可能性あります

ルール 14 の定義

そこで、必敗の局面評価値ヒューリスティックな条件評価値組み込む という ルール 14 を下記のように 定義 することにします。具体的には、ルール 13優先順位2条件 に『また、優先順位5 の条件考慮 する』を 加えました

順位 条件 種類
1 勝てる場合勝つ 十分条件
2 相手勝利できる 着手を 行わない
ただし、「自 0 敵 2 空 1」の 少ない着手優先 する
また、優先順位5 の条件考慮 する
必要条件
3 自 2 敵 0 空 1」が 2 つ以上存在 する着手を行う 十分条件
4 斜め方向〇×〇並び他の 6 マス空のマス
場合は、いずれか辺のマス に着手を行う
十分条件
5 自 2 敵 0 空 1」が 1 つ存在 する着手を行う
自 1 敵 0 空 2」が 最も多い 着手を行う
自 0 敵 1 空 2」が 最も少ない 着手を行う
6 ランダム なマスに 着手 する

評価値の設定

次に、ルール 14 の、優先順位が 2条件評価値計算する方法考える必要 があります。その方法として、下記 のような 方法考えられます

  • ルール 13優先順位が 5条件評価値 に、優先順位が 5ヒューリスティックな条件評価値加算 する

その際に、先程と同様 に、計算 された 評価値範囲 が、優先順位条件評価値範囲重ならない ようにする 必要 があります。

下記は、そのこと確認 するために、それぞれ優先順位条件 に対する 評価値範囲まとめた表 です。 から、それぞれ優先順位計算 される 評価値範囲重複していない ことが 確認 できます。

順位 局面の状況 個別 評価値
1 自分が勝利している 300
3 「自 2 敵 0 空 1」が 2 つ以上存在する 200
4 片方斜め方向〇×〇並び
いずれか1 つのマスのみ
× が配置 されている
100
5 「自 2 敵 0 空 1」が 1 つ存在する
「自 1 敵 0 空 2」が x 個存在する
「自 0 敵 1 空 2」が y 個存在する
2 (0~2)
0.5 * x (0~4)
-y (-8~0)
-8~6
2 「自 0 敵 2 空 1」が z 個存在する
ただし、z > 0 とし、優先順位5
評価値加算 する
-100 * z
(-800 ~ -100)
+-8~6
-808 ~ -94

評価値を加算する際の注意点

上記では、優先順位2評価値範囲 が、その上の 優先順位5評価値範囲重ならない ことを 確認 しましたが、それだけ では 意図通り評価値計算されない 場合が ある 点に 注意必要 です。

例えば、ai13s では、「自 0 敵 2 空 1」の マークのパターン少ない合法手優先的選択 するようにするために、「自 0 敵 2 空 1」の マークのパターン1 つあたり評価値-100設定 しました。

ルール 14評価値設定 する に、この『「自 0 敵 2 空 1」の マークのパターン少ない合法手優先的選択する』という 条件 が、ヒューリスティックな条件 よりも 重要である考える のであれば、ヒューリスティックな条件 による 評価値加算 しても、「自 0 敵 2 空 1」の マークのパターン少ない場合評価値大きくなるよう に、評価値設定 する 必要あります。そのことを 確認したほう良い でしょう。

下記 は、「自 0 敵 2 空 1」の マークのパターン1 つあたり評価値-100 とした場合に、「自 0 敵 2 空 1」の マークのパターン1 ~ 3場合 のそれぞれの 評価値範囲表す表 です。表から、この場合は、「自 0 敵 2 空 1」の マークのパターンそれぞれの数 に対する 評価値の範囲 が、重ならない ので、 マークのパターン少ない場合評価値必ず大きくなる ことが 確認 できます。

そこで、本記事では、「自 0 敵 2 空 1」の マークのパターン1 つあたり評価値ai13s と同様 に、-100設定 することにします。

局面の状況 個別 評価値
「自 2 敵 0 空 1」が 1 つ存在する
「自 1 敵 0 空 2」が x 個存在する
「自 0 敵 1 空 2」が y 個存在する
2 (0~2)
0.5 * x (0~4)
-y (-8~0)
-8~6
「自 0 敵 2 空 1」が 1 個存在する -100
+-8~6
-108 ~ -94
「自 0 敵 2 空 1」が 2 個存在する -200
+-8~6
-208 ~ -194
「自 0 敵 2 空 1」が 3 個存在する -300
+-8~6
-308 ~ -294

下記は、「自 0 敵 2 空 1」の 1 つあたり評価値-10設定 した場合の です。表から、この場合は、「自 0 敵 2 空 1」の それぞれマークのパターン に対する 評価値範囲 が、他の数 に対する 評価値範囲一部重なっている(例えば -18 ~ -14重なっている)ことが わかる ので、目的達成できません

局面の状況 個別 評価値
「自 2 敵 0 空 1」が 1 つ存在する
「自 1 敵 0 空 2」が x 個存在する
「自 0 敵 1 空 2」が y 個存在する
2 (0~2)
0.5 * x (0~4)
-y (-8~0)
-8~6
「自 0 敵 2 空 1」が 1 個存在する -10
+-8~6
-18 ~ -4
「自 0 敵 2 空 1」が 2 個存在する -20
+-8~6
-28 ~ -14
「自 0 敵 2 空 1」が 3 個存在する -30
+-8~6
-38 ~ -24

このように、異なる優先順位評価値合計 する場合は、上記のような 表を作成 して、意図通り評価値が設定 されているかどうかを 確認 することが 重要 です。

逆に、「自 0 敵 2 空 1」の マークのパターン数より も、ヒューリスティックな条件 のほうが 重要である考える のであれば、上記設定問題ありません

ai14s の定義

下記は、上記評価値計算 を行う ai14s定義 するプログラムです。

  • 5、10 行目評価値合計計算 する変数 score初期化処理 を、eval_func最初で行う ように 修正 する
  • 8 行目相手が勝利 できる 場合 に、これまで は 計算した 評価値return 文返していた のを、score加算する ように 修正 する。そのようにすることで、score12 行目以降ヒューリスティックな条件に よって 計算 された 評価値加算される
 1  def ai14s(mb, score_victory=300, score_sure_victory=200, score_defeat=-100,
 2            score_special=100, score_201=2, score_102=0.5, score_012=-1, debug=False):    
 3      def eval_func(mb):         
 4          # 評価値の合計を計算する変数を 0 で初期化する
 5          score = 0        
同じなので略
 6          # 相手が勝利できる場合は評価値を加算する
 7          if markpats[Markpat(last_turn=0, turn=2, empty=1)] > 0:
 8              score = score_defeat * markpats[Markpat(last_turn=0, turn=2, empty=1)]
同じなので略
 9          # この下にあった評価値の合計を計算する変数を 0 で初期化する処理を削除する
10
11          # 次の自分の手番で自分が勝利できる場合は評価値に score_201 を加算する
12          if markpats[Markpat(last_turn=2, turn=0, empty=1)] == 1:
同じなので略
行番号のないプログラム
def ai14s(mb, score_victory=300, score_sure_victory=200, score_defeat=-100,
          score_special=100, score_201=2, score_102=0.5, score_012=-1, debug=False):    
    def eval_func(mb):         
        # 評価値の合計を計算する変数を 0 で初期化する
        score = 0        

        # 自分が勝利している場合
        if mb.status == mb.last_turn:
            return score_victory

        markpats = mb.count_markpats()
        if debug:
            pprint(markpats)
        # 相手が勝利できる場合は評価値を加算する
        if markpats[Markpat(last_turn=0, turn=2, empty=1)] > 0:
            score = score_defeat * markpats[Markpat(last_turn=0, turn=2, empty=1)]
        # 次の自分の手番で自分が必ず勝利できる場合
        elif markpats[Markpat(last_turn=2, turn=0, empty=1)] >= 2:
            return score_sure_victory
        
        # 斜め方向に 〇×〇 が並び、いずれかの辺の 1 つのマスのみに × が配置されている場合
        if mb.board[1][1] == Marubatsu.CROSS and \
           (mb.board[0][0] == mb.board[2][2] == Marubatsu.CIRCLE or \
            mb.board[2][0] == mb.board[0][2] == Marubatsu.CIRCLE) and \
           (mb.board[1][0] == Marubatsu.CROSS or \
            mb.board[0][1] == Marubatsu.CROSS or \
            mb.board[2][1] == Marubatsu.CROSS or \
            mb.board[1][2] == Marubatsu.CROSS) and \
           mb.move_count == 4:
            return score_special    

        # 次の自分の手番で自分が勝利できる場合は評価値に 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)
修正箇所
-def ai13s(mb, score_victory=300, score_sure_victory=200, score_defeat=-100,
-          score_special=100, score_201=2, score_102=0.5, score_012=-1, debug=False):    
+def ai14s(mb, score_victory=300, score_sure_victory=200, score_defeat=-100,
+          score_special=100, score_201=2, score_102=0.5, score_012=-1, debug=False):    
    def eval_func(mb):         
+       # 評価値の合計を計算する変数を 0 で初期化する
+       score = 0        
同じなので略
        # 相手が勝利できる場合は評価値を加算する
        if markpats[Markpat(last_turn=0, turn=2, empty=1)] > 0:
-           return score_defeat * markpats[Markpat(last_turn=0, turn=2, empty=1)]
+           score += score_defeat * markpats[Markpat(last_turn=0, turn=2, empty=1)]
同じなので略
        # 評価値の合計を計算する変数を 0 で初期化する
-       score = 0        
        # 次の自分の手番で自分が勝利できる場合は評価値に score_201 を加算する
        if markpats[Markpat(last_turn=2, turn=0, empty=1)] == 1:
同じなので略

ai14s が 5 手目で選択する合法手の確認

上図局面 で、ai14s選択 する 合法手 を、下記 のプログラムで 確認 します。

ai14s(mb, debug=True)
実行結果(長いのでクリックして開いてください)
Start ai_by_score
Turn x
x..
.ox
.Oo

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

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

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

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

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

下記は、先程の表 から 勝率など削除 し、評価値計算利用 する マークのパターン と、上記実行結果 に表示された 評価値加えた ものです。

「201」 「021」 「102」 「012」 評価値
評価値 1:+2
2~:200
1 つで
-100
1 つで
+0.5
1 つで
-1
1 1 1 1 1 -98.5
2 1 2 1 -197.5
3 1 2 1 1 -198.5
4 1 1 1 -97.5

表から、合法手 4(0, 2)評価値最も高い -97.5 になり、この合法手のみ選択 されるようになったことが 確認 できました。また、そのことは、下記の 実行結果最後の 2 行 からも 確認 できます。

best score -97.5
best moves [(0, 2)]

なお、ai13sai14s は、上記のように 同じ局面異なる合法手選択する ようになりましたが、どちら強い AI であるか を、ランダムな AI対戦 して 判定 することは できませんその理由 は、先程説明したように、この局面 は、ランダムな AI対戦 した際に 生じない局面 だからです。ai13sai14sどちら最強の AI に近い かどうかについては、探索型の AI作成する際確認する ことにします。

ai2 との対戦

下記 のプログラムで、ランダムな AI である ai2 と対戦 してみることにします。

ai_match(ai=[ai14s, ai2])

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

ai14s VS ai2
count     win    lose    draw
o        9900       0     100
x        8885       0    1115
total   18785       0    1215

ratio     win    lose    draw
o       99.0%    0.0%    1.0%
x       88.8%    0.0%   11.2%
total   93.9%    0.0%    6.1%

実行結果 から、敗率0 % であることが 確認 できたので、ai14s弱解決の AI である 可能性が高い ことが わかりました

下記は、上記実行結果 に、ai12s ver 2 VS ai2ai13s VS ai2対戦結果加えた ものです。実行結果 から、通算成績いずれもほぼ同じ であることが わかりました

なお、通算成績変わらない理由 は、ai13sai14s違い が、ai13s VS ai2ai14s VS ai2出現しない局面 に対する 評価値計算 だからです。

関数名 o 勝 o 負 o 分 x 勝 x 負 x 分
ai12s ver 2 98.9 0.0 1.1 88.7 0.0 11.3 93.8 0.0 6.2
ai13s 98.9 0.0 1.1 88.5 0.0 11.5 93.7 0.0 6.3
ai14s 99.0 0.0 1.0 88.8 0.0 11.2 93.9 0.0 6.1

今回の記事のまとめ

今回の記事では、最初に ai12s ver 3バグの原因検証 しました。このように、ルールベースの AI では、正しい思っていた条件正しくなかった ということが あります

次に、必勝の局面 と、必敗の局面 での、最善手の優劣 について 説明 しました。いずれの場合 も、相手最強の AI場合最善手優劣ありません が、最強ではない AI人間対戦 する場合を 考慮に入れる と、最善手 の間に 優劣が生じる 場合が あります

特に 人間と対戦 を行うことを 想定する 場合は、必勝の局面 の場合では 人間の心理考慮に入れた優劣評価値の計算組み込んだほうが良い でしょう。

また、必敗の局面 でも、評価値計算工夫 することによって、相手最強の AI でない 場合に、より良い結果 になる 確率が高い 最善手を 選択できる ようになることを説明しました。ただし、その際に、計算される 評価値の範囲注意 する 必要がある ことを 忘れないで下さい

なお、今回の記事では説明を省略しましたが、引き分けの局面最善手 にも、同様の意味 での 優劣存在します

ルールベースの AI について、〇×ゲーム思いつく範囲説明ほぼ行った のではないかと思いますので、ルールベースの AI に関する 説明今回の記事終わり にします。ただし、ルールベースの AI に関する 重要な内容思いついた場合 は、適宜説明 します。

本記事では、マークのパターン利用 した ルール条件 を作成してきましたが、局面の状況うまく表す他の性質 を考え、その性質を使って 条件考える ことも できます。興味や時間がある方は考えてみると良いでしょう。

ここまでの記事を読んで、ルールベースAI を作成 することが、非常に大変 であることが 実感できた のではないかと 思います。また、〇×ゲーム は、単純なゲーム なので、ルールベースの AI でも、弱解決の AI作成 することが できました が、〇×ゲームより複雑なゲームほとんど は、ルールベースの AI弱解決の AI作成 することは 非常に困難 か、ほぼ不可能 です。そのため、これまでに何度か言及してきましたが、最近 では ルールベースゲームの AI作成 することは 主流 では なくなっています

それにもかかわらず、本記事最初 に、主流ではないルールベースAI を作成 してきた 主な理由 は、以下の通り です。

  • ルールベース の AI考え方 が、人間問題解決 するための 考え方似ている
  • は、ルールベース の AI主流 であった
  • 〇×ゲーム であれば、ルールベース弱解決の AI作成できる
  • ルール少しづつ加えていく ことで、AI作り上げていく ことが できる
  • 初心者プログラミング基礎学ぶため重要な題材豊富 である

次回からは、〇×ゲームの AI に関する別の内容を説明したいと思います。

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

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

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

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

次回の記事

  1. 相手勝利できる 着手を 行わない」という 条件の記述省略 しています

  2. この局面 では、ai12s ver 2 は、最善手を選択しない場合があるという 問題発生しません

  3. もちろん、何も思わない人もいるでしょう

  4. 将棋のプロ棋士どうしの対局では、勝敗明らか になった 局面 で、敗北悟った側投了する(負けを認める)するのが 一般的 です

  5. それぞれの 勝率小数点以下 1 桁四捨五入 しているので、合計100 %なりません

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1