目次と前回の記事
これまでに作成したモジュール
以下のリンクから、これまでに作成したモジュールを見ることができます。
これまでに作成した AI
これまでに作成した AI の アルゴリズム は以下の通りです。
関数名 | アルゴリズム |
---|---|
ai1 |
左上から順に空いているマスを探し、最初に見つかったマスに着手する |
ai2 |
ランダムなマスに着手する |
ai_match
の改良 その 6(AI の視点からの結果の表示)
前回の記事から引き続き、ai_match
の 改良 を行います。
ai_match
で AI どうし の 対戦 を行う 目的 は、ある AI が、別の AI に対して どれくらいの強さ を持つかを 調べる ことにあります。しかし、現状 の ai_match
は、特定の AI からの視点で結果を表示するのではなく、〇 と × を 担当 した AI からの視点 で 結果を表示 しています。そのため、ai_match
の 表示 から、どちら の AI が 強いか を 知る ことは 困難 です。
ai_match
の 表示 を、例えば下記のように、ai[0]
からの 視点 で 勝ち、負け、引き分け の 回数と比率 を 表示 すれば、ai[0]
と ai[1]
の どちらが強い かが わかりやすく なります。
ai1 VS ai2
count
win 435 lose 110 draw 22
win 237 lose 303 draw 27
ratio
win 76.7% lose 19.4% draw 3.9%
win 41.8% lose 53.4% draw 4.8%
ai[0]
からの視点の通算成績の回数の計算
上記のような表示を行うためには、ai[0]
からの 視点 での 通算成績 の 回数 を 計算 する必要があります。そこで、下記のプログラムのように、count_list
を 計算 した 後で、count_list_ai0
という 変数 に、ai[0]
からの 視点 の 通算成績 を 計算 した結果を 代入 することにします。なお、このプログラムを 繰り返し を使って 記述 することは 可能 ですが、かえって わかりづらくなる ので、本記事では下記のように記述することにします。
-
3 ~ 7 行目:win、lose、draw のそれぞれの キー の 値 に、
ai[0]
VSai[1]
の、ai[0]
から見た通算成績 の 勝数、敗数、引き分け数 を 代入 する dict -
9 ~ 13 行目:win、lose、draw のそれぞれの キー の 値 に、
ai[1]
VSai[0]
の、ai[0]
から見た通算成績 の 勝数、敗数、引き分け数 を 代入 する dict
1 count_list_ai0 = [
2 # ai[0] VS ai[1] の場合の、ai[0] から見た通算成績
3 {
4 "win": count_list[0][Marubatsu.CIRCLE],
5 "lose": count_list[0][Marubatsu.CROSS],
6 "draw": count_list[0][Marubatsu.DRAW],
7 },
8 # ai[1] VS ai[0] の場合の、ai[0] から見た通算成績
9 {
10 "win": count_list[1][Marubatsu.CROSS],
11 "lose": count_list[1][Marubatsu.CIRCLE],
12 "draw": count_list[1][Marubatsu.DRAW],
13 },
14 ]
ratio の比率の計算の修正
ratio の比率の 計算 は、count_list_ai0
を 使って計算 する 必要 があるので、下記のプログラムのように、比率 を 計算 する 部分 の count_list
を count_list_ai0
に 修正 します。
for i in range(2):
for key in count_list_ai0[i]:
ratio_list[i][key] = count_list_ai0[i][key] / match_num
修正箇所
for i in range(2):
- for key in count_list[i]:
+ for key in count_list_ai0[i]:
- ratio_list[i][key] = count_list[i][key] / match_num
+ ratio_list[i][key] = count_list_ai0[i][key] / match_num
結果の表示の修正
次に、ai_match
を、先程示した、下記のように表示するように修正します。
ai1 VS ai2
count
win 435 lose 110 draw 22
win 237 lose 303 draw 27
ratio
win 76.7% lose 19.4% draw 3.9%
win 41.8% lose 53.4% draw 4.8%
通算成績 の 回数 を 代入 する 変数 が、count_list
から count_list_ai0
に 変化 したので、結果を表示 する部分のプログラムをそのように 修正 します。なお、ratio_list
は、count_list_ai0
を 使って計算 するように 修正済 なので、変更 する 必要 は ありません。
また、count_list_ai0
の キー が "win"
、"lose"
、"draw"
に変化したので、order_list
の値も、下記のプログラムのように 修正 する 必要 があります。
order_list = [ "win", "lose", "draw" ]
修正箇所
-order_list = [ Marubatsu.CIRCLE, Marubatsu.CROSS, Marubatsu.DRAW ]
+order_list = [ "win", "lose", "draw" ]
下記は、ai_match
の通算成績を表示する部分を修正したプログラムです。
-
2 行目:
order_list
を上記のように 修正 する -
6 行目:
count_list
をcount_list_ai0
に 修正 する
1 # 〇の勝利、×の勝利、引き分けの順番で表示することを表す list
2 order_list = [ "win", "lose", "draw" ]
3
4 # 通算成績の回数と比率の表示
5 width = max(len(str(match_num)), 6)
6 diff_list = [ ("count", count_list_ai0, f"{width}d"),
7 ("ratio", ratio_list, f"{width}.1%") ]
8 for title, data, format in diff_list:
9 print(title)
10 for i in range(2):
11 for key in order_list:
12 print(f" {key} {data[i][key]:{format}}", end="")
13 print()
14 print()
修正箇所
# 〇の勝利、×の勝利、引き分けの順番で表示することを表す list
-order_list = [Marubatsu.CIRCLE, Marubatsu.CROSS, Marubatsu.DRAW]
+order_list = ["win", "lose", "draw"]
# 通算成績の回数と比率の表示
width = max(len(str(match_num)), 6)
-diff_list = [ ("count", count_list, f"{width}d"),
+diff_list = [ ("count", count_list_ai0, f"{width}d"),
("ratio", ratio_list, f"{width}.1%") ]
for title, data, format in diff_list:
print(title)
for i in range(2):
for key in order_list:
print(f" {key} {data[i][key]:{format}}", end="")
print()
print()
ai_match
の修正
下記は、ai_match
に対して上記の修正を行ったプログラムです。
from marubatsu import Marubatsu
from collections import defaultdict
def ai_match(ai, 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, verbose=False)] += 1
count_list[1][mb.play(ai=[ai[1], ai[0]], 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],
},
]
# それぞれの比率を計算し、ratio_list に代入する
ratio_list = [ {}, {} ]
for i in range(2):
for key in count_list_ai0[i]:
ratio_list[i][key] = count_list_ai0[i][key] / match_num
# ai[0] の勝利、勝利、引き分けの順番で表示することを表す list
order_list = ["win", "lose", "draw"]
# 通算成績の回数と比率の表示
width = max(len(str(match_num)), 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)
for i in range(2):
for key in order_list:
print(f" {key} {data[i][key]:{format}}", end="")
print()
print()
修正箇所
from marubatsu import Marubatsu
from collections import defaultdict
def ai_match(ai, 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, verbose=False)] += 1
count_list[1][mb.play(ai=[ai[1], ai[0]], 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],
+ },
+ ]
# それぞれの比率を計算し、ratio_list に代入する
ratio_list = [ {}, {} ]
for i in range(2):
- for key in count_list[i]:
+ for key in count_list_ai0[i]:
- ratio_list[i][key] = count_list[i][key] / match_num
+ ratio_list[i][key] = count_list_ai0[i][key] / match_num
# ai[0] の勝利、勝利、引き分けの順番で表示することを表す list
- order_list = [Marubatsu.CIRCLE, Marubatsu.CROSS, Marubatsu.DRAW]
+ order_list = ["win", "lose", "draw"]
# 通算成績の回数と比率の表示
width = max(len(str(match_num)), 7)
- diff_list = [ ("count", count_list, f"{width}d"),
+ diff_list = [ ("count", count_list_ai0, f"{width}d"),
("ratio", ratio_list, f"{width}.1%") ]
for title, data, format in diff_list:
print(title)
for i in range(2):
for key in order_list:
print(f" {key} {data[i][key]:{format}}", end="")
print()
print()
下記のプログラムを実行することで、ai[0]
からの 視点 で、通算成績の 回数 と 比率 の 表示 が行われることが 確認 できます。なお、match_num
の デフォルト値 である、10000 回 の 対戦 を行うと、数秒 ほど時 間がかかる ので、対戦回数 に 1000
を指定 しています。
from ai import ai1, ai2
ai_match(ai=[ai1, ai2], match_num=1000)
実行結果(実行結果はランダムなので下記とは異なる場合があります)
ai1 VS ai2
count
win 756 lose 203 draw 41
win 446 lose 514 draw 40
ratio
win 75.6% lose 20.3% draw 4.1%
win 44.6% lose 51.4% draw 4.0%
order_list
の廃止
前回の記事で説明したように、order_list
は、count
の 要素の値 を、dict から defaultdict に 変更 した結果、items
メソッドを使った 繰り返し処理 で、count
の キーの値 の 表示の順番 が 思い通り の順番に ならない場合がある ため 導入した ものです。
count_list_ai0
の 各要素 に代入された dict は、dict の リテラル で "win"
、 "lose"
、 "draw"
の キー を 記述 した 順番通り で キー に 値を代入 するので、items
メソッドを 利用 した 繰り返し処理 では、必ず "win"
、 "lose"
、 "draw"
の 順番 で キー と キーの値 が 取り出され ます。従って、count_list_ai0
の場合は order_list
は 不要 になります。
また、ratio_list
の 各要素 に代入される dict は、count_list_ai0
の 各要素 に対する for 文 の 繰り返し処理 によって キー に 値が代入 されるので、"win"
、 "lose"
、 "draw"
の 順番 で キー に 値が代入 されます。従って、比率の表示 でも order_list
は 不要 です。
下記は、order_list
を 使わない ように ai_match
を 修正 したプログラムです。
-
order_list = ["win", "lose", "draw"]
を削除する -
5 行目:
order_list
をdata[i].items()
に修正し、繰り返しのたび に取り出す キー と キーの値 をkey
とvalue
に代入する -
6 行目:
data[i][key]
をkey
に 修正 する
この修正で、order_list
を 削除 するだけでなく、6 行目 が 簡潔 になります。
1 def ai_match(ai, match_num=10000):
元と同じなので省略
2 for title, data, format in diff_list:
3 print(title)
4 for i in range(2):
5 for key, value in data[i].items():
6 print(f" {key} {value:{format}}", end="")
7 print()
8 print()
行番号のないプログラム
def ai_match(ai, 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, verbose=False)] += 1
count_list[1][mb.play(ai=[ai[1], ai[0]], 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],
},
]
# それぞれの比率を計算し、ratio_list に代入する
ratio_list = [ {}, {} ]
for i in range(2):
for key in count_list_ai0[i]:
ratio_list[i][key] = count_list_ai0[i][key] / match_num
# 通算成績の回数と比率の表示
width = max(len(str(match_num)), 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)
for i in range(2):
for key, value in data[i].items():
print(f" {key} {value:{format}}", end="")
print()
print()
修正箇所
def ai_match(ai, match_num=10000):
元と同じなので省略
- # ai[0] の勝利、勝利、引き分けの順番で表示することを表す list
- order_list = ["win", "lose", "draw"]
# 通算成績の回数と比率の表示
width = max(len(str(match_num)), 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)
for i in range(2):
- for key, value in order_list:
+ for key, value in data[i].items():
- print(f" {key} {data[i][key]:{format}}", end="")
+ print(f" {key} {value:{format}}", end="")
print()
print()
下記のプログラムを実行することで、修正後も正しく表示されることが確認できます。
ai_match(ai=[ai1, ai2], match_num=1000)
実行結果(実行結果はランダムなので下記とは異なる場合があります)
ai1 VS ai2
count
win 798 lose 155 draw 47
win 457 lose 504 draw 39
ratio
win 79.8% lose 15.5% draw 4.7%
win 45.7% lose 50.4% draw 3.9%
表示の修正 その 1 (担当するマークの表示)
上記の表示では、それぞれ の 行 のデータで、ai[0]
が どのマーク を 担当 しているかが わかりづらい ので、下記のように、各 行の先頭 に、ai[0]
が 担当 する マークを表示 するように 修正 することにします。
ai1 VS ai2
count
o win 798 lose 152 draw 50
x win 436 lose 528 draw 36
ratio
o win 79.8% lose 15.2% draw 5.0%
x win 43.6% lose 52.8% draw 3.6%
そのためには、繰り返しの処理の中 で、各 行の先頭 に 表示 する 文字列 を 要素 とする list が 必要 になります。下記のプログラムでは、そのデータを item_text_list
という名前の変数に 代入 しています。名前 は、項目(item)の 文字(text)のリストから付けました。
item_text_list = [ Marubatsu.CIRCLE, Marubatsu.CROSS ]
次に、下記のプログラムのように、結果 を 表示する処理 を 修正 します。
-
4 行目:それぞれの 行を表示 する 繰り返し処理 の 直前 で、
item_text_list[i]
を 表示 するように 修正 する。その際に、改行しない ようにend=""
を 記述 する
1 for title, data, format in diff_list:
2 print(title)
3 for i in range(2):
4 print(item_text_list[i], end="")
5 for key, value in data[i].items():
6 print(f" {key} {value:{format}}", end="")
7 print()
8 print()
修正箇所
for title, data, format in diff_list:
print(title)
for i in range(2):
+ print(item_text_list[i], end="")
for key, value in data[i].items():
print(f" {key} {value:{format}}", end="")
print()
print()
下記は、上記の修正を行った ai_match
のプログラムです。
def ai_match(ai, match_num=10000):
元と同じなので省略
# 各行の先頭に表示する文字列のリスト
item_text_list = [ Marubatsu.CIRCLE, Marubatsu.CROSS ]
# 通算成績の回数と比率の表示
width = max(len(str(match_num)), 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)
for i in range(2):
print(item_text_list[i], end="")
for key, value in data[i].items():
print(f" {key} {value:{format}}", end="")
print()
print()
全体のプログラム
def ai_match(ai, 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, verbose=False)] += 1
count_list[1][mb.play(ai=[ai[1], ai[0]], 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],
},
]
# それぞれの比率を計算し、ratio_list に代入する
ratio_list = [ {}, {} ]
for i in range(2):
for key in count_list_ai0[i]:
ratio_list[i][key] = count_list_ai0[i][key] / match_num
# 各行の先頭に表示する文字列のリスト
item_text_list = [ Marubatsu.CIRCLE, Marubatsu.CROSS ]
# 通算成績の回数と比率の表示
width = max(len(str(match_num)), 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)
for i in range(2):
print(item_text_list[i], end="")
for key, value in data[i].items():
print(f" {key} {value:{format}}", end="")
print()
print()
修正箇所
def ai_match(ai, match_num=10000):
元と同じなので省略
# 各行の先頭に表示する文字列のリスト
+ item_text_list = [ Marubatsu.CIRCLE, Marubatsu.CROSS ]
# 通算成績の回数と比率の表示
width = max(len(str(match_num)), 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)
for i in range(2):
+ print(item_text_list[i], end="")
for key, value in data[i].items():
print(f" {key} {value:{format}}", end="")
print()
print()
下記のプログラムを実行することで、行の先頭 に マークが表示 されることが 確認 できます。
ai_match(ai=[ai1, ai2], match_num=1000)
実行結果(実行結果はランダムなので下記とは異なる場合があります)
ai1 VS ai2
count
o win 804 lose 164 draw 32
x win 448 lose 508 draw 44
ratio
o win 80.4% lose 16.4% draw 3.2%
x win 44.8% lose 50.8% draw 4.4%
表示の修正 その 2 (表形式での表示)
上記の表示では、win などの 文字 と 数字 が 交互 に 並んでいる 点が少し わかりにくい という問題があります。そこで、下記のように、表 のような 形式 で 表示 することにします。
count win lose draw
o 435 110 22
x 237 303 27
ratio win lose draw
o 76.7% 19.4% 3.9%
x 41.8% 53.4% 4.8%
下記は、ai_match
をそのように修正したプログラムです。
-
3 行目:
end=""
を 記述 して、count や ratio の表示の 直後 で 改行しない ようにする -
4、5 行目:for 文 による 繰り返し処理 によって、
data[0]
の キー を 順番に取り出して、改行せず に 表示 する。その際に、キー の 直前 に 半角の空白 を 表示 することで、キーの表示 が くっつかない ようにしている -
6 行目:
print()
を実行して 改行 する -
9 行目:数字 を 表示 する 直前 に、キー を 表示 する 必要が無くなった ので、
items
ではなく、values
メソッドを使って、キーの値のみ を 取り出す ように 修正 する - 10 行目:キー を 表示しない ように 修正 する
1 def ai_match(ai, match_num=10000):
元と同じなので省略
2 for title, data, format in diff_list:
3 print(title, end="")
4 for key in data[0]:
5 print(f" {key}", end="")
6 print()
7 for i in range(2):
8 print(item_text_list[i], end="")
9 for value in data[i].values():
10 print(f" {value:{format}}", end="")
11 print()
12 print()
行番号のないプログラム
def ai_match(ai, 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, verbose=False)] += 1
count_list[1][mb.play(ai=[ai[1], ai[0]], 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],
},
]
# それぞれの比率を計算し、ratio_list に代入する
ratio_list = [ {}, {} ]
for i in range(2):
for key in count_list_ai0[i]:
ratio_list[i][key] = count_list_ai0[i][key] / match_num
# 各行の先頭に表示する文字列のリスト
item_text_list = [ Marubatsu.CIRCLE, Marubatsu.CROSS ]
# 通算成績の回数と比率の表示
width = max(len(str(match_num)), 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}", end="")
print()
for i in range(2):
print(item_text_list[i], end="")
for value in data[i].values():
print(f" {value:{format}}", end="")
print()
print()
修正箇所
def ai_match(ai, match_num=10000):
元と同じなので省略
for title, data, format in diff_list:
- print(title)
+ print(title, end="")
+ for key in data[0]:
+ print(f" {key}", end="")
+ print()
for i in range(2):
print(item_text_list[i], end="")
for value in data[i].values():
- print(f" {key} {value:{format}}", end="")
+ print(f" {value:{format}}", end="")
print()
print()
上記の 4 行目 の for 文 の 反復可能オブジェクト に data[0]
を 記述 する点が わかりづらい と思いますので説明します。文章で説明すると長くなるので、箇条書きで説明します。
-
for 文 の 繰り返し処理 によって、
data
には、count_list_ai0
とratio_list
が 順番 に 代入 される - この 2 つ の データ は、いずれも 2 つの要素 を持つ list であり、その 2 つの要素 は それぞれ
data[0]
、data[1]
のように 記述 できる - 先ほど説明したように、どの要素 も
"win"
、"lose"
、"draw"
の 順番 で キー に 値が代入 される dict である - 従って、4 行目の for 文 の 反復可能オブジェクト に、
data[0]
とdata[1]
の どちらを記述 しても、キーは"win"
、"lose"
、"draw"
の 順番 で 取り出される
上記から、4 行目の for 文 の 反復可能オブジェクト には data[0]
または data[1]
の どちらを記述 しても 構いません が、このような場合は、先頭 の 要素 の data[0]
を記述するのが一般的でしょう。なお、反復可能オブジェクト に count_list_ai0[0]
や、ratio_list[0]
などを 記述 しても かまいません。わかりやすい と思った 記述 を 採用 して下さい。
下記のプログラムを実行することで、表の形式 で 表示 されることが 確認 できますが、count と ratio の 行 と、データの表示 の 行 の 表示内容 が 上下でずれる という 問題 があります。このような、上下のずれが起きる 原因 について少し考えてみて下さい。
ai_match(ai=[ai1, ai2], match_num=1000)
実行結果(実行結果はランダムなので下記とは異なる場合があります)
ai1 VS ai2
count win lose draw
o 762 195 43
x 448 516 36
ratio win lose draw
o 76.2% 19.5% 4.3%
x 44.8% 51.6% 3.6%
上下のずれの問題の修正
上下のずれ がおきる 原因 と 修正方法 は以下の通りです。
-
count、ratio と、
o
、x
の 表示幅 が 異なる。o
とx
の 表示幅 を count、ratio と 同じ 5 文字 にすることで解決できる -
win、lose、draw の 表示幅 が、数字の表示幅 と 異なる。これらの 表示幅 を、数字 の 表示幅 である
width
に 合わせる ことで解決できる
下記は、そのように ai_match
を修正したプログラムです。
-
5 行目:
key
の 書式指定 の 文字幅 にwidth
を 指定 するように 修正 する -
8 行目:
item_text_list[i]
の 表示 を f 文字列 を使って、書式指定 の 文字幅 に5
を 指定 するように 修正 する
1 def ai_match(ai, match_num=10000):
元と同じなので省略
2 for title, data, format in diff_list:
3 print(title, end="")
4 for key in data[0]:
5 print(f" {key:{width}}", end="")
6 print()
7 for i in range(2):
8 print(f"{item_text_list[i]:5}", end="")
9 for value in data[i].values():
10 print(f" {value:{format}}", end="")
11 print()
12 print()
行番号のないプログラム
def ai_match(ai, 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, verbose=False)] += 1
count_list[1][mb.play(ai=[ai[1], ai[0]], 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],
},
]
# それぞれの比率を計算し、ratio_list に代入する
ratio_list = [ {}, {} ]
for i in range(2):
for key in count_list_ai0[i]:
ratio_list[i][key] = count_list_ai0[i][key] / match_num
# 各行の先頭に表示する文字列のリスト
item_text_list = [ Marubatsu.CIRCLE, Marubatsu.CROSS ]
# 通算成績の回数と比率の表示
width = max(len(str(match_num)), 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(2):
print(f"{item_text_list[i]:5}", end="")
for value in data[i].values():
print(f" {value:{format}}", end="")
print()
print()
修正箇所
def ai_match(ai, match_num=10000):
元と同じなので省略
for title, data, format in diff_list:
print(title, end="")
for key in data[0]:
- print(f" {key}", end="")
+ print(f" {key:{width}}", end="")
print()
for i in range(2):
- print(item_text_list[i], end="")
+ print(f"{item_text_list[i]:5}", end="")
for value in data[i].values():
print(f" {value:{format}}", end="")
print()
print()
下記のプログラムを実行すると、win、lose、draw と、その 下の数字 の ずれが解消されていない ことがわかります。その 原因 について少し考えてみて下さい。
ai_match(ai=[ai1, ai2], match_num=1000)
実行結果(実行結果はランダムなので下記とは異なる場合があります)
ai1 VS ai2
count win lose draw
o 787 167 46
x 453 519 28
ratio win lose draw
o 78.7% 16.7% 4.6%
x 45.3% 51.9% 2.8%
揃えの修正
ずれが解消されない 原因 は、前回の記事 で説明したように、f 文字列 では、書式指定 に 揃えを指定しない 場合は、文字列型 のデータは 左揃え で、数値型 のデータは 右揃え が 揃えに指定 されたことになるからです。従って、下記のプログラムのように、win、lose、draw の 書式指定 の 揃え を 右揃え に 指定 することでこの問題を 解決 することができます。
-
5 行目:書式指定 の 揃え に
>
を 指定 することで、key
を 右揃え で 表示 する
1 def ai_match(ai, match_num=10000):
元と同じなので省略
2 for title, data, format in diff_list:
3 print(title, end="")
4 for key in data[0]:
5 print(f" {key:>{width}}", end="")
6 print()
7 for i in range(2):
8 print(f"{item_text_list[i]:5}", end="")
9 for value in data[i].values():
10 print(f" {value:{format}}", end="")
11 print()
12 print()
行番号のないプログラム
def ai_match(ai, 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, verbose=False)] += 1
count_list[1][mb.play(ai=[ai[1], ai[0]], 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],
},
]
# それぞれの比率を計算し、ratio_list に代入する
ratio_list = [ {}, {} ]
for i in range(2):
for key in count_list_ai0[i]:
ratio_list[i][key] = count_list_ai0[i][key] / match_num
# 各行の先頭に表示する文字列のリスト
item_text_list = [ Marubatsu.CIRCLE, Marubatsu.CROSS ]
# 通算成績の回数と比率の表示
width = max(len(str(match_num)), 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(2):
print(f"{item_text_list[i]:5}", end="")
for value in data[i].values():
print(f" {value:{format}}", end="")
print()
print()
修正箇所
def ai_match(ai, match_num=10000):
元と同じなので省略
for title, data, format in diff_list:
print(title, end="")
for key in data[0]:
- print(f" {key:{width}}", end="")
+ print(f" {key:>{width}}", end="")
print()
for i in range(2):
print(f"{item_text_list[i]:5}", end="")
for value in data[i].values():
print(f" {value:{format}}", end="")
print()
print()
下記のプログラムを実行するとで、意図通り に 表示 が行われることが 確認 できます。
ai_match(ai=[ai1, ai2], match_num=1000)
実行結果(実行結果はランダムなので下記とは異なる場合があります)
ai1 VS ai2
count win lose draw
o 780 173 47
x 436 537 27
ratio win lose draw
o 78.0% 17.3% 4.7%
x 43.6% 53.7% 2.7%
行の先頭に表示する文字の、文字数が異なる場合の対処法
ai_match
では、行の先頭 に 表示 する、count と ratio の文字列が 偶然同じ 5 文字 だったので 必要ありません でしたが、それらの 文字列の長さ が 異なる 場合は、下記のプログラムのように、表示 する 文字幅 を 変数 に代入し、その 変数を使って 書式指定の 文字幅 を 指定 する必要があります。なお、変数 に 文字幅 を 代入 しておくことで、後から 文字幅を 変更したい と思った時に、簡単 に 変更できるようになる という 利点 が得られます。
-
2 行目:文字幅 を表す 数値 を
title_width
に代入する -
4、9 行目:
title_width
を使って、書式指定 の 文字幅 を 指定 するように 修正 する
1 def ai_match(ai, match_num=10000):
元と同じなので省略
2 title_width = 5
3 for title, data, format in diff_list:
4 print(f"{title:{title_width}}", end="")
5 for key in data[0]:
6 print(f" {key:>{width}}", end="")
7 print()
8 for i in range(2):
9 print(f"{item_text_list[i]:{title_width}}", end="")
10 for value in data[i].values():
11 print(f" {value:{format}}", end="")
12 print()
13 print()
行番号のないプログラム
def ai_match(ai, 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, verbose=False)] += 1
count_list[1][mb.play(ai=[ai[1], ai[0]], 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],
},
]
# それぞれの比率を計算し、ratio_list に代入する
ratio_list = [ {}, {} ]
for i in range(2):
for key in count_list_ai0[i]:
ratio_list[i][key] = count_list_ai0[i][key] / match_num
# 各行の先頭に表示する文字列のリスト
item_text_list = [ Marubatsu.CIRCLE, Marubatsu.CROSS ]
# 通算成績の回数と比率の表示
width = max(len(str(match_num)), 7)
diff_list = [ ("count", count_list_ai0, f"{width}d"),
("ratio", ratio_list, f"{width}.1%") ]
title_width = 5
for title, data, format in diff_list:
print(f"{title:{title_width}}", end="")
for key in data[0]:
print(f" {key:>{width}}", end="")
print()
for i in range(2):
print(f"{item_text_list[i]:{title_width}}", end="")
for value in data[i].values():
print(f" {value:{format}}", end="")
print()
print()
修正箇所
def ai_match(ai, match_num=10000):
元と同じなので省略
+ title_width = 5
for title, data, format in diff_list:
- print(title, end="")
+ print(f"{title:{title_width}}", end="")
for key in data[0]:
print(f" {key:>{width}}", end="")
print()
for i in range(2):
- print(f"{item_text_list[i]:5", end="")
+ print(f"{item_text_list[i]:{title_width}}", end="")
for value in data[i].values():
print(f" {value:{format}}", end="")
print()
print()
下記のプログラムを実行するとで、意図通り に 表示 が行われることが 確認 できます。興味がある方は、title_width
の値を 変更 したり、diff_list
の中の "count"
と "ratio"
を 長さの異なる文字列 に 変更 しても 意図通り に 表示 されることを 確認 してみて下さい。
ai_match(ai=[ai1, ai2], match_num=1000)
実行結果(実行結果はランダムなので下記とは異なる場合があります)
ai1 VS ai2
count win lose draw
o 788 192 20
x 440 534 26
ratio win lose draw
o 78.8% 19.2% 2.0%
x 44.0% 53.4% 2.6%
ai_match
の改良 その 7(両方の対戦を合わせた結果の表示)
ai_match
は、手番を入れ替えない場合 と、手番を入れ替えた場合 の 表示 を行いますが、〇×ゲーム は、〇 の手番 と × の手番 で、有利不利 が 大きく異なる ゲームなので、それだけ では どちらが強いか が はっきりとしません。そこで、その 両方 の 対戦を合わせた 成績を 計算 して 表示 することで、どちらが強いか を 明確にする ことにします。
両方の対戦 を合わせた 回数 と 比率 は、count_list_ai0
と ratio_list
の 要素 に 代入 するのが 自然 なので、それらの 2 番 の 要素 に 代入 することにします。
下記の表は、count_list_ai0
と ratio_list
の インデックス と 要素 をまとめたものです。
インデックス | 要素 |
---|---|
0 | 順番を 入れ替えない 場合の対戦結果 |
1 | 順番を 入れ替えた 場合の対戦結果 |
2 | 両方の対戦 を 合わせた 場合の対戦結果 |
両方の対戦を合わせた回数の計算
両方の対戦 を 合わせた回数 の 計算 は、count_list_ai0
の 0 番 と 1 番 の 要素 の win、lose、draw の それぞれ の 値の合計 なので、下記のプログラムのように、繰り返し処理 を使って 計算 することが できます。
-
1 行目:
count_list_ai0
の 2 番 の 要素 として、空の dict を 追加 する -
2、3 行目:
count_list_ai0
の 0 番 の 要素 に代入された dict から 順番 に キーを取り出し、2 番 の 要素 の dict の そのキーの値 に、0 番 と 1 番 の 要素 の そのキーの値 の 合計 を計算して 代入 する
2 行目の for 文 の 反復可能オブジェクト に、count_list_ai[0]
を 記述 した 理由 は、先程の「表示の修正その 2」で 説明 した理由と 同じ です。同様の理由で、反復可能オブジェクト に count_list_ai[1]
を 記述 しても 構いません。
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
の 2 番 の要素は、0 番 と 1 番 の 要素 と 同じ方法 で 計算 できるので、ratio_list
に関する プログラム は以下のように 修正 します。
- 1 行目:3 つ の 空の dict を 要素 として持つ list に 修正 する
- 2 行目:繰り返し の 回数 を 2 回から 3 回 に 修正 する
ratio_list = [ {}, {}, {} ]
for i in range(3):
for key in ai_count_list[i]:
ratio_list[i][key] = ai_count_list[i][key] / sum(ai_count_list[i])
次に、両方の対戦 を 合わせた 結果の 行の先頭 に 表示 する 文字列 を 決める 必要があります。両方の対戦 の 合計 なので、"total"
と表示することにし、下記のプログラムのように、各行の 先頭に表示 する 文字列 を 代入 する item_text_list
を 修正 します。
item_text_list = [ Marubatsu.CIRCLE, Marubatsu.CROSS, "total" ]
最後に、下記のプログラムの 1 行目のように、結果の行 を 表示 する 繰り返し処理 の 回数 を 2 から 3 に 修正 することで、両方の対戦を合わせた結果 が 表示 されるようになります。
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()
修正箇所
-for i in range(2):
+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()
下記は、ai_match
を上記のように修正したプログラムです。
def ai_match(ai, match_num=10000):
元と同じなので省略
# 両方の対戦の通算成績の合計を計算する
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] / match_num
# 各行の先頭に表示する文字列のリスト
item_text_list = [ Marubatsu.CIRCLE, Marubatsu.CROSS, "total" ]
# 通算成績の回数と比率の表示
width = max(len(str(match_num)), 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()
行番号のないプログラム
def ai_match(ai, 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, verbose=False)] += 1
count_list[1][mb.play(ai=[ai[1], ai[0]], 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] / match_num
# 各行の先頭に表示する文字列のリスト
item_text_list = [ Marubatsu.CIRCLE, Marubatsu.CROSS, "total" ]
# 通算成績の回数と比率の表示
width = max(len(str(match_num)), 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()
修正箇所
def ai_match(ai, match_num=10000):
元と同じなので省略
+ # 両方の対戦の通算成績の合計を計算する
+ 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 = [ {}, {} ]
+ ratio_list = [ {}, {}, {} ]
- for i in range(2):
+ for i in range(3):
for key in count_list_ai0[i]:
ratio_list[i][key] = count_list_ai0[i][key] / match_num
# 各行の先頭に表示する文字列のリスト
- item_text_list = [ Marubatsu.CIRCLE, Marubatsu.CROSS ]
+ item_text_list = [ Marubatsu.CIRCLE, Marubatsu.CROSS, "total" ]
# 通算成績の回数と比率の表示
width = max(len(str(match_num)), 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(2):
+ 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()
下記のプログラムを実行することで、total の行 が 表示 されるようになることが 確認 できますが、total の win の 数字 が 121.7% のように、100% を 超えてしまう という 問題 があることがわかります。このようなことが起きる原因について少し考えてみて下さい。
ai_match(ai=[ai1, ai2], match_num=1000)
実行結果(実行結果はランダムなので下記とは異なる場合があります)
ai1 VS ai2
count win lose draw
o 780 180 40
x 437 533 30
total 1217 713 70
ratio win lose draw
o 78.0% 18.0% 4.0%
x 43.7% 53.3% 3.0%
total 121.7% 71.3% 7.0%
比率の計算の修正
問題は、下記の total の 比率 を 計算 するプログラムの 3 行目 の 計算式 にあります。
for i in range(3):
for key in count_list_ai0[i]:
ratio_list[i][key] = count_list_ai0[i][key] / match_num
具体的には、total の 試合数 は、入れ替えない場合 の match_num
回 と、入れ替えた場合 の match_num
回 の 合計 の、match_num * 2
回 です。それにも関わらず、上記のプログラムの 3 行目 では、比率を計算 する際に、match_num
で 割っている 点が バグの原因 です。
このバグを修正する方法の一つは、下記のプログラムのように if 文 を使って、total の場合 の比率を計算する i
が 2
の 場合 に match_num * 2
で 割る という方法です。
for i in range(3):
for key in count_list_ai0[i]:
if i != 2:
ratio_list[i][key] = count_list_ai0[i][key] / match_num
else:
ratio_list[i][key] = count_list_ai0[i][key] / (match_num * 2)
ただし、それぞれ の 試合数の合計 が count_list_ai0
の 要素 の win と lose と draw の キーの値 の 合計 であることに 気が付く ことができれば、以前の記事で紹介した、反復可能オブジェクト の 要素の合計 を 計算 する組み込み関数 sum
を使って、上記のプログラムの処理を、下記のプログラムのように 簡潔に記述 することができます。
-
3 行目:
count_list_ai0
のi
番 の 要素 に代入された dict の すべて の キーの値 の 合計 をsum
とvalues
メソッドを使って 計算 した値で、割り算 するように 修正 する
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())
dict の キーの値 の 合計 を 計算 する場合は、上記のプログラムのように、sum
の 実引数 に、dict の キーの値 を 要素 として持つ 反復可能オブジェクト を返す values
メソッドの 返り値 を 記述 する 必要 がある点に 注意 して下さい。これは、下記のプログラムの 1 行目のように、sum
の 実引数 に、dict を そのまま記述 すると、dict の キーの値ではなく、キー の 合計 が 計算 されるからです。
a = { 1: 10, 2: 20, 3: 30 }
print(sum(a)) # キーの合計の 1 + 2 + 3 が計算される
print(sum(a.values())) # キーの値の合計の 10 + 20 + 30 が計算される
実行結果
6
60
表示幅の修正
気づいていない方が多いのではないかと思いますが、実は、total の 表示 を 導入 したことによって、もう一つ 表示 に関する バグ が 発生する可能性 が生じています。
それは、回数 の total の行に 表示 される 数字 の 最大値 が match_num * 2
であることから、数字 の 表示幅 が width = max(len(str(match_num)), 7)
という計算式で計算される幅 よりも大きく なってしまう 可能性が生じるか らです。そのため、width
を下記のように、match_num * 2
を 使って計算 するように 修正 する必要があります。
width = max(len(str(match_num * 2)), 7)
修正箇所
-width = max(len(str(match_num)), 7)
+width = max(len(str(match_num * 2)), 7)
ai_match
の修正
下記は、ai_match
を上記のように修正したプログラムです。
def ai_match(ai, match_num=10000):
元と同じなので省略
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)), 7)
元と同じなので省略
全体のプログラム
def ai_match(ai, 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, verbose=False)] += 1
count_list[1][mb.play(ai=[ai[1], ai[0]], 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()
修正箇所
def ai_match(ai, match_num=10000):
元と同じなので省略
for i in range(3):
for key in count_list_ai0[i]:
- ratio_list[i][key] = count_list_ai0[i][key] / match_num
+ 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)), 7)
+ width = max(len(str(match_num * 2)), 7)
元と同じなので省略
下記のプログラムを実行することで、total の行 が 正しく表示 されるようになったことが 確認 できます。
ai_match(ai=[ai1, ai2], match_num=1000)
実行結果(実行結果はランダムなので下記とは異なる場合があります)
ai1 VS ai2
count win lose draw
o 770 196 34
x 456 506 38
total 1226 702 72
ratio win lose draw
o 77.0% 19.6% 3.4%
x 45.6% 50.6% 3.8%
total 61.3% 35.1% 3.6%
ai1
と ai2
の対戦
AI どうし の 対戦 を行い、結果を表示 する 関数 が 完成 したので、これまでに作成した ai1
と ai2
で 対戦 を行うことにします。以前の記事 で説明したように、基準 となる ai2
に対し、ai1
VS ai2
と ai2
VS ai2
で対戦を行います。なお、まだ 強い AI は 作成していない ので、それまでに作成した 最も強い AI との 対戦 を 行う必要 は まだありません。
下記は、その対戦結果です。
ai_match(ai=[ai1, ai2])
実行結果(実行結果はランダムなので下記とは異なる場合があります)
ai1 VS ai2
count win lose draw
o 7811 1753 436
x 4467 5156 377
total 12278 6909 813
ratio win lose draw
o 78.1% 17.5% 4.4%
x 44.7% 51.6% 3.8%
total 61.4% 34.5% 4.1%
ai_match(ai=[ai2, ai2])
実行結果(実行結果はランダムなので下記とは異なる場合があります)
ai2 VS ai2
count win lose draw
o 5867 2876 1257
x 2908 5858 1234
total 8775 8734 2491
ratio win lose draw
o 58.7% 28.8% 12.6%
x 29.1% 58.6% 12.3%
total 43.9% 43.7% 12.5%
この 結果 を 表にまとめます。この表は、関数名 の 関数 と、基準 となる ai2
で 複数回対戦 した際の、「o を担当 した場合」、「x を担当 した場合」、「両方を合わせた 場合」、のそれぞれの 勝率、敗率、引き分け率 を % の単位 で 表記 したものです。なお、回数 の データ は 強さを評価 する際に 必要がない ので 表には入れない ことにします。
また、ai2
VS ai2
の 対戦結果 を 基準 とし、それよりも 勝率が高い、または、敗率が低い 数値は、基準となる ai2
に対して強い AI であることを表すため、 太字で表記 します。
関数名 | o 勝 | o 負 | o 分 | x 勝 | x 負 | x 分 | 勝 | 負 | 分 |
---|---|---|---|---|---|---|---|---|---|
ai1 |
78.1 | 17.5 | 4.4 | 44.7 | 51.6 | 3.8 | 61.4 | 34.5 | 4.1 |
ai2 |
58.7 | 28.8 | 12.6 | 29.1 | 58.6 | 12.3 | 43.9 | 43.7 | 12.5 |
表から、ai1
は ai2
に 対して強い AI であることが わかります。ただし、以前の記事で説明したように、基準 となる ai2
よりも強い からと言って、その AI が 他の ai2
より弱い AI より強い とは 限らない 点に 注意 して下さい。ai1
が 実際にそうである ように、ai2
と 相性が良い せいで、ai1
が ai2
に 大きく勝ち越す ような 場合がある からです。
また、ai2
VS ai2
の 合計 の 勝率と敗率 が 約 43% で ほぼ同じ であるという、同じ AI どうし で 対戦 した場合の 当然の結果 が得られたことが 確認 できます。
以後は、新しい AI を作成 する たび に、この 表を更新 していくことで、作成した AI の 強さの評価 を 行います。
本記事で入力したプログラム
以下のリンクから、本記事で入力して実行した JupyterLab のファイルを見ることができます。
今回の記事では、marubatsu.py は修正していないので、marubatsu_new.py はありません。
以下のリンクは、今回の記事で更新した ai.py です。
次回の記事