目次と前回の記事
これまでに作成したモジュール
以下のリンクから、これまでに作成したモジュールを見ることができます。
これまでに作成した AI
これまでに作成した AI の アルゴリズム は以下の通りです。
関数名 | アルゴリズム |
---|---|
ai1 |
左上から順に空いているマスを探し、最初に見つかったマスに着手する |
ai2 |
ランダムなマスに着手する |
ai_match
の改良 その 4(手番を入れ替えた対戦)
前回の記事では ai_match
のいくつかの改良を行いました。今回の記事では、引き続き ai_match
の改良を行います。
〇× ゲームを遊んでみるとすぐにわかると思いますが、〇× ゲーム は、〇 の手番 と × の手番 で 有利不利 が 大きく変わる ゲームです。そのため、AI どうし の 強さを測る ためには、〇 と × の担当 を 入れ替え た対戦も 行う必要 がありますが、ai_match
は 片方 の対戦 しか行いません。例えば ai1
と ai2
の場合は、ai1
VS ai2
と ai2
VS ai1
の 両方の対戦 を行う 必要 があります。そこで、実引数で指定 した 2 つの AI を、〇 と × の 手番を入れ替え た状態でも 対戦を行う ように ai_match
を 修正 することにします。
この修正では、ai_match
の修正箇所が多いので、順番に少しづつ修正することにします。
count
の修正
手番を入れ替えない場合と、入れ替えた場合の 2 種類 の 対戦 を行うので、2 種類 の 通算成績 を 数える 必要があります。元のプログラム では、count
という 変数 で 通算成績 を数えていましたが、手番を入れ替えた場合 の通算成績を count2
のような 別の変数 に代入すると プログラムの記述 が 面倒 なことになるので、下記のプログラムの 5 行目のように、count_list
という変数に、手番を入れ替えない 場合と 手番を入れ替える場合 の 通算成績 を 数える ための defaultdict を 要素 として持つ list を代入 することにします。
1 def ai_match(ai, match_num=10000):
2 mb = Marubatsu()
3
4 # ai[0] VS ai[1] と ai[1] VS a[0] の対戦を match_num 回行い、通算成績を数える
5 count_list = [ defaultdict(int), defaultdict(int) ]
元と同じなので省略
上記を count_list = [defaultdict(int)] * 2
のように記述すると、以前の記事で説明したように、count_list
の 0 番の要素 と、1 番の要素 が、同一の defaultdict 表すことになるため、プログラムが 正しく動作しなくなる 点に注意して下さい。
修正箇所
def ai_match(ai, match_num=10000):
mb = Marubatsu()
# # ai[0] VS ai[1] と ai[1] VS a[0] の対戦を match_num 回行い、通算成績を数える
- count = defaultdict(int)
+ count_list = [ defaultdict(int), defaultdict(int) ]
元と同じなので省略
なお、上記の 修正途中 のプログラムを 実行 すると エラーが発生 するので、行番号のないプログラム は 省略 します。また、プログラムが 正しく動作するか どうかの 確認 は、すべての 修正が完了 してから 行います。
対戦を行い、通算成績を数える処理の修正
次に、対戦 を行い、通算成績を数える 処理を、下記のプログラムのように修正します。
-
7 行目:手番を入れ替えない 場合の 通算成績 は
count_list[0]
に代入されるので、元のプログラムのcount
をcount_list[0]
に 修正 する -
8 行目:手番を入れ替えた場合 の AI の関数 の list は、
[ai[1], ai[0]]
なので、play
メソッドの キーワード引数ai
にその list を代入して呼び出すことで、手番を入れ替えた対戦 を行うことができる。通算成績 はcount_list[1]
を使って数える
1 def ai_match(ai, match_num=10000):
2 mb = Marubatsu()
3
4 # ai[0] VS ai[1] と ai[1] VS a[0] の対戦を match_num 回行い、通算成績を数える
5 count_list = [ defaultdict(int), defaultdict(int)]
6 for _ in range(match_num):
7 count_list[0][mb.play(ai, verbose=False)] += 1
8 count_list[1][mb.play(ai=[ai[1], ai[0]], verbose=False)] += 1
元と同じなので省略
修正箇所
def ai_match(ai, match_num=10000):
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[mb.play(ai, verbose=False)] += 1
+ count_list[0][mb.play(ai, verbose=False)] += 1
+ count_list[1][mb.play(ai=[ai[1], ai[0]], verbose=False)] += 1
元と同じなので省略
7 行目と 8 行目のプログラムを、下記のプログラムのように、for 文 による 繰り返し処理 をつかって まとめる ことができますが、慣れない とプログラムの意味が 分かりづらくなる ので、無理にまとめる必要はない でしょう。本記事では下記のようにまとめませんが、まとめるかどうかは各自で判断して下さい。
for _ in range(match_num):
for i in range(2):
count_list[i][mb.play(ai=[ai[i], ai[1 - i]], verbose=False)] += 1
修正箇所
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
+ for i in range(2):
+ count_list[i][mb.play(ai=[ai[i], ai[1 - i]], verbose=False)] += 1
上記のプログラムの意味が分からない人は、i
が 0
と 1
の それぞれの場合 で 3 行目のプログラム が行う処理を、下記の表のように 実際に記述 してみてください。
i |
3 行目のプログラム |
---|---|
0 |
count_list[0][mb.play(ai=[ai[0], ai[1]], verbose=False)] += 1 |
1 |
count_list[1][mb.play(ai=[ai[1], ai[0]], verbose=False)] += 1 |
表から、i
が 1
の 場合 は、元のプログラムの 8 行目と同じ処理 が行われることがわかります。i
が 0
の場合は、play
メソッドの 実引数 が ai=[ai[0], ai[1]]
となっている点が 異なります が、[ai[0], ai[1]]
が、仮引数 に ai
に代入された list と全く 同じ要素を持つ list であることが 理解 できれば、元のプログラムの 7 行目と同じ処理 が行われることがわかります。
[ ai[1], ai[0] ]
は、ai
に代入された list の 要素 の 順番を逆 にしたものです。以前の記事 で説明したように、list のスライス表記に [::-1]
を記述することで、list 要素 の 順番を逆 にした list を 作成 することができます。従って、先程の 8 行目のプログラムは、下記のように記述できます。
count_list[1][mb.play(ai[::-1], verbose=False)] += 1
修正箇所
-count_list[1][mb.play(ai=[ai[1], ai[0]], verbose=False)] += 1
+count_list[1][mb.play(ai[::-1], verbose=False)] += 1
なお、上記の記述は、要素が多い list の場合は 簡潔に記述 できて 便利 ですが、慣れない とプログラムの 意味が分かりづらくなる ので本記事では採用しません。
他にも、reverse
や reversed
メソッド を使う方法がありますが、それらを 使いこなす ためには、正しい知識 が 必要 で、間違った使い方 をすると バグの原因 になる 可能性が高い ので、今回の記事では紹介しません。それらについては、必要になる場面があれば紹介しようと思います。
ratio
と比率の計算の修正
比率を数える ための 変数 も、count_list
と 同様 に、ratio_list
という 名前 にし、入れ替えない場合 と、入れ替えた場合 の 比率 を 要素 とする list を 代入 することにします。
下記は、ai_match
の中で、比率 を 計算 する部分の 処理 を抜き出して、ratio_list
を使って 比率 を 計算 するように 修正 したプログラムです。
-
2 行目:入れ替えない場合と、入れ替えた場合の 比率 を 計算する ための 空の dict を 要素 とする list を
ratio_list
に 代入 する - 3 行目:for 文 による 繰り返し処理 によって、入れ替えない場合 と 入れ替える場合 の 比率を計算 する 処理 を、4、5 行目で まとめて記述する
-
4、5 行目:
count
をcount_list[i]
に、ratio
をratio_list[i]
に 修正 する
# それぞれの比率を計算し、ratio_list に代入する
ratio_list = [ {}, {} ]
for i in range(2):
for key in count_list[i]:
ratio_list[i][key] = count_list[i][key] / match_num
修正箇所
for 文 の 追加 による インデントの変化 は、修正箇所 に 含めません。
# それぞれの比率を計算し、ratio_list に代入する
-ratio = {}
+ratio_list = [ {}, {} ]
+for i in range(2):
- for key in count.keys():
+ for key in count_list[i]:
- ratio[i][key] = count[key] / match_num
+ ratio_list[i][key] = count_list[i][key] / match_num
前回の記事では、上記のプログラムの 3 行目の for 文 の 反復可能オブジェクト には、下記のプログラムの 1 行目のように keys
メソッドを 記述 していましたが、下記の 4 行目のように、keys
メソッドを 記述しない 場合でも 同じ処理 が行われるので、今回の記事からは、keys
メソッドの 記述 を 省略 することにします。
for key in count.keys():
for 文のブロックの処理
for key in count:
for 文のブロックの処理
上記のプログラムの 3 行目と 4 行目の for 文 を、下記のプログラムのように 入れ替える と エラーが発生する 点に 注意が必要 です。エラーが発生する 理由 は、下記のプログラムの 3 行目を実行 する際に、count_list[i]
の i
が 定義されていない からです。元のプログラム では、for i in range(2)
の ブロックの中 に、count_list[i]
が 記述 されているため エラー は 発生しません。
# それぞれの比率を計算し、ratio_list に代入する
ratio_list = [ {}, {} ]
for key in count_list[i]:
for i in range(2):
ratio_list[i][key] = count_list[i][key] / match_num
結果の表示部分の修正
下記は、ai_match
の中で、結果を表示 する部分の処理を抜き出して 修正 したプログラムです。回数や比率 を計算する場合と 同様 に、for 文 による 繰り返し処理 で 処理をまとめる ことができます。なお、この直前に記述する order_list
は 修正 の 必要 は ありません。
-
2 行目:
count
をcount_list
に、ratio
をratio_list
に 修正 する - 5 行目:for 文 による 繰り返し処理 によって、入れ替えない場合 と 入れ替える場合 の 結果を表示 する 処理 を、6 ~ 8 行目で まとめて記述する
-
7月 行目:
data
をdata[i]
に修正する
1 # 通算成績の回数と比率の表示
2 diff_list = [ ("count", count_list, "d"), ("ratio", ratio_list, ".1%") ]
3 for title, data, format in diff_list:
4 print(title, end="")
5 for i in range(2):
6 for key in order_list:
7 print(f" {key} {data[i][key]:{format}}", end="")
8 print()
修正箇所
for 文 の 追加 による インデントの変化 は、修正箇所 に 含めません。
# 通算成績の回数と比率の表示
-diff_list = [ ("count", count, "d"), ("ratio", ratio, ".1%") ]
+diff_list = [ ("count", count_list, "d"), ("ratio", ratio_list, ".1%") ]
for title, data, format in diff_list:
print(title, end="")
+ for i in range(2):
for key in order_list:
- print(f" {key} {data[key]:{format}}", end="")
+ print(f" {key} {data[i][key]:{format}}", end="")
print()
上記の 7 行目 の 修正 がわかりづらいと思いますので補足します。下記の表は、元のプログラムと、修正後のプログラムで、data
と data[i][key]
の部分が 何であるか を表します。表から、7 行目 の 修正 によって、7 行目 の data[i][key]
の部分の処理で、count
が count_list[i]
に、ratio
が ratio_list[i]
に 修正 されたことがわかります。
元のプログラム | 修正後のプログラム | |
---|---|---|
data の部分 |
count またはratio
|
count_list またはratio_list
|
data[i][key] の部分 |
count[key] またはratio[key]
|
count_list[i][key] またはratio_list[i][key]
|
ai_match
の修正
下記は、上記のすべての修正を行った ai_match
のプログラムです。
from marubatsu import Marubatsu
from collections import defaultdict
def ai_match(ai, match_num=10000):
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
# それぞれの比率を計算し、ratio_list に代入する
ratio_list = [ {}, {} ]
for i in range(2):
for key in count_list[i]:
ratio_list[i][key] = count_list[i][key] / match_num
# 〇の勝利、×の勝利、引き分けの順番で表示することを表す list
order_list = [Marubatsu.CIRCLE, Marubatsu.CROSS, Marubatsu.DRAW]
# 通算成績の回数と比率の表示
diff_list = [ ("count", count_list, "d"), ("ratio", ratio_list, ".1%") ]
for title, data, format in diff_list:
print(title, end="")
for i in range(2):
for key in order_list:
print(f" {key} {data[i][key]:{format}}", end="")
print()
修正箇所
from marubatsu import Marubatsu
from collections import defaultdict
def ai_match(ai, match_num=10000):
mb = Marubatsu()
# ai[0] VS ai[1] と ai[1] VS a[0] の対戦を match_num 回行い、通算成績を数える
- count = defaultdict(int)
+ count_list = [ defaultdict(int), defaultdict(int) ]
for _ in range(match_num):
- count[mb.play(ai, verbose=False)] += 1
+ count_list[0][mb.play(ai, verbose=False)] += 1
+ count_list[1][mb.play(ai=[ai[1], ai[0]], verbose=False)] += 1
# それぞれの比率を計算し、ratio_list に代入する
- ratio = {}
+ ratio_list = [ {}, {} ]
+ for i in range(2):
- for key in count:
+ for key in count_list[i]:
- ratio[i][key] = count[key] / match_num
+ ratio_list[i][key] = count_list[i][key] / match_num
# 〇の勝利、×の勝利、引き分けの順番で表示することを表す list
order_list = [Marubatsu.CIRCLE, Marubatsu.CROSS, Marubatsu.DRAW]
# 通算成績の回数と比率の表示
- diff_list = [ ("count", count, "d"), ("ratio", ratio, ".1%") ]
+ diff_list = [ ("count", count_list, "d"), ("ratio", ratio_list, ".1%") ]
for title, data, format in diff_list:
print(title, end="")
+ for i in range(2):
for key in order_list:
- print(f" {key} {data[key]:{format}}", end="")
+ print(f" {key} {data[i][key]:{format}}", end="")
print()
下記のプログラムを実行することで、ai1
VS ai2
と ai2
VS ai1
の それぞれ の 通算成績 の 回数 と 比率 が 表示 されることが確認できます。
from ai import ai1, ai2
ai_match(ai=[ai1, ai2])
実行結果(実行結果はランダムなので下記とは異なる場合があります)
count o 7843 x 1758 draw 399
o 5234 x 4397 draw 369
ratio o 78.4% x 17.6% draw 4.0%
o 52.3% x 44.0% draw 3.7%
ai_match
の改良 その 5(表示をわかりやすくする)
修正した ai_match
の 表示 には、いくつか 分かりづらい 点があるので 改良 します。どのような点が分かりづらいかについて少し考えてみて下さい。
表示の改良その 1(count と ratio の後で改行する)
先程の実行結果では、1 行目と、3 行目の先頭に、count と ratio が表示されるため、上下の行 での数字の 表示位置 が ずれてしまう せいで 分かりづらく なっています。この問題は、下記のプログラムのように、count と ratio の後で 改行 することで簡単に 解決 できます。
-
5 行目:
print
の 実引数 のend=""
を 削除 して 改行しない ようにする
1 def ai_match(ai, match_num=10000):
元と同じなので省略
2 # 通算成績の回数と比率の表示
3 diff_list = [ ("count", count_list, "d"), ("ratio", ratio_list, ".1%") ]
4 for title, data, format in diff_list:
5 print(title)
6 for i in range(2):
7 for key in order_list:
8 print(f" {key} {data[i][key]:{format}}", end="")
9 print()
行番号のないプログラム
def ai_match(ai, match_num=10000):
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
# それぞれの比率を計算し、ratio_list に代入する
ratio_list = [ {}, {} ]
for i in range(2):
for key in count_list[i]:
ratio_list[i][key] = count_list[i][key] / match_num
# 〇の勝利、×の勝利、引き分けの順番で表示することを表す list
order_list = [Marubatsu.CIRCLE, Marubatsu.CROSS, Marubatsu.DRAW]
# 通算成績の回数と比率の表示
diff_list = [ ("count", count_list, "d"), ("ratio", ratio_list, ".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()
修正箇所
def ai_match(ai, match_num=10000):
元と同じなので省略
# 通算成績の回数と比率の表示
diff_list = [ ("count", count_list, "d"), ("ratio", ratio_list, ".1%") ]
for title, data, format in diff_list:
- print(title, end="")
+ print(title)
for i in range(2):
for key in order_list:
print(f" {key} {data[i][key]:{format}}", end="")
print()
下記のプログラムを実行することで、count などの後で 改行 されることが確認できます。
ai_match(ai=[ai1, ai2])
実行結果(実行結果はランダムなので下記とは異なる場合があります)
count
o 7791 x 1800 draw 409
o 5198 x 4402 draw 400
ratio
o 77.9% x 18.0% draw 4.1%
o 52.0% x 44.0% draw 4.0%
表示の改良その 2(count の回数の表示の文字幅を揃える)
下記は、match_num
に 300
を指定して 300 回 ずつ 対戦を行う プログラムです。
ai_match(ai=[ai1, ai2], match_num=300)
実行結果(実行結果はランダムなので下記とは異なる場合があります)
count
o 248 x 41 draw 11
o 152 x 137 draw 11
ratio
o 82.7% x 13.7% draw 3.7%
o 50.7% x 45.7% draw 3.7%
上記の実行結果では、2 行目 の x の数 が 41
、3 行目 の x の数 が 137
のように、桁数が異なる ので、その後の draw
の 表示 が、2 行目と 3 行目で ずれて しまいます。上下の行 で 数字 の 表示位置 が ずれる と わかりづらくなる ので、表示位置 を 揃える ことにします。
数字の 表示位置 を 揃える 最も簡単な方法は、すべて の数字の 表示幅 を 揃える という方法です。そのためには、数字の 表示幅 を 決める 必要があります。
o と x と draw で、それぞれ 別々に 数字の 表示幅 を 決める こともできますが、処理が面倒 なわりに、見やすさ が 大きく向上しない ので本記事では紹介しません。
数字の表示幅の決め方 その 1 (想定される数値の最大値から決める)
数字の表示幅 として、想定 される 最大 の 表示幅 を 設定する という方法があります。ai_match
の場合、表示される 通算成績 の 回数 の 数字 の 最大値 は、対戦 を行う 回数 です。従って、対戦 を行う 回数 の 最大値 がわかれば、表示幅 の 最大値 が 決まり ます。
ai_match
では、match_num
に 任意 の 対戦回数 を 設定 できますが、あまり 大きな回数 の対戦を 行っても 、ai_match
の 計算に時間がかかる だけで、通算成績の 精度 はほとんど 向上しません。そこで、多く見積もって、match_num
の 最大値 として 1000000
(百万)を 想定 することにします。なお、この百万は、筆者のパソコンで、match_num
に 10000
(一万)を指定して ai_match
を 実行 すると、約 3 秒 かかることから設定しました。
1000000
の 文字数 は 7 文字 なので、下記のプログラムのように、前回の記事 で紹介した、書式指定 の 文字幅 に 7 を指定 することで、空白文字 を 含めて、常に 回数を表す数値が 7 文字で表示 されるようになり、上下 の数字の 表示位置 が 必ず揃う ようになります。
-
3 行目:回数 の 書式指定 の
d
を、文字幅 に7
を 指定 する7d
に 修正 する
1 def ai_match(ai, match_num=10000):
元と同じなので省略
2 # 通算成績の回数と比率の表示
3 diff_list = [ ("count", count_list, "7d"), ("ratio", ratio_list, ".1%") ]
4 for title, data, format in diff_list:
5 print(title)
6 for i in range(2):
7 for key in order_list:
8 print(f" {key} {data[i][key]:{format}}", end="")
9 print()
行番号のないプログラム
def ai_match(ai, match_num=10000):
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
# それぞれの比率を計算し、ratio_list に代入する
ratio_list = [ {}, {} ]
for i in range(2):
for key in count_list[i]:
ratio_list[i][key] = count_list[i][key] / match_num
# 〇の勝利、×の勝利、引き分けの順番で表示することを表す list
order_list = [Marubatsu.CIRCLE, Marubatsu.CROSS, Marubatsu.DRAW]
# 通算成績の回数と比率の表示
diff_list = [ ("count", count_list, "7d"), ("ratio", ratio_list, ".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()
修正箇所
def ai_match(ai, match_num=10000):
元と同じなので省略 # 通算成績の回数と比率の表示
- diff_list = [ ("count", count_list, "d"), ("ratio", ratio_list, ".1%") ]
+ diff_list = [ ("count", count_list, "7d"), ("ratio", ratio_list, ".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()
下記のプログラムを実行することで、回数の 表示位置 が 上下で揃う ことが 確認 できます。なお、 間が空きすぎる 点や、数字 と o や x の 間が狭い ことが 気になる人 は、書式指定 の 文字幅 を 5
や 6
に 減らし たり、8 行目の {key}
の 直前 に 半角の空白文字 を 追加 するなどの方法で、自由 に 表示 を自分好みに 調整 して下さい。
ai_match(ai=[ai1, ai2], match_num=300)
実行結果(実行結果はランダムなので下記とは異なる場合があります)
count
o 232 x 51 draw 17
o 154 x 139 draw 7
ratio
o 77.3% x 17.0% draw 5.7%
o 51.3% x 46.3% draw 2.3%
数字の表示幅の決め方 その 2 (計算で求める)
上記の、数値 の 最大値 を 想定 する方法では、上記の表示結果のように、対戦回数 が 少ない場合 は、多くの空白 が 表示 されてしまうという 欠点 があります。また、書式指定 の 文字幅 は、文字列に変換した際の 文字幅 の 最低値 を 指定 するものなので、想定以上 の 回数の対戦 を行うと、表示 が上下で ずれる場合 が あります。
計算 が 必要 になりますが、match_num
に 代入 された 数値 の 文字列の長さ を計算し、それを 書式設定 の 文字幅 に 設定 することで、下記のプログラムのように、対戦回数 に 応じた長さ で、数字の 表示幅 を 自動的 に 調整 して 表示 することができるようになります。
-
3 行目:組み込み関数
str
を使って、match_num
を 文字列型 のデータに 変換 し、組み込み関数len
を使って、その 文字列の長さ を 計算 し、width
に代入する -
4 行目:f 文字列 を使って、回数 の数字の 書式指定 の 文字幅 に
width
を 設定 する。式 を使って 書式指定 を 指定 する方法を忘れた方は、前回の記事を参照すること
1 def ai_match(ai, match_num=10000):
元と同じなので省略
2 # 通算成績の回数と比率の表示
3 width = len(str(match_num))
4 diff_list = [ ("count", count_list, f"{width}d"), ("ratio", ratio_list, ".1%") ]
5 for title, data, format in diff_list:
6 print(title)
7 for i in range(2):
8 for key in order_list:
9 print(f" {key} {data[i][key]:{format}}", end="")
10 print()
行番号のないプログラム
def ai_match(ai, match_num=10000):
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
# それぞれの比率を計算し、ratio_list に代入する
ratio_list = [ {}, {} ]
for i in range(2):
for key in count_list[i]:
ratio_list[i][key] = count_list[i][key] / match_num
# 〇の勝利、×の勝利、引き分けの順番で表示することを表す list
order_list = [Marubatsu.CIRCLE, Marubatsu.CROSS, Marubatsu.DRAW]
# 通算成績の回数と比率の表示
width = len(str(match_num))
diff_list = [ ("count", count_list, f"{width}d"), ("ratio", ratio_list, ".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()
修正箇所
def ai_match(ai, match_num=10000):
元と同じなので省略
# 通算成績の回数と比率の表示
+ width = len(str(match_num))
- diff_list = [ ("count", count_list, "7d"), ("ratio", ratio_list, ".1%") ]
+ diff_list = [ ("count", count_list, f"{width}d"), ("ratio", ratio_list, ".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()
下記のプログラムを実行することで、match_num
に 3 桁 の 300
を指定した際に、回数 の数値が、空白を含め て すべて 3 文字の幅 で 表示 されることが 確認 できます。
ai_match(ai=[ai1, ai2], match_num=300)
実行結果(実行結果はランダムなので下記とは異なる場合があります)
count
o 239 x 48 draw 13
o 147 x 142 draw 11
ratio
o 79.7% x 16.0% draw 4.3%
o 49.0% x 47.3% draw 3.7%
表示の改良その 3(ratio の比率の表示の文字幅を揃える)
上記の表示結果では、ratio
の 比率 の 数字 の 表示 は 上下 で 揃って いますが、x
の 19.7%
が 5 文字、draw
の 5.7%
が 4 文字 になっていることからわかるように、比率 の 数字 も 表示幅 が 異なる場合 があるため、下記のように、上下が揃わない可能性 があります。
ratio
o 50.0% x 5.0% draw 45.0%
o 30.0% x 50.0% draw 20.0%
比率 は、最小値 が 0.0% で、最大値 が 100.0% なので、文字幅 の 最大値 は 常に 100.0%
の 6 文字 です。そのため、下記のプログラムの 4 行目のように、書式指定 で 比率 の 表示幅 を 6 に設定 すれば、上下 で ずれる ことは決して 無くなります。
1 def ai_match(ai, match_num=10000):
元と同じなので省略
2 # 通算成績の回数と比率の表示
3 width = len(str(match_num))
4 diff_list = [ ("count", count_list, f"{width}d"), ("ratio", ratio_list, "6.1%") ]
5 for title, data, format in diff_list:
6 print(title)
7 for i in range(2):
8 for key in order_list:
9 print(f" {key} {data[i][key]:{format}}", end="")
10 print()
行番号のないプログラム
def ai_match(ai, match_num=10000):
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
# それぞれの比率を計算し、ratio_list に代入する
ratio_list = [ {}, {} ]
for i in range(2):
for key in count_list[i]:
ratio_list[i][key] = count_list[i][key] / match_num
# 〇の勝利、×の勝利、引き分けの順番で表示することを表す list
order_list = [Marubatsu.CIRCLE, Marubatsu.CROSS, Marubatsu.DRAW]
# 通算成績の回数と比率の表示
width = len(str(match_num))
diff_list = [ ("count", count_list, f"{width}d"), ("ratio", ratio_list, "6.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()
修正箇所
def ai_match(ai, match_num=10000):
元と同じなので省略
# 通算成績の回数と比率の表示
+ width = len(str(match_num))
- diff_list = [ ("count", count_list, f"{width}d"), ("ratio", ratio_list, ".1%") ]
+ diff_list = [ ("count", count_list, f"{width}d"), ("ratio", ratio_list, "6.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()
下記のプログラムを実行することで、比率 の 数字 の 表示幅 が、空白を含めて すべて 6 文字 になることが確認できます。
ai_match(ai=[ai1, ai2], match_num=300)
実行結果(実行結果はランダムなので下記とは異なる場合があります)
count
o 241 x 44 draw 15
o 144 x 146 draw 10
ratio
o 80.3% x 14.7% draw 5.0%
o 48.0% x 48.7% draw 3.3%
表示の改良その 4(count と ratio で上下を揃える)
上記の実行結果では、count と ratio で x と draw の 表示位置 が 上下 で 揃わない 点が わかりづらい ので、揃える ことにします。その場合は、下記のプログラムのように、count と ratio の 数字 の 表示幅 を、幅の大きい方 に 揃える 必要があります。
-
3 行目:実引数 に記述した式の計算結果の中の 最大値 を計算して返す
max
という組み込み関数を使って、回数の文字幅であるlen(str(match_num))
と 比率の文字幅である6
の 最大値 を 計算 してwidth
に 代入 する -
4、5 行目:f 文字列 を使って、比率 の数字の 書式指定 の 文字幅 に
width
を 設定 する。なお、行の内容が長くなって見づらくなったので 途中 で 改行 した
1 def ai_match(ai, match_num=10000):
元と同じなので省略
2 # 通算成績の回数と比率の表示
3 width = max(len(str(match_num)), 6)
4 diff_list = [ ("count", count_list, f"{width}d"),
5 ("ratio", ratio_list, f"{width}.1%") ]
6 for title, data, format in diff_list:
7 print(title)
8 for i in range(2):
9 for key in order_list:
10 print(f" {key} {data[i][key]:{format}}", end="")
11 print()
行番号のないプログラム
def ai_match(ai, match_num=10000):
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
# それぞれの比率を計算し、ratio_list に代入する
ratio_list = [ {}, {} ]
for i in range(2):
for key in count_list[i]:
ratio_list[i][key] = count_list[i][key] / match_num
# 〇の勝利、×の勝利、引き分けの順番で表示することを表す list
order_list = [Marubatsu.CIRCLE, Marubatsu.CROSS, Marubatsu.DRAW]
# 通算成績の回数と比率の表示
width = max(len(str(match_num)), 7)
diff_list = [ ("count", count_list, 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()
修正箇所
def ai_match(ai, match_num=10000):
元と同じなので省略
# 通算成績の回数と比率の表示
- width = len(str(match_num))
+ width = max(len(str(match_num)), 7)
- diff_list = [ ("count", count_list, f"{width}d"), ("ratio", ratio_list, "6.1%") ]
+ diff_list = [ ("count", count_list, 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()
max
の 実引数 に、反復可能オブジェクト を 記述 すると、下記のプログラムのように、その 要素 の中の 最大値 を計算して返します。
print(max([1, 2, 3])
実行結果
3
なお、max
の 実引数 に 反復可能オブジェクト を 複数記述 することは できません。
max
に 類似 する 組み込み関数 として、最小値 を 計算 する min
があります。
max
と min
についての詳細は、下記のリンク先を参照して下さい。
下記のプログラムを実行することで、count と ratio で、すべての数字 の 上下 の 表示位置 が 揃う ようになることが確認できます。
ai_match(ai=[ai1, ai2], match_num=300)
実行結果(実行結果はランダムなので下記とは異なる場合があります)
count
o 236 x 45 draw 19
o 154 x 136 draw 10
ratio
o 78.7% x 15.0% draw 6.3%
o 51.3% x 45.3% draw 3.3%
表示の改良その 5(ai の名前の表示)
表示結果 の 最初 に、対戦 する AI の 関数の名前 を 表示 すると わかりやすくなる と思いませんか?関数の名前 は、関数オブジェクト の __name__
という 特殊属性 に代入されているので、それを利用することで、AI の関数の名前を表示することができます。
もう一つの改良として、元のプログラムでは、count と ratio の 結果 が 続けて表示 される点が、すこし わかりづらい ので、間で改行を行う ことで 見やすくする ことにします。
下記は、ai_match
をそのように修正したプログラムです。
-
2 行目:AI の関数 が 代入 された
ai[0]
とai[1]
の__name__
属性 を 表示 することで、対戦 する AI の 関数の名前 を 表示 する -
15 行目:count や ratio を 表示 する ブロックの最後 に
print()
を 記述 して 改行 する
1 def ai_match(ai, match_num=10000):
2 print(f"{ai[0].__name__} VS {ai[1].__name__}")
3
4 mb = Marubatsu()
元と同じなので省略
5 for title, data, format in diff_list:
6 print(title, end="")
7 for key in data[0]:
8 print(f" {key:>{width}}", end="")
9 print()
10 for i in range(3):
11 print(f"{item_text_list[i]:5}", end="")
12 for value in data[i].values():
13 print(f" {value:{format}}", end="")
14 print()
15 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
# それぞれの比率を計算し、ratio_list に代入する
ratio_list = [ {}, {} ]
for i in range(2):
for key in count_list[i]:
ratio_list[i][key] = count_list[i][key] / match_num
# 〇の勝利、×の勝利、引き分けの順番で表示することを表す list
order_list = [Marubatsu.CIRCLE, Marubatsu.CROSS, Marubatsu.DRAW]
# 通算成績の回数と比率の表示
width = max(len(str(match_num)), 7)
diff_list = [ ("count", count_list, 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()
修正箇所
def ai_match(ai, match_num=10000):
+ print(f"{ai[0].__name__} VS {ai[1].__name__}")
mb = Marubatsu()
元と同じなので省略
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=[ai1, ai2], match_num=300)
実行結果(実行結果はランダムなので下記とは異なる場合があります)
ai1 VS ai2
count
o 241 x 49 draw 10
o 141 x 150 draw 9
ratio
o 80.3% x 16.3% draw 3.3%
o 47.0% x 50.0% draw 3.0%
今回の記事のまとめ
長くなったので、今回の記事はここまでにしたいと思います。なお、ai_match
の改良は次回の記事で完了する予定です。
今回の記事では、手番を入れ替えた対戦 を行うように ai_match
を改良しました。また、表示 が わかりやすく なるように改良を行いました。
本記事で入力したプログラム
以下のリンクから、本記事で入力して実行した JupyterLab のファイルを見ることができます。
今回の記事では、marubatsu.py は修正していないので、marubatsu_new.py はありません。
以下のリンクは、今回の記事で更新した ai.py です。
次回の記事