0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2024-02-26

目次と前回の記事

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

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

これまでに作成した 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」が 最も少ない 着手を行う
そうでない場合は ランダム なマスに 着手 する

基準となる 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 98.1 0.0 1.9 82.5 1.9 15.6 90.3 1.0 8.7 あり

前回の記事のおさらい

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

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

前回の記事では、4 手目ai11s が上図の (0, 2) の着手を 選択 した場合の 検証行っていない ので、最初に その検証行います。なお、実際 には 4 手目 で、(2, 0)着手 する 場合 がありますが、その場合局面(0, 2) に着手した場合の局面と 同一局面 なので、(0, 2)着手 した場合の 検証まとめて説明 することにします。

5 手目の検証

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

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

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

  • 4 ~ 8 行目:上図の 4 手目まで着手行う
1  from marubatsu import Marubatsu
2  from ai import ai_match, ai10s, ai11s
3
4  mb = Marubatsu()
5  mb.move(1, 1)
6  mb.move(0, 0)
7  mb.move(2, 2)
8  mb.move(0, 2)
9  ai10s(mb, debug=True)
行番号のないプログラム
from marubatsu import Marubatsu
from ai import ai_match, ai10s, ai11s

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

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

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

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

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

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

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

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

「201」 「021」 「102」 評価値
評価値 1:+1
2~:100
-100 1 つで
+1
1 1 1 2 -100
2 1 1 2 -100
3 1 2 3
4 2 1 1 -100
5 1 1 2 -100

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

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

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

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

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

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

6 手目の検証

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

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

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

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

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

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

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

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

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

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

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

7 手目の検証

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

mb.move(2, 1)
ai10s(mb, debug=True)
実行結果(長いのでクリックして開いてください)
Start ai_by_score
Turn o
x..
ooX
x.o

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

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

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

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

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

「201」 「021」 「102」 評価値
評価値 1:+1
2~:100
-100 1 つで
+1
1 1 1
2 1 1
3 1 1

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

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

8 手目の検証

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

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

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

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

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

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

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

8 手目の検証のまとめ

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

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

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

  • 4 手目(0, 2)着手 が行われた場合は、ai10s VS ai11s7 手目まで必ず同一局面 になる着手を行う
  • 7 手目選択 される 着手3 種類 あるが、どの着手 が行われても 引き分け になる

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

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

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

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

  • 4 手目ai11s50 %確率(1, 2)着手 すると、ai11s が負ける
  • 4 手目ai11s50 %確率(0, 2)着手 すると、引き分け になる

このことは、前回の記事で実行した、下記のプログラムによる ai11s VS ai10s対戦結果 で、ai11s× を担当 した場合の 敗率 約 50 %引き分け 約 50%一致 します。従って、これまでに行ってきた ai10s VS ai11s行われる着手検証結果ほぼ間違いがなさそう であることが 確認 できました。

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

ai_match(ai=[ai11s, ai10s])

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

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

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

ai11s の敗因の検証と改善

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

  • ai10sai11s が、ルール通り処理 を行っている 可能性が高い こと
  • ai10s VS ai11sai11s50 %確率負ける理由

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

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

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

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

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

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

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

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

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

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

評価値の計算方法の修正

下記は、前回の記事4 手目検証 を行った際に 作成した表 です。

「201」 「021」 「102」 「012」 評価値
評価値 1:+1
2~:100
-100 1 つで
+1
1 つで
-1
1 1 1 4 -2
2 1 1 3 -1
3 2 3 -1

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

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

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

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

この表の 評価値数字 は、以前の記事試行錯誤 して決めたものであったり、とりあえずこの値に設定する という方法で決めたものであるため、それぞれの値AI を強くする ために ベストな数字であるか どうかは わかっていません。実際に、以前の記事で、『「自 2 敵 0 空 1」が 1 つ の場合』の 評価値変えて ai9s対戦 した 結果対戦成績変化 したことからわかるように、これらの値変える ことで、AI の強さ変わります

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

パラメータparameter)は、日本語 では 変数訳されプログラム用語 での 任意の値代入 できる a のような 変数パラメータの一種 です。ただし、プログラム用語変数 は、一般的英語 では variable表記 し、parameter仮引数 という意味で、変数区別 して 使われます1

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

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

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

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

  • 現状:合法手 2 の評価値 = 合法手 3 の評価値 > 合法手 1 の評価値
  • 目標:合法手 2 の評価値 > 合法手 1 の評価値、合法手 2 の評価値 > 合法手 3 の評価値

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • 現状:音量 = 10
  • 目標:音量 >= 20、音量 < 30

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • 国語 の点数が 50 点以上
  • 英語 の点数が 50 点以上
  • 数学 の点数が 50 点以上

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

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

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

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

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

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

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

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

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

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

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

この 2 つ修正方法 に関して、合法手 12関係 は以下のようになっています。

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

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

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

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

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

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

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

  • 合法手 2 の評価値 > 合法手 1 の評価値
  • 合法手 2 の評価値 > 合法手 3 の評価値
「201」 「021」 「102」 「012」 評価値
評価値 1:+1
2~:100
-100 1 つで
+1
1 つで
-1
1 1 1 → 3 4 -2 → 0
2 1 1 3 -1
3 2 3 -1

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

「201」 「021」 「102」 「012」 評価値
評価値 1:+10000
2~:100
-100 1 つで
+1
1 つで
-1
1 1 1 4 9997
2 1 1 3 9998
3 2 3 -1

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

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

問題発生しない ことを 確認 するためには、以前の記事で作成した、優先順位 ごとの 評価値作成すると良い でしょう。下記は、『「自 2 敵 0 空 1」が 1 つ の場合』の 評価値1 から 2修正 した場合の で、問題発生しない ことが 確認 できます。

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

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

「201」 「021」 「102」 「012」 評価値
評価値 1:+2
2~:100
-100 1 つで
+1
1 つで
-1
1 1 1 4 -1
2 1 1 3 0
3 2 3 -1

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

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

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

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

目的を達成できない場合

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

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

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

先程、問題がない ことを 確認 した、『「自 2 敵 0 空 1」が 1 つ の場合』の 評価値1 から 2修正 するという方法で ai11s修正 することにします。そのように パラメータ修正する ためには、以前の記事評価値の調整 を行った際と 同様 に、ai11s の下記のプログラムの 3 行目score += 1 で、評価値加算する数値1 から 2変更すればよい のですが、今後、このように パラメータ変更する必要 が生じた場合に、ai11s のプログラムを 毎回修正する のは 大変 です。

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

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

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

仮引数の名前
自 2 敵 0 空 1」が 1 つ の場合 score_201
自 1 敵 0 空 2」が 1 つあたり score_102
自 0 敵 1 空 2」が 1 つあたり score_012

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

ai11s の修正

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

  • 5 行目パラメータ代入 する 仮引数追加 する
  • 11 行目:「自 2 敵 0 空 1」が 1 つ の場合に、評価値score_201加算する ように 修正 する
  • 13 行目評価値 に『「自 1 敵 0 空 2」の数 × score_102』を 加算 するように 修正
  • 15 行目評価値 に『「自 0 敵 1 空 2」の数 × score_012』を 加算 するように 修正。なお、score -= 1 であったが、score_102 には 負の値代入 することを 想定している ので、修正後score += 式変更 する 必要 がある点に 注意 すること
 1  from pprint import pprint
 2  from ai import ai_by_score
 3  from marubatsu import Markpat
 4
 5  def ai11s(mb, score_201, score_102, score_012, debug=False):
 6      def eval_func(mb):      
元と同じなので省略
 7          # 評価値の合計を計算する変数を 0 で初期化する
 8          score = 0        
 9          # 次の自分の手番で自分が勝利できる場合は評価値に score_201 を加算する
10          if markpats[Markpat(last_turn=2, turn=0, empty=1)] == 1:
11              score += score_201
12          # 「自 1 敵 0 空 2」1 つあたり score_102 だけ、評価値を加算する
13          score += markpats[Markpat(last_turn=1, turn=0, empty=2)] * score_102
14          # 「自 0 敵 1 空 2」1 つあたり score_201 だけ、評価値を減算する
15          score += markpats[Markpat(last_turn=0, turn=1, empty=2)] * score_012
16        
17          # 計算した評価値を返す
18          return score
19
20      return ai_by_score(mb, eval_func, debug=debug)
行番号のないプログラム
from pprint import pprint
from ai import ai_by_score
from marubatsu import Markpat

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

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

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

    return ai_by_score(mb, eval_func, debug=debug)
修正箇所
from pprint import pprint
from ai import ai_by_score
from marubatsu import Markpat

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

    return ai_by_score(mb, eval_func, debug=debug)

動作の確認

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

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

mb = Marubatsu()
mb.move(1, 1)
mb.move(0, 0)
mb.move(2, 2)
ai11s(mb, score_201=1, score_102=1, score_012=-1, debug=True)
実行結果(長いのでクリックして開いてください)
Start ai_by_score
Turn x
x..
.o.
..O

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

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

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

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

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

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

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

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

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

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

  • 1 行目score_201=1score_201=2修正 する
ai11s(mb, score_201=2, score_102=1, score_012=-1, debug=True)
修正箇所
-ai11s(mb, score_201=1, score_102=1, score_012=-1, debug=True)
+ai11s(mb, score_201=2, score_102=1, score_012=-1, debug=True)
実行結果(長いのでクリックして開いてください)
Start ai_by_score
Turn x
x..
.o.
..O

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

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

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

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

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

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

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

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

「201」 「021」 「102」 「012」 評価値
評価値 1:+2
2~:100
-100 1 つで
+1
1 つで
-1
1 1 1 4 -1
2 1 1 3 0
3 2 3 -1

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

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

ai10s との対戦

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

ai_match の問題点

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

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

実行結果

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

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

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

  • AttrubuteError
    オブジェクトの属性(attribute)に関するエラー
  • 'tuple' object has no attribute 'name'
    tuple というオブジェクト(object)は、__name__ という属性(attribute)を持たない(has no)

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

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

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

ai_match への仮引数の追加

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

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

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

def ai_match(ai, params, match_num=10000):
修正箇所
-def ai_match(ai, match_num=10000):
+def ai_match(ai, params, match_num=10000):

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

次に、この 仮引数 params代入 する list の要素どのようなデータ記述 し、どのように して そのデータ を、AI の関数呼び出す際実引数に記述 するかを 説明 します。その 方法 は、初心者の方にはおそらく思いつくことはできないと思いますが、以前の記事で説明した マッピング型の展開 を使って 行う ことが できます

おそらく忘れてしまった方が多いのではないかと思いますので、マッピング型の展開おさらい します。マッピング型の展開 とは、dict などの マッピング型データキーキーの値 を、キーワード引数変換 したものと みなして関数呼び出しを行う という 処理 で、マッピング型先頭**記述 します。例えば、下記 のプログラムの 1 行目 のプログラムは、マッピング型の展開 を使って、2 ~ 7 行目 のように 記述できます

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

1  ai11s(mb, score_201=1, score_102=2, score_012=3)
2  param = { 
3      "score_201": 1,
4      "score_102": 2,
5      "score_012": 3
6  }
7  ai11s(mb, **param)
行番号のないプログラム
ai11s(mb, score_201=1, score_102=2, score_012=3)
param = { 
    "score_201": 1,
    "score_102": 2,
    "score_012": 3
}
ai11s(mb, **param)

従って、ai_matchparams代入 する list の要素 には、それぞれの AI関数呼び出す際実引数に記述 する キーワード引数を表す dict記述 し、AI の関数呼び出す際 に、マッピング型の展開記述すれば良い ことが 分かります

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

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

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


def play(self, ai, verbose=True):

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

play メソッドと ai_match の修正

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

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

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

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

次に、ai_match を、下記のプログラムの 4、5 行目 のように、play メソッドに params実引数追加 するように 修正 します。なお、プログラム5 行目 では、手番入れ替えて対戦を行う 際に、ai の要素順番入れ替える処理ai=[ai[0], a[1]] のように記述していましたが、以前の記事で説明したように、スライス表記利用する ことで、list要素の順番 にした list は、ai[::-1] のように 簡潔記述できる ので、今回の記事では その方法aiparams要素逆にした list記述 しています。

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

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

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

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

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

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

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

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

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

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

実行結果

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

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

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

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

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

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

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

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

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

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

評価値
自 2 敵 0 空 1」が 1 つ の場合 2
自 1 敵 0 空 2」が 1 つあたり 1 → 0.5
自 0 敵 1 空 2」が 1 つあたり -1

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

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

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

mb = Marubatsu()
mb.move(1, 1)
mb.move(0, 0)
mb.move(2, 2)
param = { 
    "score_201": 1,
    "score_102": 0.5,
    "score_012": -1,
}
ai11s(mb, **param)
実行結果(長いのでクリックして開いてください)
Start ai_by_score
Turn x
x..
.o.
..O

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

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

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

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

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

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

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

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

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

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

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

実行結果

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

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

今回の記事のまとめ

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

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

解決方法4 手目 で、(0, 2)(または (2, 0))のみ着手行われる ようにする

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

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

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

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

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

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

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

次回の記事

  1. 仮引数変数一種 なので、プログラム用語 での parameter も、厳密 には 変数表します

  2. tuple でもかまいません。また、tuple を記述 してもプログラムを 変更する必要ありません

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?