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を一から作成する その43 AI の視点からの結果の表示

Last updated at Posted at 2024-01-07

目次と前回の記事

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

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

これまでに作成した AI

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

関数名 アルゴリズム
ai1 左上から順に空いているマスを探し、最初に見つかったマスに着手する
ai2 ランダムなマスに着手する

ai_match の改良 その 6(AI の視点からの結果の表示)

前回の記事から引き続き、ai_match改良 を行います。

ai_matchAI どうし対戦 を行う 目的 は、ある 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 行目winlosedraw のそれぞれの キー に、ai[0] VS ai[1] の、ai[0] から見た通算成績勝数敗数引き分け数代入 する dict
  • 9 ~ 13 行目winlosedraw のそれぞれの キー に、ai[1] VS ai[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_listcount_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_listcount_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_listdata[i].items() に修正し、繰り返しのたび に取り出す キーキーの値keyvalue に代入する
  • 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_ai0ratio_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] などを 記述 しても かまいませんわかりやすい と思った 記述採用 して下さい。

下記のプログラムを実行することで、表の形式表示 されることが 確認 できますが、countratio と、データの表示表示内容上下でずれる という 問題 があります。このような、上下のずれが起きる 原因 について少し考えてみて下さい。

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%

上下のずれの問題の修正

上下のずれ がおきる 原因修正方法 は以下の通りです。

  • countratio と、ox表示幅異なるox表示幅countratio同じ 5 文字 にすることで解決できる
  • winlosedraw表示幅 が、数字の表示幅異なる。これらの 表示幅 を、数字表示幅 である 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()

下記のプログラムを実行すると、winlosedraw と、その 下の数字ずれが解消されていない ことがわかります。その 原因 について少し考えてみて下さい。

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 文字列 では、書式指定揃えを指定しない 場合は、文字列型 のデータは 左揃え で、数値型 のデータは 右揃え揃えに指定 されたことになるからです。従って、下記のプログラムのように、winlosedraw書式指定揃え右揃え指定 することでこの問題を 解決 することができます。

  • 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 では、行の先頭表示 する、countratio の文字列が 偶然同じ 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_ai0ratio_list要素代入 するのが 自然 なので、それらの 2 番要素代入 することにします。

下記の表は、count_list_ai0ratio_listインデックス要素 をまとめたものです。

インデックス 要素
0 順番を 入れ替えない 場合の対戦結果
1 順番を 入れ替えた 場合の対戦結果
2 両方の対戦合わせた 場合の対戦結果

両方の対戦を合わせた回数の計算

両方の対戦合わせた回数計算 は、count_list_ai00 番1 番要素winlosedrawそれぞれ値の合計 なので、下記のプログラムのように、繰り返し処理 を使って 計算 することが できます

  • 1 行目count_list_ai02 番要素 として、空の dict追加 する
  • 2、3 行目count_list_ai00 番要素 に代入された 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_list2 番 の要素は、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 の行表示 されるようになることが 確認 できますが、totalwin数字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 の場合 の比率を計算する i2場合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要素winlosedrawキーの値合計 であることに 気が付く ことができれば、以前の記事で紹介した、反復可能オブジェクト要素の合計計算 する組み込み関数 sum を使って、上記のプログラムの処理を、下記のプログラムのように 簡潔に記述 することができます。

  • 3 行目count_list_ai0i要素 に代入された dictすべてキーの値合計sumvalues メソッドを使って 計算 した値で、割り算 するように 修正 する
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%

ai1ai2 の対戦

AI どうし対戦 を行い、結果を表示 する 関数完成 したので、これまでに作成した ai1ai2対戦 を行うことにします。以前の記事 で説明したように、基準 となる ai2 に対し、ai1 VS ai2ai2 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

表から、ai1ai2対して強い AI であることが わかります。ただし、以前の記事で説明したように、基準 となる ai2 よりも強い からと言って、その AI が 他の ai2 より弱い AI より強い とは 限らない 点に 注意 して下さい。ai1実際にそうである ように、ai2相性が良い せいで、ai1ai2大きく勝ち越す ような 場合がある からです。

また、ai2 VS ai2合計勝率と敗率約 43%ほぼ同じ であるという、同じ AI どうし対戦 した場合の 当然の結果 が得られたことが 確認 できます。

以後は、新しい AI を作成 する たび に、この 表を更新 していくことで、作成した AI強さの評価行います

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

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

今回の記事では、marubatsu.py は修正していないので、marubatsu_new.py はありません。

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

次回の記事

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?