目次と前回の記事
これまでに作成したモジュール
以下のリンクから、これまでに作成したモジュールを見ることができます。
ルールベースの AI の一覧
ルールベースの AI の一覧については、下記の記事を参照して下さい。
循環インポートのエラーの意味とその対処方法
前回の記事で 作成 したプログラムを marubatsu.py に 記述 し、下記 のプログラムを実行すると 実行結果 のような エラーが発生 します。今回の記事では、最初にこの エラーの意味 と 対処方法 について 説明 します。
%matplotlib widget
from marubatsu import Marubatsu
mb = Marubatsu()
mb.play(ai=[None, None], gui=True)
実行結果
略
File c:\Users\ys\ai\marubatsu\075\ai.py:3
1 # 3.7 ~ 3.9 の Python のバージョンでエラーが発生しないようにするためのインポート
2 from __future__ import annotations
----> 3 from marubatsu import Marubatsu, Markpat
4 from random import choice
5 from collections import defaultdict
ImportError: cannot import name 'Marubatsu' from partially initialized module 'marubatsu' (most likely due to a circular import) (c:\Users\ys\ai\marubatsu\075\marubatsu.py)
上記のエラーメッセージは、以下のような意味を持ちます。
-
ImportError
モジュールのインポート(import)に関するエラー -
cannot import name 'Marubatsu' from partially initialized module 'marubatsu' (most likely due to a circular import)
部分的に(partially)初期化された(initialized)marubatsu というモジュール(module)から(from)Marubatsu という名前(name)をインポート(import)できない(can not)。循環インポート(circular import)が最も可能性が高い(most likely)原因(due to)である
循環インポートとは何か
上記 の エラーメッセージ から、エラーの原因 が、循環インポート(circular import)である 可能性が最も高い ことが わかります。
循環インポート がどのようなものであるかを説明するために、先程実行した プログラム の 処理の流れ を、順を追って説明 します。
marubatsu モジュールのインポート
下記 の 先程のプログラム は、2 行目 で marubatsu モジュールから Marubatsu
という クラス を インポート します。
%matplotlib widget
from marubatsu import Marubatsu
以下略
以前の記事で説明したように、モジュール を インポート する際には、その モジュール のファイルの中に 記述 された プログラムが すべて 実行 されます。それは、import marubatsu
のように、モジュールそのもの を インポート する場合でも、from marubatsu import Marubatsu
のように、モジュールの中 で 定義 された 関数 や クラス などを インポート する場合でも 変わりません。そのため、上記 のプログラムの 2 行目 を 実行 すると、marubatsu.py の中に 記述 された プログラム が すべて実行 されます。
python では、モジュール を インポート する処理が行われると、関数 や メソッド などの 呼び出し と 同様 に、モジュール に 記述 された プログラム を すべて実行 し、その 処理 が 完了してから プログラムの 続きが実行 されます。従って、上記 の marubatsu.py の 2 行目 の from marubatsu import Marubatsu
が 実行 されると marubatsu.py に 記述 された プログラムが実行 され、その 処理が完了 してから、3 行目以降 の プログラムが 実行 されます。
marubatsu モジュールの実行
下記は、marubatsu.py の 先頭に記述 された、他のモジュール を インポート するプログラムで、marubatsu.py の中 で、ai モジュールを インポート していることが わかります。
# 3.7 ~ 3.9 の Python のバージョンでエラーが発生しないようにするためのインポート
from __future__ import annotations
略
import ai as ai_module
以下略
先程と同様に、上記 の marubatsu.py の 4 行目 の import ai as ai_module
が 実行 されると ai.py に 記述 された プログラムが実行 され、その 処理が完了 してから、marubatsu.py の 5 行目以降 の プログラムが 実行 されます。
ai モジュールの実行
下記は、ai.py の 先頭に記述 された、他のモジュール を インポート するプログラムで、ai.py の中 で、marubatsu モジュールを インポート していることが わかります。
# 3.7 ~ 3.9 の Python のバージョンでエラーが発生しないようにするためのインポート
from __future__ import annotations
from marubatsu import Marubatsu, Markpat
以下略
以前の記事で、Python では、インポートしたモジュール を もう一度インポート しようとした場合は、その モジュールのプログラム が 実行されることはない と説明しましたが、それは、インポート した モジュールのプログラム が 完全に実行された場合 です。
最初に実行 した from marubatsu import Marubatsu
によって 実行 された marubatsu.py のプログラムは、実行 の 途中 で ai モジュールを インポート する 処理を行った ため、上記 のプログラムの 3 行目 の from marubatsu import Marubatsu, Markpat
を 実行する時点 では 処理 が 完了していません。そのため、上記の 3 行目 を 実行 すると、marubatsu モジュールを インポート するために、marubatsu.py のプログラムが 実行する必要 が 生じます。
インポートの無限の繰り返し
実際 には、先程 の エラーが発生 するので、marubatsu モジュールが もう一度実行 されることは ありません が、仮に 2 度目 の marubatsu モジュールが 実行された場合 に どのようなことが起きるか について 説明 します。
2 度目 の marubatsu モジュールが 実行 された場合は、1 度目 と 同様 に、import ai as ai_module
によって ai モジュールを インポート する 必要 が 生じます が、1 度目 の ai モジュールの ai.py の 実行処理 も、途中 で marubatsu モジュールを インポートする処理 が 行われた ため 完了していません。そのため、ai モジュールを インポート するために、もう一度 の ai.py を 実行 する 必要 が 生じます。以後 は 同様の処理 が 繰り返し 行われます。
モジュール A から モジュール B を インポートする処理 を 「A → B」のように 表記 した場合、上記の処理 は、 「marubatsu → ai → marubatsu → ai → marubatsu → ・・・」のように、インポートの処理 が 循環 するため、無限 に 繰り返される ことになります。これが 循環インポート で、Python では 循環インポート が起きた場合は ImportError を 発生 させて、プログラム を 強制的に終了 するという 仕組み になっています。
循環インポートのような、参照が循環 する 状況 のことを 循環参照 と呼びます。
循環インポート は、以下 のような場合に 発生 する。
モジュール A が 別のモジュール B を インポートする という 処理 を「A → B」と 表記 した場合に、「A → B →・・・→ A」のように、インポート した モジュール を 辿っていく と、元のモジュール に 戻る という 循環が存在する。
Python では、モジュール を インポートする際 に行われる 処理 を、モジュール を 初期化する(initalize)と呼びます。先程 の プログラム を 実行 した際に 表示 された partially initialized module という エラーメッセージ は、モジュール が 部分的(partially)に 初期化 されている(initialized)、すなわち、モジュール の インポート の 処理 が 完了していない という 意味 を表します。
このノート は、細かい話 なので、意味がわからない 場合は 無視して下さい。
循環インポート が あっても、インポート した モジュール の 名前空間 の 名前 を 利用しなければ、エラー は 発生しません。例えば、下記 のような、お互い を インポート する test.py と test2.py があった場合に、import test
で test モジュールを インポート しても、それらの モジュールの中 で、インポートしたモジュール の 名前空間 の 名前 を 参照していない ので エラー は 発生しません。
import test2
import test
import test
実行結果(エラーは発生せず、何も表示されない)
一方、下記 の お互い を インポート する test3.py と test4.py は、その中で インポート した モジュール の 名前空間 の 名前を参照する ので、import test3
を 実行 すると 循環インポート を表す ImportError の エラーが発生 します。このようなことが起きるのは、モジュール内 で 定義 された 名前を利用 するための 名前空間 が 完成 するのは、モジュール の 実行の処理 が 完了した時点 だからです。
import test4
a = 1
print(test4.a)
import test3
a = 2
print(test3.a)
import test3
実行結果
略
File c:\Users\ys\ai\marubatsu\075\test4.py:3
1 import test3
2 a = 2
----> 3 print(test3.a)
AttributeError: partially initialized module 'test3' has no attribute 'a' (most likely due to a circular import)
循環インポートの解決方法
循環インポート の エラーを修正 するためには、循環インポート が 起きないよう に プログラム を 記述する必要 が あります。今回の場合は、 「marubatsu → ai → marubatsu → ・・・」という 循環インポート が 発生 してるので、以下 の いずれかの修正 を行うことで、循環インポート が 発生しなくなります。
- marubatsu モジュールから ai モジュールを インポートしない
- ai モジュールから marubatsu モジュールを インポートしない
ai モジュール内のプログラムは、marubatsu モジュールで 定義 された Marubatsu
クラスを 利用する必要 が ある ので、どうしても marubatsu モジュールを インポート する 必要 が あります。そのため、循環インポート を 解消 するためには、marubatsu モジュールから、ai モジュールを インポートしない ようにする 必要 が あります。
marubatsu モジュール内で ai モジュールを 利用 しているのは、前回の記事で ai モジュール内で 定義 された AI を Dropdown で 選択できる ように 修正 した play
メソッドです。下記 は、play
メソッド内で ai モジュールを 利用する処理 の部分のプログラムです。具体的には、5 行目 の ai_module
が インポート した ai モジュールを 表します。
# Dropdown の作成の際に必要となる、AI のリストを作成する
ai_list = []
for i in range(1, 15):
ai_name = f"ai{i}s"
ai_list.append((ai_name, getattr(ai_module, ai_name)))
上記 の 処理 を play
メソッドから 削除 することで、marubatsu モジュール内で ai モジュールを インポート する 必要 が なくなります。ただし、この処理 を play
メソッドから 削除 してしまうと、Dropdown に 登録 する AI の一覧 の 情報 が なくなってしまう ので、その代わりに 下記 のプログラムのように、play
メソッドの 仮引数 に ai_list
を追加 し、play
メソッドを 実行する際 に、Dropdown に 登録 する AI の一覧 を 実引数 で記述して 指定する ように 修正 することにします。その際に、これまでのプログラムとの 互換性を考慮 して、ai_list
は、空の list を デフォルト値 とした デフォルト引数 とします。
-
1 行目:仮引数 に、デフォルト値 に 空の list を 設定 した
ai_list
を 追加 する -
ai_list
を 作成する処理 を 削除 する
1 def play(self, ai, ai_list=[], params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
2 # seed が None でない場合は、seed を乱数の種として設定する
3 if seed is not None:
4 random.seed(seed)
5
6 # この下にあった、ai_list を作成する処理を削除する
元と同じなので省略
行番号のないプログラム
def play(self, ai, ai_list=[], params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
# seed が None でない場合は、seed を乱数の種として設定する
if seed is not None:
random.seed(seed)
# 〇 と × の Dropdown を作成する
dropdown_circle = widgets.Dropdown(
options=ai_list,
description="〇",
layout=widgets.Layout(width="100px"),
style={"description_width": "20px"},
)
dropdown_cross = widgets.Dropdown(
options=ai_list,
description="×",
layout=widgets.Layout(width="100px"),
style={"description_width": "20px"},
)
# リセットボタンを作成する
button = widgets.Button(
description="リセット",
layout=widgets.Layout(width="100px"),
)
# 〇 と × の dropdown と リセットボタンを横に配置した HBox を作成し、表示する
hbox = widgets.HBox([dropdown_circle, dropdown_cross, button])
display(hbox)
# リセットボタンのイベントハンドラを定義する
def on_button_clicked(b):
self.restart()
self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
# イベントハンドラをリセットボタンに結びつける
button.on_click(on_button_clicked)
# gui が True の場合に、ゲーム盤を描画する画像を作成し、イベントハンドラに結びつける
if gui:
fig, ax = plt.subplots(figsize=[size, size])
fig.canvas.toolbar_visible = False
fig.canvas.header_visible = False
fig.canvas.footer_visible = False
fig.canvas.resizable = False
# ローカル関数としてイベントハンドラを定義する
def on_mouse_down(event):
# Axes の上でマウスを押していた場合のみ処理を行う
if event.inaxes and self.status == Marubatsu.PLAYING:
x = math.floor(event.xdata)
y = math.floor(event.ydata)
self.move(x, y)
self.draw_board(ax)
# 次の手番の処理を行うメソッドを呼び出す
self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
# fig の画像にマウスを押した際のイベントハンドラを結び付ける
fig.canvas.mpl_connect("button_press_event", on_mouse_down)
self.restart()
return self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
修正箇所
- def play(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
+ def play(self, ai, ai_list=[], params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
# seed が None でない場合は、seed を乱数の種として設定する
if seed is not None:
random.seed(seed)
# Dropdown の作成の際に必要となる、AI のリストを作成する
- ai_list = []
- for i in range(1, 15):
- ai_name = f"ai{i}s"
- ai_list.append((ai_name, getattr(ai_module, ai_name)))
元と同じなので省略
marubatsu モジュールを インポート すると エラーが発生 するため、これまで のように、上記 のプログラムを 記述した後 で、Marubatsu.play = play
を 実行 するという方法で play
メソッドを 修正 することは できません。そこで、今回の記事では、marubatsu.py から ai モジュールを インポート する import ai as ai_module
を 削除 し、play
メソッドを 上記 のように 修正 した marubatsu_correct.py という ファイルを保存 することにします。次回の記事 からは、これまでと同様 に marubatsu.py というファイルに 保存 します。
下記 は、そのファイル を 保存後 に、ai_list
を 作成する処理 を 記述 し、play
メソッドの 実引数に記述 して 実行 するプログラムです。実行結果 から、正しく プログラムが 動作する ことが 確認できます。
-
1 行目:marubatsu_correct から
Marubatsu
クラスを インポート する -
2 行目:ai モジュールを インポート する。なお、
play
メソッドの 外 でai_list
を作成 するため、ai モジュールと、play
メソッドの 仮引数ai
の 名前 が かぶらなくなった ので、import ai as ai_module
のように 名前を変えてインポート する 必要はない -
5 ~ 8 行目:
play
メソッド内で 記述 していたのと 同じ方法 でai_list
を 作成 する -
11 行目:
play
メソッドの 実引数 にai_list=ai_list
を 記述 して 呼び出す
1 from marubatsu_correct import Marubatsu
2 import ai
3
4 # Dropdown の作成の際に必要となる、AI のリストを作成する
5 ai_list = []
6 for i in range(1, 15):
7 ai_name = f"ai{i}s"
8 ai_list.append((ai_name, getattr(ai, ai_name)))
9
10 mb = Marubatsu()
11 mb.play(ai=[None, None], ai_list=ai_list, gui=True)
行番号のないプログラム
from marubatsu_correct import Marubatsu
import ai
# Dropdown の作成の際に必要となる、AI のリストを作成する
ai_list = []
for i in range(1, 15):
ai_name = f"ai{i}s"
ai_list.append((ai_name, getattr(ai, ai_name)))
mb = Marubatsu()
mb.play(ai=[None, None], ai_list=ai_list, gui=True)
実行結果(下図は、画像なので操作することはできません)
play
メソッドの改良その 1(ai
の内容の自動登録)
前回の記事では play
メソッドを、ai1s
~ ai14s
の 14 種類 の AI を 選択できる ように 実装 しましたが、一方で それ以外の AI を 選択できない という 欠点 があります。
それに対し、先程修正 した play
メソッドは、好きな AI のリスト を 作成 して 実引数に記述 することで、任意の AI を 選択できる ようになったという 利点 があります。しかし、その一方 で、下記 のプログラムのように ai_list
に 対応 する 実引数 を 記述せず に play
メソッドを呼び出すと、Dropdown の options
属性に 空の list が 代入 されるため、下図左 の 実行結果 のように、Dropdown に 何も表示されなく なります。また、Dropdown の上 で マウスを押す と、下図右 のように、選択できる項目がない メニューが 表示 されます。
mb.play(ai=[None, None], gui=True)
実行結果(下図は、画像なので操作することはできません)
Dropdown の 項目 に 何も表示されない のは 変 なので、仮引数 ai
に 代入 された 内容 を Dropdown に 自動的に登録 するように play
メソッドを 修正 することにします。
人間の登録
先程 の 下記 のプログラムは、人間どうし の 対戦 を行うものですが、前回の記事 で 作成 した Dropdown には AI のみ が 登録 されていました。
mb.play(ai=[None, None], gui=True)
人間を表す Dropdown の項目の設定
そのため、Dropdown に 人間を表す項目 を どのように登録するか について 決める必要 が あります。Dropdown の項目 には、メニューに 表示する文字 を表す ラベル と、項目の値 を 決める必要 が あります。本記事では ラベル は "人間" とすることにします。他の文字が良いと思った人は自由に変更して下さい。項目の値 は、play
メソッドの 仮引数 ai
では 人間 の担当を None
と 表していた ので、そのまま None
を設定 することにします。
実は、Dropdown の 項目の値 に None
を 設定するべき では ありません が、筆者はそのことに気づかずに最初は None
を設定し、後からおかしいことに気づきました。Dropdown の 仕組み を 理解する 上で、この 間違いの体験 は 役に立つ と 思いました ので、あえて最初 は Dropdown の 項目の値 に None
を 設定 しました。
play
メソッドの修正
下記 は、仮引数 ai
の 要素 に 人間を表す None
が 代入 されていた場合に、ai_list
に 人間を表す項目 を 追加 するように play
メソッドを 修正 したプログラムです。
-
11 ~ 13 行目:繰り返し処理 によって、
ai
の 各要素 にNone
が代入 されている場合に、ai_list
に 人間の項目 を 追加 する
1 import matplotlib.pyplot as plt
2 import ipywidgets as widgets
3 import math
4
5 def play(self, ai, ai_list=[], params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
6 # seed が None でない場合は、seed を乱数の種として設定する
7 if seed is not None:
8 random.seed(seed)
9
10 # ai に代入されている内容を ai_list に追加する
11 for i in range(2):
12 if ai[i] is None:
13 ai_list.append(("人間", None))
元と同じなので省略
14
15 Marubatsu.play = play
行番号のないプログラム
import matplotlib.pyplot as plt
import ipywidgets as widgets
import math
def play(self, ai, ai_list=[], params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
# seed が None でない場合は、seed を乱数の種として設定する
if seed is not None:
random.seed(seed)
# ai に代入されている内容を ai_list に追加する
for i in range(2):
if ai[i] is None:
ai_list.append(("人間", None))
# 〇 と × の Dropdown を作成する
dropdown_circle = widgets.Dropdown(
options=ai_list,
description="〇",
layout=widgets.Layout(width="100px"),
style={"description_width": "20px"},
)
dropdown_cross = widgets.Dropdown(
options=ai_list,
description="×",
layout=widgets.Layout(width="100px"),
style={"description_width": "20px"},
)
# リセットボタンを作成する
button = widgets.Button(
description="リセット",
layout=widgets.Layout(width="100px"),
)
# 〇 と × の dropdown と リセットボタンを横に配置した HBox を作成し、表示する
hbox = widgets.HBox([dropdown_circle, dropdown_cross, button])
display(hbox)
# リセットボタンのイベントハンドラを定義する
def on_button_clicked(b):
self.restart()
self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
# イベントハンドラをリセットボタンに結びつける
button.on_click(on_button_clicked)
# gui が True の場合に、ゲーム盤を描画する画像を作成し、イベントハンドラに結びつける
if gui:
fig, ax = plt.subplots(figsize=[size, size])
fig.canvas.toolbar_visible = False
fig.canvas.header_visible = False
fig.canvas.footer_visible = False
fig.canvas.resizable = False
# ローカル関数としてイベントハンドラを定義する
def on_mouse_down(event):
# Axes の上でマウスを押していた場合のみ処理を行う
if event.inaxes and self.status == Marubatsu.PLAYING:
x = math.floor(event.xdata)
y = math.floor(event.ydata)
self.move(x, y)
self.draw_board(ax)
# 次の手番の処理を行うメソッドを呼び出す
self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
# fig の画像にマウスを押した際のイベントハンドラを結び付ける
fig.canvas.mpl_connect("button_press_event", on_mouse_down)
self.restart()
return self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
Marubatsu.play = play
修正箇所
import matplotlib.pyplot as plt
import ipywidgets as widgets
import math
def play(self, ai, ai_list=[], params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
# seed が None でない場合は、seed を乱数の種として設定する
if seed is not None:
random.seed(seed)
# ai に代入されている内容を ai_list に追加する
+ for i in range(2):
+ if ai[i] is None:
+ ai_list.append(("人間", None))
元と同じなので省略
Marubatsu.play = play
上記 の 修正後 に、下記 のプログラムで play
メソッドを 実行 すると、実行結果 の 下図左 のように、Dropdown に 人間が表示 されるようになります。しかし、Dropdown の上 で マウスを押す と、下図右 のように 人間 が 2 つ登録 されるという 問題が発生 します。このようなことがおきる原因について少し考えてみて下さい。
mb.play(ai=[None, None], gui=True);
実行結果(下図は、画像なので操作することはできません)
項目の重複登録の原因と修正
先程 のプログラムは、繰り返し処理 によって、ai
の 各要素 が None
の場合に 人間の項目 を 登録 する処理を行います。そのため、人間 VS 人間 の場合は、2 回分 の 人間の項目 が 登録 されてしまいます。この 問題を解決 するためには、ai_list
に既に 登録済の項目 を 重複 して 登録しない ようにする 必要 が あります。
list の 要素の中 に、特定の値 が 含まれているか どうかは、in
という 演算子 で 判定 することが できます が、ai_list
の 要素 に 人間を表す項目 が 含まれているか どうかを None in ai_list
で 判定 することは残念ながら できません。その 理由 は、ai_list
の 要素 が、(ラベル, 項目の値)
という tuple だからです。ai_list
の中から、項目の値 だけを 取り出した list を 作成 することで、in
演算子を使って 判定できる ようになります。
下記は、play
メソッドで 人間の項目 を 重複 して 登録しないよう にするプログラムです。
-
7 行目:list 内包表記 を使って、
ai_list
の 各要素 から 項目の値 を 取り出した list を 作成 し、ai_values
に 代入 する -
10 行目:条件式 に「
None
がai_values
に 登録されていない」という 条件を追加 する -
12 行目:
ai_values
に、None
を 追加 する
ai_list
と ai_values
は 異なる list なので、12 行目 で ai_values
に None
を 追加 する 処理 を 忘れないよう に 注意 して下さい。
1 def play(self, ai, ai_list=[], params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
2 # seed が None でない場合は、seed を乱数の種として設定する
3 if seed is not None:
4 random.seed(seed)
5
6 # ai_list から、項目だけを取り出した list を作成する
7 ai_values = [value for label, value in ai_list]
8 # ai に代入されている内容を ai_list に追加する
9 for i in range(2):
10 if ai[i] is None and None not in ai_values:
11 ai_list.append(("人間", None))
12 ai_values.append(None)
元と同じなので省略
13
14 Marubatsu.play = play
行番号のないプログラム
def play(self, ai, ai_list=[], params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
# seed が None でない場合は、seed を乱数の種として設定する
if seed is not None:
random.seed(seed)
# ai_list から、項目だけを取り出した list を作成する
ai_values = [value for label, value in ai_list]
# ai に代入されている内容を ai_list に追加する
for i in range(2):
if ai[i] is None and None not in ai_values:
ai_list.append(("人間", None))
ai_values.append(None)
# 〇 と × の Dropdown を作成する
dropdown_circle = widgets.Dropdown(
options=ai_list,
description="〇",
layout=widgets.Layout(width="100px"),
style={"description_width": "20px"},
)
dropdown_cross = widgets.Dropdown(
options=ai_list,
description="×",
layout=widgets.Layout(width="100px"),
style={"description_width": "20px"},
)
# リセットボタンを作成する
button = widgets.Button(
description="リセット",
layout=widgets.Layout(width="100px"),
)
# 〇 と × の dropdown と リセットボタンを横に配置した HBox を作成し、表示する
hbox = widgets.HBox([dropdown_circle, dropdown_cross, button])
display(hbox)
# リセットボタンのイベントハンドラを定義する
def on_button_clicked(b):
self.restart()
self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
# イベントハンドラをリセットボタンに結びつける
button.on_click(on_button_clicked)
# gui が True の場合に、ゲーム盤を描画する画像を作成し、イベントハンドラに結びつける
if gui:
fig, ax = plt.subplots(figsize=[size, size])
fig.canvas.toolbar_visible = False
fig.canvas.header_visible = False
fig.canvas.footer_visible = False
fig.canvas.resizable = False
# ローカル関数としてイベントハンドラを定義する
def on_mouse_down(event):
# Axes の上でマウスを押していた場合のみ処理を行う
if event.inaxes and self.status == Marubatsu.PLAYING:
x = math.floor(event.xdata)
y = math.floor(event.ydata)
self.move(x, y)
self.draw_board(ax)
# 次の手番の処理を行うメソッドを呼び出す
self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
# fig の画像にマウスを押した際のイベントハンドラを結び付ける
fig.canvas.mpl_connect("button_press_event", on_mouse_down)
self.restart()
return self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
Marubatsu.play = play
修正箇所
def play(self, ai, ai_list=[], params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
# seed が None でない場合は、seed を乱数の種として設定する
if seed is not None:
random.seed(seed)
# ai_list から、項目だけを取り出した list を作成する
+ ai_values = [value for label, value in ai_list]
# ai に代入されている内容を ai_list に追加する
for i in range(2):
- if ai[i] is None:
+ if ai[i] is None and None not in ai_values:
ai_list.append(("人間", None))
ai_values.append(None)
元と同じなので省略
Marubatsu.play = play
上記 の 修正後 に、下記 のプログラムで play
メソッドを 実行 し、Dropdown の上 で マウスを押す と、下図右 のように 人間 が 1 つだけ表示 されることが 確認 できます。
mb.play(ai=[None, None], gui=True);
実行結果(下図は、画像なので操作することはできません)
AI の登録
次は、AI を 自動的に登録 することにします。AI の ラベル は、AI の 関数名 を 設定 すれば良く、関数の名前 は 以前の記事で説明したように、__name__
という 特殊属性 に 代入 されています。従って、play
メソッドの 該当する部分 を 下記 のように 修正 します。
-
6 行目:人間でなく、
ai[i]
がai_values
に 登録されていない ことを 判定 する -
7、8 行目:
ai_list
とai_values
に AI のデータ を 登録 する
1 # ai に代入されている内容を ai_list に追加する
2 for i in range(2):
3 if ai[i] is None and None not in ai_values:
4 ai_list.append(("人間", None))
5 ai_values.append(None)
6 elif ai[i] is not None and ai[i] not in ai_values:
7 ai_list.append((ai[i].__name__, ai[i]))
8 ai_values.append(ai[i])
上記 のプログラムは、3 ~ 5 行目 と 6 ~ 8 行目 の 重複する処理 を まとめる ことが できます。どのようにすれば良いかについて少し考えてみて下さい。
重複する処理の統合
上記 のプログラムの 3 行目 の 条件式 が True
になる 場合 は、ai[i]
の値が None
になります。従って、上記 のプログラムは、下記 のように 記述 することができます。
-
3 ~ 5 行目:
None
をai[i]
に 修正 する
1 # ai に代入されている内容を ai_list に追加する
2 for i in range(2):
3 if ai[i] is None and ai[i] not in ai_values:
4 ai_list.append(("人間", ai[i]))
5 ai_values.append(ai[i])
6 elif ai[i] is not None and ai[i] not in ai_values:
7 ai_list.append((ai[i].__name__, ai[i]))
8 ai_values.append(ai[i])
行番号のないプログラム
# ai に代入されている内容を ai_list に追加する
for i in range(2):
if ai[i] is None and ai[i] not in ai_values:
ai_list.append(("人間", ai[i]))
ai_values.append(ai[i])
elif ai[i] is not None and ai[i] not in ai_values:
ai_list.append((ai[i].__name__, ai[i]))
ai_values.append(ai[i])
修正箇所
# ai に代入されている内容を ai_list に追加する
for i in range(2):
- if ai[i] is None and None not in ai_values:
+ if ai[i] is None and ai[i] not in ai_values:
- ai_list.append(("人間", None))
+ ai_list.append(("人間", ai[i]))
- ai_values.append(None)
+ ai_values.append(ai[i])
elif ai[i] is not None and ai[i] not in ai_values:
ai_list.append((ai[i].__name__, ai[i]))
ai_values.append(ai[i])
上記 の 3 行目 と 6 行目 の 条件式 は、いずれ も and
の後ろ に ai[i] not in ai_values
が 記述 されているので、どちらのブロック も ai[i] not in ai_values
が True
になる 場合のみ実行 されます。従って、上記 のプログラムは、下記 のように 記述 できます。
-
3 行目:
ai[i]
がai_values
に 含まれているか どうかを 判定 する - 4 ~ 6 行目:人間が担当 する場合に、人間の項目 を 登録 する
- 7 ~ 9 行目:AI が担当 する場合に、AI の項目 を 登録 する
1 # ai に代入されている内容を ai_list に追加する
2 for i in range(2):
3 if ai[i] not in ai_values:
4 if ai[i] is None:
5 ai_list.append(("人間", None))
6 ai_values.append(ai[i])
7 elif ai[i] is not None:
8 ai_list.append((ai[i].__name__, ai[i]))
9 ai_values.append(ai[i])
行番号のないプログラム
# ai に代入されている内容を ai_list に追加する
for i in range(2):
if ai[i] not in ai_values:
if ai[i] is None:
ai_list.append(("人間", None))
ai_values.append(ai[i])
elif ai[i] is not None:
ai_list.append((ai[i].__name__, ai[i]))
ai_values.append(ai[i])
修正箇所
# ai に代入されている内容を ai_list に追加する
for i in range(2):
- if ai[i] is None and ai[i] not in ai_values:
- ai_list.append(("人間", ai[i]))
- ai_values.append(ai[i])
+ if ai[i] not in ai_values:
+ if ai[i] is None:
+ ai_list.append(("人間", None))
+ ai_values.append(ai[i])
- elif ai[i] is not None and ai[i] not in ai_values:
- ai_list.append((ai[i].__name__, ai[i]))
- ai_values.append(ai[i])
+ elif ai[i] is not None:
+ ai_list.append((ai[i].__name__, ai[i]))
+ ai_values.append(ai[i])
上記 の 7 行目 の 条件式 の ai[i] is not None
は、4 行目 の ai[i] is None
が False
の場合に 実行 されるので、その場合 は 必ず True
になります。従って、上記 のプログラムは 下記 のように 記述 できます。
-
7 行目:
else
に 修正 する
1 # ai に代入されている内容を ai_list に追加する
2 for i in range(2):
3 if ai[i] not in ai_values:
4 if ai[i] is None:
5 ai_list.append(("人間", ai[i]))
6 ai_values.append(ai[i])
7 else:
8 ai_list.append((ai[i].__name__, ai[i]))
9 ai_values.append(ai[i])
行番号のないプログラム
# ai に代入されている内容を ai_list に追加する
for i in range(2):
if ai[i] not in ai_values:
if ai[i] is None:
ai_list.append(("人間", ai[i]))
ai_values.append(ai[i])
else:
ai_list.append((ai[i].__name__, ai[i]))
ai_values.append(ai[i])
修正箇所
# ai に代入されている内容を ai_list に追加する
for i in range(2):
if ai[i] not in ai_values:
if ai[i] is None:
ai_list.append(("人間", ai[i]))
ai_values.append(ai[i])
- elif ai[i] is not None:
+ else:
ai_list.append((ai[i].__name__, ai[i]))
ai_values.append(ai[i])
上記 の 5、6 行目 の ブロック と、8、9 行目 の ブロック の処理で 異なる のは、項目 の ラベルだけ なので、上記 のプログラムは、下記 のように 記述 できます。
- 5、7 行目:条件式 の ブロック内 で行う 処理 を、ラベルの計算だけ にする
-
8、9 行目:
ai_list
とai_values
の 要素の追加 を、ブロックの外 で 行う ようにする
1 # ai に代入されている内容を ai_list に追加する
2 for i in range(2):
3 if ai[i] not in ai_values:
4 if ai[i] is None:
5 label = "人間"
6 else:
7 label = ai[i].__name__
8 ai_list.append((label, ai[i]))
9 ai_values.append(ai[i])
行番号のないプログラム
# ai に代入されている内容を ai_list に追加する
for i in range(2):
if ai[i] not in ai_values:
if ai[i] is None:
label = "人間"
else:
label = ai[i].__name__
ai_list.append((label, ai[i]))
ai_values.append(ai[i])
修正箇所
# ai に代入されている内容を ai_list に追加する
for i in range(2):
if ai[i] not in ai_values:
+ if ai[i] is None:
- ai_list.append(("人間", ai[i]))
- ai_values.append(ai[i])
+ label = "人間"
+ else:
- ai_list.append((ai[i].__name__, ai[i]))
- ai_values.append(ai[i])
+ label = ai[i].__name__
+ ai_list.append((label, ai[i]))
+ ai_values.append(ai[i])
上記が 重複する処理 を まとめた プログラムです。下記 の まとめる前 のプログラムと 比べる と、行数 が 1 行増えています が、「条件式 が 簡潔 になっている」、「重複する処理 が まとめられている」という点で 元のプログラム より わかりやすく なっています。
1 # ai に代入されている内容を ai_list に追加する
2 for i in range(2):
3 if ai[i] is None and None not in ai_values:
4 ai_list.append(("人間", None))
5 ai_values.append(None)
6 elif ai[i] is not None and ai[i] not in ai_values:
7 ai_list.append((ai[i].__name__, ai[i]))
8 ai_values.append(ai[i])
play
メソッドの修正
下記 は、play
メソッドを 修正 したプログラムです。
def play(self, ai, ai_list=[], params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
# seed が None でない場合は、seed を乱数の種として設定する
if seed is not None:
random.seed(seed)
# ai_list から、項目だけを取り出した list を作成する
ai_values = [value for label, value in ai_list]
# ai に代入されている内容を ai_list に追加する
for i in range(2):
# ai[i] が ai_values に登録済かどうかを判定する
if ai[i] not in ai_values:
# ラベルを計算する
if ai[i] is None:
label = "人間"
else:
label = ai[i].__name__
# 項目を登録する
ai_list.append((label, ai[i]))
ai_values.append(ai[i])
元と同じなので省略
Marubatsu.play = play
プログラム全体
def play(self, ai, ai_list=[], params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
# seed が None でない場合は、seed を乱数の種として設定する
if seed is not None:
random.seed(seed)
# ai_list から、項目だけを取り出した list を作成する
ai_values = [value for label, value in ai_list]
# ai に代入されている内容を ai_list に追加する
for i in range(2):
# ai[i] が ai_values に登録済かどうかを判定する
if ai[i] not in ai_values:
# ラベルを計算する
if ai[i] is None:
label = "人間"
else:
label = ai[i].__name__
# 項目を登録する
ai_list.append((label, ai[i]))
ai_values.append(ai[i])
# 〇 と × の Dropdown を作成する
dropdown_circle = widgets.Dropdown(
options=ai_list,
description="〇",
layout=widgets.Layout(width="100px"),
style={"description_width": "20px"},
)
dropdown_cross = widgets.Dropdown(
options=ai_list,
description="×",
layout=widgets.Layout(width="100px"),
style={"description_width": "20px"},
)
# リセットボタンを作成する
button = widgets.Button(
description="リセット",
layout=widgets.Layout(width="100px"),
)
# 〇 と × の dropdown と リセットボタンを横に配置した HBox を作成し、表示する
hbox = widgets.HBox([dropdown_circle, dropdown_cross, button])
display(hbox)
# リセットボタンのイベントハンドラを定義する
def on_button_clicked(b):
self.restart()
self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
# イベントハンドラをリセットボタンに結びつける
button.on_click(on_button_clicked)
# gui が True の場合に、ゲーム盤を描画する画像を作成し、イベントハンドラに結びつける
if gui:
fig, ax = plt.subplots(figsize=[size, size])
fig.canvas.toolbar_visible = False
fig.canvas.header_visible = False
fig.canvas.footer_visible = False
fig.canvas.resizable = False
# ローカル関数としてイベントハンドラを定義する
def on_mouse_down(event):
# Axes の上でマウスを押していた場合のみ処理を行う
if event.inaxes and self.status == Marubatsu.PLAYING:
x = math.floor(event.xdata)
y = math.floor(event.ydata)
self.move(x, y)
self.draw_board(ax)
# 次の手番の処理を行うメソッドを呼び出す
self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
# fig の画像にマウスを押した際のイベントハンドラを結び付ける
fig.canvas.mpl_connect("button_press_event", on_mouse_down)
self.restart()
return self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
Marubatsu.play = play
修正箇所
def play(self, ai, ai_list=[], params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
# seed が None でない場合は、seed を乱数の種として設定する
if seed is not None:
random.seed(seed)
# ai_list から、項目だけを取り出した list を作成する
ai_values = [value for label, value in ai_list]
# ai に代入されている内容を ai_list に追加する
for i in range(2):
# ai[i] が ai_values に登録済かどうかを判定する
- if ai[i] is None and None not in ai_values:
- ai_list.append(("人間", None))
- ai_values.append(None)
- elif ai[i] is not None and ai[i] not in ai_values:
- ai_list.append((ai[i].__name__, ai[i]))
- ai_values.append(ai[i])
+ if ai[i] not in ai_values:
# ラベルを計算する
+ if ai[i] is None:
+ label = "人間"
+ else:
+ label = ai[i].__name__
# 項目を登録する
+ ai_list.append((label, ai[i]))
+ ai_values.append(ai[i])
元と同じなので省略
Marubatsu.play = play
上記 の 修正後 に、下記 のプログラムで play
メソッドを 実行 すると、実行結果 の 下図左 のように、Dropdown に 人間のみが表示 されますが、Dropdown の上 で マウスを押す と、下図右 のように 人間 と ai1s が 表示 されることが 確認 できます。
from ai import ai1s
mb.play(ai=[None, ai1s], gui=True)
実行結果(下図は、画像なので操作することはできません)
play
メソッドの改良その 2(Dropdown の初期選択)
上記 のプログラムでは、人間 VS ai1s
の 対戦 を行っていますが、実行結果 からわかるように、両方 の Dropdown に "人間" が 表示される という 問題 があります。これは、前回の記事で説明したように、Dropdown の 作成時 に value
、index
、label
の いずれの属性 も 設定しない 場合は、先頭の項目 が 選択状態になる からです。
そこで、下記 のプログラムのように、項目の値 を表す value
属性に、それぞれ の ai
の要素 を 代入 することで、適切な項目 が Dropdown に 選択される ようにします。
-
8、15 行目:
Dropdown
の キーワード引数value
に それぞれの手番 のai
の要素 を 記述 することで、Dropdown の 作成時 にvalue
属性に 値を代入 する
1 def play(self, ai, ai_list=[], params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
元と同じなので省略
2 # 〇 と × の Dropdown を作成する
3 dropdown_circle = widgets.Dropdown(
4 options=ai_list,
5 description="〇",
6 layout=widgets.Layout(width="100px"),
7 style={"description_width": "20px"},
8 value=ai[0],
9 )
10 dropdown_cross = widgets.Dropdown(
11 options=ai_list,
12 description="×",
13 layout=widgets.Layout(width="100px"),
14 style={"description_width": "20px"},
15 value=ai[1],
16 )
元と同じなので省略
17
18 Marubatsu.play = play
行番号のないプログラム
def play(self, ai, ai_list=[], params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
# seed が None でない場合は、seed を乱数の種として設定する
if seed is not None:
random.seed(seed)
# ai_list から、項目だけを取り出した list を作成する
ai_values = [value for label, value in ai_list]
# ai に代入されている内容を ai_list に追加する
for i in range(2):
# ai[i] が ai_values に登録済かどうかを判定する
if ai[i] not in ai_values:
# ラベルを計算する
if ai[i] is None:
label = "人間"
else:
label = ai[i].__name__
# 項目を登録する
ai_list.append((label, ai[i]))
ai_values.append(ai[i])
# 〇 と × の Dropdown を作成する
dropdown_circle = widgets.Dropdown(
options=ai_list,
description="〇",
layout=widgets.Layout(width="100px"),
style={"description_width": "20px"},
value=ai[0],
)
dropdown_cross = widgets.Dropdown(
options=ai_list,
description="×",
layout=widgets.Layout(width="100px"),
style={"description_width": "20px"},
value=ai[1],
)
# リセットボタンを作成する
button = widgets.Button(
description="リセット",
layout=widgets.Layout(width="100px"),
)
# 〇 と × の dropdown と リセットボタンを横に配置した HBox を作成し、表示する
hbox = widgets.HBox([dropdown_circle, dropdown_cross, button])
display(hbox)
# リセットボタンのイベントハンドラを定義する
def on_button_clicked(b):
self.restart()
self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
# イベントハンドラをリセットボタンに結びつける
button.on_click(on_button_clicked)
# gui が True の場合に、ゲーム盤を描画する画像を作成し、イベントハンドラに結びつける
if gui:
fig, ax = plt.subplots(figsize=[size, size])
fig.canvas.toolbar_visible = False
fig.canvas.header_visible = False
fig.canvas.footer_visible = False
fig.canvas.resizable = False
# ローカル関数としてイベントハンドラを定義する
def on_mouse_down(event):
# Axes の上でマウスを押していた場合のみ処理を行う
if event.inaxes and self.status == Marubatsu.PLAYING:
x = math.floor(event.xdata)
y = math.floor(event.ydata)
self.move(x, y)
self.draw_board(ax)
# 次の手番の処理を行うメソッドを呼び出す
self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
# fig の画像にマウスを押した際のイベントハンドラを結び付ける
fig.canvas.mpl_connect("button_press_event", on_mouse_down)
self.restart()
return self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
Marubatsu.play = play
修正箇所
def play(self, ai, ai_list=[], params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
元と同じなので省略
# 〇 と × の Dropdown を作成する
dropdown_circle = widgets.Dropdown(
options=ai_list,
description="〇",
layout=widgets.Layout(width="100px"),
style={"description_width": "20px"},
+ value=ai[0],
)
dropdown_cross = widgets.Dropdown(
options=ai_list,
description="×",
layout=widgets.Layout(width="100px"),
style={"description_width": "20px"},
+ value=ai[1],
)
元と同じなので省略
Marubatsu.play = play
上記 の 修正後 に、下記 のプログラムで play
メソッドを 実行 すると、実行結果 の 下図左 のように、× の Dropdown には ai1s が 正しく表示 されますが、〇 の Dropdown には 空白 が 表示 されてしまいます。ただし、〇 の Dropdown の上 で マウスを押す と、下図右 のように 人間 と ai1s が 正しく表示 されることが 確認 できます。
mb.play(ai=[None, ai1s], gui=True)
実行結果(下図は、画像なので操作することはできません)
Dropdown の value
属性の None
の意味
このようなことが起きる 原因 は、value
属性に None
が 代入 された場合は、どの項目 も 選択されていない という 意味を表す ように Dropdown が 設計されている からです。
そのため、上記 のプログラムで 人間 が 手番を担当 する場合の 項目の値 を None
に 設定 した場合のように、Dropdown の 項目の値 に None
が設定 されている場合では、value
属性に None
を代入 すると、None
が 設定された項目 が 選択状態 になるの ではなく、先程の実行結果のように、どの項目 も 選択されない という 状態 に なります。
Dropdown では、項目の値 に None
を 設定するべきではない。
問題の解決方法
この問題 を 修正 する 方法 の 一つ に、play
メソッドの 仮引数 ai
の要素が 人間の手番 を 表す値 を None
以外 のデータに 変更する という方法がありますが、その方法 で play
メソッドを 修正 すると、これまで に play
メソッドを 利用するプログラム が 動作しなくなる という、互換性 の 問題が発生 します。
play
メソッドの 互換性を保つ 別の 解決方法 として、人間を表す Dropdown の 項目の値 を None
以外の値 に 変更 するという 方法 があります。None
以外 の 値 であれば 何でも構わない ので、本記事では "人間" という 文字列 で 表現 することにします。
他の候補 として、AI ではない という 意図 の False
や、何もない という 意図 の 0
や ""
などが挙げられるでしょう。変更したい人は自由に変更して下さい。
play
メソッドの修正
下記 は、そのように play
メソッドを 修正 したプログラムです。
-
9、12 行目:
value
に それぞれ の場合の 適切 な 項目の値 を 代入 する -
14、15 行目:
value
を使ってai_list
とai_values
の 要素 を 追加 する
1 def play(self, ai, ai_list=[], params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
元と同じなので省略
2 # ai に代入されている内容を ai_list に追加する
3 for i in range(2):
4 # ai[i] が ai_values に登録済かどうかを判定する
5 if ai[i] not in ai_values:
6 # ラベルと項目の値を計算する
7 if ai[i] is None:
8 label = "人間"
9 value = "人間"
10 else:
11 label = ai[i].__name__
12 value = ai[i]
13 # 項目を登録する
14 ai_list.append((label, value))
15 ai_values.append(value)
元と同じなので省略
16
17 Marubatsu.play = play
行番号のないプログラム
def play(self, ai, ai_list=[], params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
# seed が None でない場合は、seed を乱数の種として設定する
if seed is not None:
random.seed(seed)
# ai_list から、項目だけを取り出した list を作成する
ai_values = [value for label, value in ai_list]
# ai に代入されている内容を ai_list に追加する
for i in range(2):
# ai[i] が ai_values に登録済かどうかを判定する
if ai[i] not in ai_values:
# ラベルと項目の値を計算する
if ai[i] is None:
label = "人間"
value = "人間"
else:
label = ai[i].__name__
value = ai[i]
# 項目を登録する
ai_list.append((label, value))
ai_values.append(value)
# 〇 と × の Dropdown を作成する
dropdown_circle = widgets.Dropdown(
options=ai_list,
description="〇",
layout=widgets.Layout(width="100px"),
style={"description_width": "20px"},
value=ai[0],
)
dropdown_cross = widgets.Dropdown(
options=ai_list,
description="×",
layout=widgets.Layout(width="100px"),
style={"description_width": "20px"},
value=ai[1],
)
# リセットボタンを作成する
button = widgets.Button(
description="リセット",
layout=widgets.Layout(width="100px"),
)
# 〇 と × の dropdown と リセットボタンを横に配置した HBox を作成し、表示する
hbox = widgets.HBox([dropdown_circle, dropdown_cross, button])
display(hbox)
# リセットボタンのイベントハンドラを定義する
def on_button_clicked(b):
self.restart()
self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
# イベントハンドラをリセットボタンに結びつける
button.on_click(on_button_clicked)
# gui が True の場合に、ゲーム盤を描画する画像を作成し、イベントハンドラに結びつける
if gui:
fig, ax = plt.subplots(figsize=[size, size])
fig.canvas.toolbar_visible = False
fig.canvas.header_visible = False
fig.canvas.footer_visible = False
fig.canvas.resizable = False
# ローカル関数としてイベントハンドラを定義する
def on_mouse_down(event):
# Axes の上でマウスを押していた場合のみ処理を行う
if event.inaxes and self.status == Marubatsu.PLAYING:
x = math.floor(event.xdata)
y = math.floor(event.ydata)
self.move(x, y)
self.draw_board(ax)
# 次の手番の処理を行うメソッドを呼び出す
self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
# fig の画像にマウスを押した際のイベントハンドラを結び付ける
fig.canvas.mpl_connect("button_press_event", on_mouse_down)
self.restart()
return self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
Marubatsu.play = play
修正箇所
def play(self, ai, ai_list=[], params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
元と同じなので省略
# ai に代入されている内容を ai_list に追加する
for i in range(2):
# ai[i] が ai_values に登録済かどうかを判定する
if ai[i] not in ai_values:
# ラベルと項目の値を計算する
if ai[i] is None:
label = "人間"
+ value = "人間"
else:
label = ai[i].__name__
+ value = ai[i]
# 項目を登録する
- ai_list.append((label, ai[i]))
+ ai_list.append((label, value))
- ai_values.append(ai[i])
+ ai_values.append(value)
元と同じなので省略
Marubatsu.play = play
上記 の 修正後 に、下記 のプログラムで play
メソッドを 実行 すると、残念ながら 実行結果 の 下図左 のように、〇 の Dropdown には相変わらず 空白が表示 されてしまいます。このようなことが起きる原因について少し考えてみて下さい。
mb.play(ai=[None, ai1s], gui=True)
実行結果(下図は、画像なので操作することはできません)
問題の原因と修正
上記 の 問題 は、人間 を表す 項目の値 を 変更 したにも 関わらず、Dropdown を 作成する際 の、キーワード引数 value
の 値 をそれに合わせて 変更していない からです。
ただし、現状のプログラム では、ai_values
に 登録 した 項目の値 を 記録していない ので、Dropdown の 作成時 に その値 を 利用 することが できません。そこで、下記 のプログラムのように、それぞれ の 手番 の Dropdown の 項目の値 を select_values
という list に記録 することで、Dropdown の 作成時 に 適切な項目 が 選択される ように 修正 します。
-
3 行目:それぞれ の 手番の担当 を表す Dropdown の 項目の値 を 記録 する
select_values
の値を 空の list で 初期化 する -
18 行目:それぞれ の 手番の担当 を表す 項目の値 を
select_values
に 追加 する -
26、33 行目:
select_values
を使って、Dropdown のvalue
属性の 値を設定 する
1 def play(self, ai, ai_list=[], params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
元と同じなので省略
2 # それぞれの手番の担当を表す Dropdown の項目の値を記録する list を初期化する
3 select_values = []
4 # ai に代入されている内容を ai_list に追加する
5 for i in range(2):
6 # ai[i] が ai_values に登録済かどうかを判定する
7 if ai[i] not in ai_values:
8 # ラベルと項目の値を計算する
9 if ai[i] is None:
10 label = "人間"
11 value = "人間"
12 else:
13 label = ai[i].__name__
14 value = ai[i]
15 # 項目を登録する
16 ai_list.append((label, value))
17 ai_values.append(value)
18 select_values.append(value)
19
20 # 〇 と × の Dropdown を作成する
21 dropdown_circle = widgets.Dropdown(
22 options=ai_list,
23 description="〇",
24 layout=widgets.Layout(width="100px"),
25 style={"description_width": "20px"},
26 value=select_values[0],
27 )
28 dropdown_cross = widgets.Dropdown(
29 options=ai_list,
30 description="×",
31 layout=widgets.Layout(width="100px"),
32 style={"description_width": "20px"},
33 value=select_values[1],
34 )
元と同じなので省略
35
36 Marubatsu.play = play
行番号のないプログラム
def play(self, ai, ai_list=[], params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
# seed が None でない場合は、seed を乱数の種として設定する
if seed is not None:
random.seed(seed)
# ai_list から、項目だけを取り出した list を作成する
ai_values = [value for label, value in ai_list]
# それぞれの手番の担当を表す Dropdown の項目の値を記録する list を初期化する
select_values = []
# ai に代入されている内容を ai_list に追加する
for i in range(2):
# ai[i] が ai_values に登録済かどうかを判定する
if ai[i] not in ai_values:
# ラベルと項目の値を計算する
if ai[i] is None:
label = "人間"
value = "人間"
else:
label = ai[i].__name__
value = ai[i]
# 項目を登録する
ai_list.append((label, value))
ai_values.append(value)
select_values.append(value)
# 〇 と × の Dropdown を作成する
dropdown_circle = widgets.Dropdown(
options=ai_list,
description="〇",
layout=widgets.Layout(width="100px"),
style={"description_width": "20px"},
value=select_values[0],
)
dropdown_cross = widgets.Dropdown(
options=ai_list,
description="×",
layout=widgets.Layout(width="100px"),
style={"description_width": "20px"},
value=select_values[1],
)
# リセットボタンを作成する
button = widgets.Button(
description="リセット",
layout=widgets.Layout(width="100px"),
)
# 〇 と × の dropdown と リセットボタンを横に配置した HBox を作成し、表示する
hbox = widgets.HBox([dropdown_circle, dropdown_cross, button])
display(hbox)
# リセットボタンのイベントハンドラを定義する
def on_button_clicked(b):
self.restart()
self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
# イベントハンドラをリセットボタンに結びつける
button.on_click(on_button_clicked)
# gui が True の場合に、ゲーム盤を描画する画像を作成し、イベントハンドラに結びつける
if gui:
fig, ax = plt.subplots(figsize=[size, size])
fig.canvas.toolbar_visible = False
fig.canvas.header_visible = False
fig.canvas.footer_visible = False
fig.canvas.resizable = False
# ローカル関数としてイベントハンドラを定義する
def on_mouse_down(event):
# Axes の上でマウスを押していた場合のみ処理を行う
if event.inaxes and self.status == Marubatsu.PLAYING:
x = math.floor(event.xdata)
y = math.floor(event.ydata)
self.move(x, y)
self.draw_board(ax)
# 次の手番の処理を行うメソッドを呼び出す
self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
# fig の画像にマウスを押した際のイベントハンドラを結び付ける
fig.canvas.mpl_connect("button_press_event", on_mouse_down)
self.restart()
return self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
Marubatsu.play = play
修正箇所
def play(self, ai, ai_list=[], params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
元と同じなので省略
# それぞれの手番の担当を表す Dropdown の項目の値を記録する list を初期化する
+ select_values = []
# ai に代入されている内容を ai_list に追加する
for i in range(2):
# ai[i] が ai_values に登録済かどうかを判定する
if ai[i] not in ai_values:
# ラベルと項目の値を計算する
if ai[i] is None:
label = "人間"
value = "人間"
else:
label = ai[i].__name__
value = ai[i]
# 項目を登録する
ai_list.append((label, value))
ai_values.append(value)
+ select_values.append(value)
# 〇 と × の Dropdown を作成する
dropdown_circle = widgets.Dropdown(
options=ai_list,
description="〇",
layout=widgets.Layout(width="100px"),
style={"description_width": "20px"},
+ value=select_values[0],
)
dropdown_cross = widgets.Dropdown(
options=ai_list,
description="×",
layout=widgets.Layout(width="100px"),
style={"description_width": "20px"},
+ value=select_values[1],
)
元と同じなので省略
Marubatsu.play = play
上記 の 修正後 に、下記 のプログラムで play
メソッドを 実行 すると、実行結果 のように、それぞれ の Dropdown が 正しい項目 を 選択 することが 確認 できます。
mb.play(ai=[None, ai1s], gui=True)
実行結果(下図は、画像なので操作することはできません)
実は、上記 の play
メソッドには 重大なバグ が いくつかあります。余裕がある方は、play
メソッドで 様々な対戦 を行ってみて、どのような バグ があるかを 探してみて下さい。
今回の記事のまとめ
今回の記事では、循環インポート の エラーの原因 と 修正方法 について 説明 しました。
また、play
メソッドの 仮引数 ai
に 代入 された値に 応じて、Dropdown の 項目 の 自動登録 と、Dropdown の 選択項目 を 設定 する方法について紹介しました。
本記事で入力したプログラム
以下のリンクから、本記事で入力して実行した JupyterLab のファイルを見ることができます。
以下のリンクは、今回の記事で更新した marubatsu.py です。
次回の記事