目次と前回の記事
これまでに作成したモジュール
以下のリンクから、これまでに作成したモジュールを見ることができます。
ルールベースの AI の一覧
ルールベースの AI の一覧については、下記の記事を参照して下さい。
今回の記事の内容
前回の記事で リセットボタン を 実装 しましたが、異なる AI と 対戦 を 行う 場合は、キーワード引数 ai
の内容を 変更 して、改めて play
メソッドを 実行する必要 がある点が 面倒 です。そこで、play
メソッドを 実行しなくても、GUI で AI を 選択できる ようにします。
ipywidgets の Dropdown ウィジェットの使い方
GUI で AI を選択 できるようにするためには、複数の項目 の中から 1 つを選択 するための GUI が必要になります。ipywidgets には、そのようなウィジェットとして、ドロップダウンメニュー を表す Dropdown や、ラジオボタン を表す RadioButtons などがあります。
選択を行うための ipywidgets のウィジェット
Dropdown は、下図左 のような、選択中 の 項目 が 表示 される ウィジェット で、この上 で マウスを押す と、下図右 のような ドロップダウンメニュー が 表示 されます。下図は画像なので操作できないので、実物を見たい方 は、下記 の リンク先 を 参照 して下さい。
RadioButtons は、下図 のように、すべての項目 が 表示 され、左の ラジオボタン で 項目を選択 する ウィジェット です。実物を見たい方 は、下記 の リンク先 を 参照 して下さい。
RadioButtons は、選択 できる 項目の数 が 多くなる と、表示範囲 が 広くなる という 欠点 があります。これまで に ai1
から ai14s
まで 多数の AI を 作成 してきたので、本記事では、Dropdown を使って AI を選択 することにします。
本記事では扱いませんが、ipywidgets には、他にも 選択を行う ための ウィジェット が 多数用意 されています。興味がある方は、下記のリンク先を参照して下さい。
Dropdown の作成方法
Dropdown は、ipywidgets の Dropdown
という 関数 を使って 作成 します。例えば、先程 紹介した 画像 の Dropdown は、下記 のプログラムで 作成 し、ボタン の ウィジェット と 同様 に、display
を使って 画面に表示 します。Dropdown
を呼び出す際に記述する 実引数 については、この後で説明 します。実際に 実行 し、表示された Dropdown の上 で マウスを押す と メニューが表示 され、項目 を 選択できる ことを 確認 して下さい。
import ipywidgets as widgets
dropdown = widgets.Dropdown(
options=['1', '2', '3'],
value='2',
description='Number:',
)
display(dropdown)
実行結果(下図は、画像なので操作することはできません)
Dropdown の属性と Dropdown
の仮引数
Dropdown
によってよって 作成 された オブジェクト には、下記 のような 属性 があります。それぞれの 属性の意味 についてはこの 後で詳しく説明 します。なお、layout
と style
属性は、すべて の ウィジェット で 共通 します。
属性 | 意味 | データ型 |
---|---|---|
options |
選択 できる 項目 の リスト | list |
value |
選択中 の 項目 | any(何でもよい) |
index |
選択中 の 項目 の インデックス | int |
label |
選択中 の 項目 の ラベル | str |
description |
左に表示 される 説明 | str |
disabled |
True の場合に 操作できなくなる
|
bool |
layout |
作成する ウィジェット の レイアウト の 設定 | widgets.Layout |
style |
作成する ウィジェット の スタイル の 設定 | dict |
これらの 属性 は、Dropwdown の 作成時 に、Dropdown
の 実引数 に、属性名 と 同じ名前 の キーワード引数 を 記述 することで 設定 することが できます。
具体例 を挙げます。下記 のプログラムは、先程作成 した Dropdown と 同じもの を 作製 するプログラムです。下記 のプログラムのように、Dropdown の属性 は、Dropdown の 作成後 に、直接値を代入 することでも 設定 することが できます。
dropdown2 = widgets.Dropdown()
dropdown2.options = ['1', '2', '3']
dropdown2.value = '2'
dropdown2.description = 'Number:'
display(dropdown2)
実行結果(下図は、画像なので操作することはできません)
また、Dropdown を display
で 表示した後 で、下記 のプログラムのように、Dropdown の 属性を変更 すると、作成した Dropdown の 表示内容 が下記の実行結果のように 変化します。なお、description
属性は、Dropdown の 左に表示 される 説明 を表します。
dropdown2.description = '数字:'
実行結果(先程表示した Dropdown の表示内容が変化します)
Dropdown の主要な属性
Dropdown の 主要な属性 について 説明 します
options
Dropdown で 選択 できる 各項目 を表す list です。先程の例 では、options=['1', '2', '3']
のように、'1'
、'2'
、'3'
という 3 つの文字列 が list の 要素 に 記述 されたので、それら が 選択できる ようになります。
Dropdown では、選択 できる 項目 が 表す値 と、メニューに その項目 を 表示 する際の 文字列 を 異なるものにする ことが できます。例えば、メニュー のそれぞれの 項目 は "令和元年"、"令和二年"、"令和三年" のように 日本語で表示 するが、それらの 項目を選択 した際には、2019、2020、2021 のような 西暦 を表す 数値 が 選択される ようにすることができます。このような、メニュー に 表示 する 文字列 の事を ラベル と呼びます。
ラベル を 設定 した Dropdown は、下記 のプログラムのように、それぞれの項目 を (ラベル, 値)
という tuple で 記述 します。下記 のプログラムを実行すると、下図左 のような Dropdown が表示され、この上 で マウスを押す と、下図右 のような、設定 した ラベル が 表示 される ドロップダウンメニュー が 表示 されます。
dropdown3 = widgets.Dropdown(
options=[("令和元年", 2019), ("令和二年", 2020), ("令和三年", 2021)],
description="年:",
)
display(dropdown3)
実行結果(下図は、画像なので操作することはできません)
なお、dropdown
や dropdown2
のように ラベル を 設定しない 場合は、それぞれの 項目 の ラベル には、自動的 に 項目の値 を 文字列型 のデータに 変換した値 が 設定 されます。
value
、index
、label
Dropdown には、選択中の項目 を表す、下記の表の value
、index
、label
という 3 つの属性 があります。
属性名 | 意味 |
---|---|
value |
選択中 の 項目 の 値 |
index |
選択中 の 項目 を表す options 属性 の 要素 の インデックス
|
label |
選択中 の 項目 の ラベル |
Dropdown の 作成時 に、Dropdown
に これら の キーワード引数 を 記述しなかった場合 は、先頭の項目 が 選択 された状態の Dropdown が作成 されます。
例えば、先程作成 した dropdown3
の 各項目 の インデックス、ラベル、値 は 以下 のように 設定 されています。
インデックス | ラベル | 値 |
---|---|---|
0 | "平成元年" |
2019 |
1 | "平成二年" |
2020 |
2 | "平成三年" |
2021 |
dropdown3
の メニューの選択 を 好きな項目 に 変更した後 で、下記 のプログラムを 実行 すると、index
、label
、value
属性に、現在選択中 の 項目 に 対応 する 上記の表 の 値 が 表示 されます。下記 の 実行結果 は、平成三年 を 選択した際 のものです。
print(dropdown3.index, dropdown3.label, dropdown3.value)
実行結果
2 令和三年 2021
また、これらの属性 の いずれか に 値を代入 することで、Dropdown の 選択項目 を 変える ことが できます。その際 に、残りの 2 つ の 属性の値 は 自動的 に 新しく選択 された 項目の値 に 変更 されます。
下記 は、index
属性に 0
を 代入 するプログラムです。実行結果 のように、Dropdown の 選択項目 の表示が 平成元年 に変更され、label
と value
属性の値も 変更 されます。
dropdown3.index = 0
print(dropdown3.index, dropdown3.label, dropdown3.value)
実行結果(下図は、画像なので操作することはできません)
0 令和元年 2019
下記 は、label
属性に "令和二年"
を 代入 するプログラムです。実行結果 のように、Dropdown の 選択項目 が 令和二年 に変更され、index
、value
属性の値も 変更 されます。
dropdown3.label = "平成二年"
print(dropdown3.index, dropdown3.label, dropdown3.value)
実行結果(下図は、画像なので操作することはできません)
1 令和二年 2020
value
属性に 値を代入 する場合も 同様 です。興味がある方は試してみて下さい。
なお、下記 のプログラムのように、対応 する 項目 が 存在しない値 を 代入 した場合は、実行結果 のような エラーが発生 します。これは、3 つの属性 に 共通する性質 です。
dropdown3.value = 2000
実行結果
略
File c:\Users\ys\Anaconda3\envs\marubatsu\Lib\site-packages\ipywidgets\widgets\widget_selection.py:244, in _Selection._validate_value(self, proposal)
242 return findvalue(self._options_values, value, self.equals) if value is not None else None
243 except ValueError:
--> 244 raise TraitError('Invalid selection: value not found')
TraitError: Invalid selection: value not found
上記のエラーメッセージは、以下のような意味を持ちます。
-
TraitError
TraitError は、ipywidget 独自のエラーです。おそらくウィジェットの選択に関するエラーを表しているのではないかと思います -
invalid syntax
見つからない(not found)value を選択するという、不正な(invalid)選択(selection)を行おうとした
description
Dropdown の左 に 表示 される 説明(description)を表します。
下記 のプログラムのように、この属性 に 値 を 代入しない 場合は、Dropdown の 左 には 何も表示されません。
dropdown4 = widgets.Dropdown(
options=['1', '2', '3'],
value='2',
)
display(dropdown4)
実行結果(下図は、画像なので操作することはできません)
disabled
下記 のプログラムのように、この属性 に True
が代入 されていた場合は、Dropdown の メニューの項目 が 薄い色で表示 され、操作ができなく(disabled)なります。実際に実行 して 操作できない ことを 確認 して下さい。なお、薄い色で表示 されるのは 項目の部分 だけで、左に表示 される description
の 説明 は 薄く表示されません。
dropdown5 = widgets.Dropdown(
options=['1', '2', '3'],
value='2',
description='Number:',
disabled=True,
)
display(dropdown5)
実行結果(下図は、画像なので操作することはできません)
なお、disabled
属性は、すべて の 操作を行うウィジェット1 に 共通する属性 で、それらのウィジェット の 作成時 には、disabled
属性の 初期値 として False
が代入 されます。
layout
ウィジェット の 横幅 や 縦幅 などの レイアウト(layout)の 設定 を表す 属性 です。レイアウト は、ipywidgets の Layout
という 関数 で 作成 される オブジェクト で 設定 します。
Layout
には、様々な仮引数 があり、その中 で ウィジェット の サイズ に関する 仮引数 には 下記 の表のようなものがあります。
仮引数 | 意味 |
---|---|
width |
横幅 |
height |
縦幅 |
max_width |
横幅の最大値 |
min_width |
横幅の最小値 |
max_height |
縦幅の最大値 |
min_height |
縦幅の最小値 |
また、サイズ の 値 は、以下の方法 で 記述 します。
記述方法 | |
---|---|
ピクセル単位で設定する |
"100px" のように、px で終わる文字列 を 記述 する |
割合で設定する |
"50%" のように、% で終わる文字列 を 記述 する |
下記 は、Dropdown の 横幅 を 100 ピクセル と、表示できる範囲 の 半分(50 %)の サイズ で 作成 するプログラムです。
dropdown6 = widgets.Dropdown(
options=['1', '2', '3'],
layout=widgets.Layout(width="100px")
)
display(dropdown6)
dropdown7 = widgets.Dropdown(
options=['1', '2', '3'],
layout=widgets.Layout(width="50%")
)
display(dropdown7)
実行結果(下図は、画像なので操作することはできません)
"50%"
のように、% で サイズを指定 した場合は、VSCode の ウィンドウの幅 を 変更 すると、ウィジェットの幅 が 自動的に変更 されます。実際に試してみて下さい。
ウィジェット の レイアウトの詳細 に関しては、下記のリンク先 を 参照 して下さい。
style
ウィジェット の 内部 の 文字の色 などの スタイル(style)の 設定 を表す 属性 です。スタイル の 設定 は、スタイルの種類 を表す キー と その値 が 設定 された dict で行います。設定 できる スタイル は、ウィジェット の 種類ごと に 異なって おり、Dropdown の場合は、下記 のプログラムのように、description_width
という キー によって、左に表示 する 説明 の 文字列 の 表示幅 を 設定できます。
下記 は 、description_width
に "50px"
と "100px"
を 設定 した場合で、実行結果 のように、description
で 表示 した "number"
の 文字列 の 表示幅 が 変化 します。
dropdown8 = widgets.Dropdown(
options=['1', '2', '3'],
description="number",
style= {
"description_width": "50px",
}
)
display(dropdown8)
dropdown9 = widgets.Dropdown(
options=['1', '2', '3'],
description="number",
style= {
"description_width": "100px",
}
)
display(dropdown9)
実行結果(下図は、画像なので操作することはできません)
ウィジェット の スタイルの詳細 に関しては、下記 の リンク先 を 参照 して下さい。
AI を選択する Dropdown の作成
AI を 選択 する Dropdown を作成 するためには、どの AI を Dropdown で 選択できる ようにするかを 決める必要 があります。せっかくなので、ルール 1 から ルール 14 までの すべての AI を 選択できる ようにすることにします。ただし、ai1
と ai1s
のように、同じルール の AI が 複数存在 する場合は、s
がつく方 を 選択できる ようにします。従って、ai1s
~ ai14s
の 14 種類 の AI を 選択できる ようにします。
AI の関数の list の作成
Dropdown の 選択項目 は、options
属性 に list の形式 で 代入する必要 があるため、その list を 作成 します。下記 は、AI の 関数の一覧 を表す list を 作成 するプログラムです。
import ai
ai_list = [ ai.ai1s, ai.ai2s, ai.ai3s, ai.ai4s, ai.ai5s, ai.ai6s, ai.ai7s,
ai.ai8s, ai.ai9s, ai.ai10s, ai.ai11s, ai.ai12s, ai.ai13s, ai.ai14s ]
これまで のプログラムで AI の関数 を 利用する際 に 記述 した 方法 で、上記 のプログラムを 下記 のプログラムのように 記述 することも できます が、記述 が かえって長くなる ので 上記 のように 記述 しました。
from ai import ai1s, ai2s, ai3s, ai4s, ai5s, ai6s, ai7s, \
ai8s, ai9s, ai10s, ai11s, ai12s, ai13s, ai14s
ai_list = [ ai1s, ai2s, ai3s, ai4s, ai5s, ai6s, ai7s,
ai8s, ai9s, ai10s, ai11s, ai12s, ai13s, ai14s ]
上記 のプログラムで 正しい list を 作成 できていますが、上記 のプログラムは 記述が大変 だと思いませんか?AI の 関数の名前 は ai
と s
の 間 に 1 から 14 まで の 整数 が __ 入る__ という、規則正しい名前 になっています。そのこと と、オブジェクトの中 から 指定した名前 を持つ 属性を計算 する getattr
という 組み込み関数 を 利用 することで、上記 のプログラムを 簡潔に記述 することが できます。
getattr
を利用した AI の関数の取得方法
getattr
は、以前の記事で 説明 していますが、これまでに作成 した marubatsu.py や ai.py では 利用していない ので、改めて説明 します。
getattr
は、任意の オブジェクト 対して 利用できる関数 で、1 つ目 の 実引数 に オブジェクト を、2 つ目 の 実引数 に 属性の名前 を 記述 することで、指定した属性 の 値 を 返り値 として 返す処理 を行います。
Python では、インポート した モジュール内 で 定義 された 変数 や 関数など を、モジュール名.名前
のように 記述 して 参照 することが できます。.
を使って 記述している ことからわかるように、モジュール の 変数 や 関数 などは、インポート した モジュール を表す オブジェクトの属性 として 管理 されています。
従って、例えば ai.ai1s
は、ai
という モジュール の "ai1s"
という 名前 の 属性 に 代入 されているので、下記 のプログラムのように、getattr
を 利用 することで、ai.ai1s
を 参照 して、特定の変数 に 代入 することが できます。
ai1s = getattr(ai, "ai1s")
上記の ai1s
と ai.ai1s
には 同じ関数オブジェクト が 代入 されているので、下記 のプログラムのように、ゲーム開始時 の 局面 を表す mb
を 実引数 に記述して 呼び出すこと で、どちらも 同じ着手 が 選択 されます。なお、ai1s
は、「左上から順 に 空いているマス を 探し、最初に見つかったマス に 着手 する」という AI なので、(0, 0) が選択されます。
from marubatsu import Marubatsu
mb = Marubatsu()
print(ai1s(mb))
print(ai.ai1s(mb))
実行結果
(0, 0)
(0, 0)
list や dict の場合は []
を使って 要素の値 を 参照 することができますが、オブジェクト の場合は []
を使って 属性の値 を 参照 することは できません。getattr
は、list や dict における []
と 同じ役割 を果たすものだと 考えると良い でしょう。
getattr
の 詳細 については、下記のリンク先を参照して下さい。
getattr
を利用した AI の関数の list の計算
先程説明したように、AI の 関数の名前 は、ai
と s
の 間 に 1 から 14 まで の 数字が入る という、規則正しい名前 になっているので、getattr
を 利用 することで、AI の 関数の list を 作成 するプログラムは、下記 のように 簡潔に記述 できます。
-
1 行目:
ai_list
を 空の list で 初期化 する - 2 行目:1 から 14 ま での 整数 の 繰り返し処理 を行う
- 3 行目:AI の 関数の名前 を 計算 する
-
4 行目:
getattr
を使って AI の関数 を 取得 し、ai_list
の 要素に追加 する
ai_list = []
for i in range(1, 15):
ai_name = f"ai{i}s"
ai_list.append(getattr(ai, ai_name))
少し わかりづらい ので本記事では採用しませんが、上記 のプログラムは、リスト内包表記 を使って、下記 のように 記述 することも できます。
ai_list = [ getattr(ai, f"ai{i}s") for i in range(1, 15) ]
AI の一覧を表す Dropdown の作成
AI の一覧 を表す Dropdown は、下記 のプログラムのように、上記で作成 した ai_list
使って、Dropdown
の 実引数 に options=ai_list
を 記述 することで 作成 できます。
dropdown10 = widgets.Dropdown(options=ai_list)
display(dropdown10)
実行結果(下図は、画像なので操作することはできません)
上図左 は 表示 される Dropdown で、上図右 は Dropdown の上 で マウス を 押した場合 に 表示 される ドロップダウンメニュー の 図 です。メニューに表示 されるのは、下記 のプログラムのように、関数 を print
で表示 した場合に 表示される内容 です。
print(ai.ai1s)
実行結果(at の後ろに表示される内容は、下記と異なる場合があります)
<function ai1s at 0x0000025E8600C860>
上記 のプログラムでは、options
に Dropdown で 選択できる項目のみ を 要素 として持つ list を 代入 しました。先程 の options
の所で 説明 したように、そのような場合 は、画面に表示 されるそれぞれの 項目のラベル には、項目 を 文字列型 のデータに 変換した値 が 自動的に設定 されます。関数 を 文字列 のデータに 変換 すると、上記 のような 表示 になるので、先程作成 した AI の 関数 を 項目 として持つ Dropdown の メニュー には、先程の図 のような 内容 が 表示 されます。
AI の名前を表すラベルの設定
先程説明したように、Dropdown では、メニュー に 表示 する 文字列 を ラベル と呼び、options
の 要素 に 下記 のような tuple を代入 することで、ラベル に 項目 とは 別の文字列 を 設定 することが できます。
(ラベルを表す文字列, 項目の値)
従って、例えば、ai1s
、ai2s
、ai3s
の 3 つ の AI の名前 を ラベル にした Dropdown は、下記 のプログラムで作成できます。
dropdown11 = widgets.Dropdown(
options=[("ai1s", ai.ai1s), ("ai2s", ai.ai2s), ("ai3s", ai.ai3s)]
)
display(dropdown11)
実行結果(下図は、画像なので操作することはできません)
上図左 は 表示 される Dropdown で、上図右 は Dropdown の上 で マウスを押した 場合に 表示 される メニューの図 です。メニューに表示 されるのは "ai1s"
という ラベル ですが、このラベル を 選択した際 に Dropdown が 実際 に 選択する項目 は、ai.ai1s
という 関数 になります。そのことは、Dropdown で ai1 を 選択した後 で、下記 のプログラムで 確認 することが できます。先ほど説明したように、Dropdown の value
属性 には、選択中 の 項目の値 が 代入 されています。そのため、ai1s を選択すると、dropdown11.value
には、ai.ai1s
という AI の関数 が代入され、dropdown11.value(mb)
を 実行 すると、ai1s
が 着手を選択 し、先程と同様 に (0, 0) が 選択 されます。
print(dropdown11.value(mb))
実行結果
(0, 0)
また、下記 のプログラムのように、dropdown11.value
を 表示 することでも、ai.ai1s
という 関数 が 代入されている ことを 確認 できます。
print(dropdown11.value)
実行結果(at の後ろに表示される内容は、下記と異なる場合があります)
<function ai1s at 0x0000025E8600C860>
ラベルに AI の名前を設定した Dropdown の作成
上記 で説明した 方法 を 利用 することで、ラベル に AI の名前 を 設定 した AI の一覧 を表す Dropdown を、下記 のプログラムで 作成 することが できます。
ai_list = []
for i in range(1, 15):
ai_name = f"ai{i}s"
ai_list.append((ai_name, getattr(ai, ai_name)))
dropdown12 = widgets.Dropdown(options=ai_list)
display(dropdown12)
実行結果(下図は、画像なので操作することはできません)
上図左 は 表示 される Dropdown で、上図右 は Dropdown の上 で マウス を 押した場合 に 表示 される ドロップダウンメニュー の 図 で、意図通り の Dropdown が 作成 されます。
〇 と × の AI を選択する Dropdown の作成
次に、〇 と × の それぞれの AI を 選択 する Dropdown を 作成 します。また、それぞれ の Dropdown が どちらの手番 を 担当する かを、Dropdown の description
属性を使って 区別 することにします。下記 は、2 つ の Dropdown を 作成 するプログラムです。
dropdown_circle = widgets.Dropdown(
options=ai_list,
description="〇",
)
display(dropdown_circle)
dropdown_cross = widgets.Dropdown(
options=ai_list,
description="×",
)
display(dropdown_cross)
実行結果(下図は、画像なので操作することはできません)
実行結果 からわかるように、作成 された Dropdown は、2 行 に 分けて表示 されます。また、2 つ の Dropdown は 左端の位置 が 微妙に揃わない という 問題 が生じています。
ipywidgets のウィジェットの配置のレイアウト
display
を使って 複数 の ipywidgets を 表示 すると、上記 のように、作成された ウィジェット が 思い通り に 表示されない という 問題 が 生じます。この問題 を 解決 するために、ipywidgets には、作成した ウィジェット の 配置 の レイアウト を 設定 するための HBox
と VBox
という ウィジェット が 用意 されています。
HBox
と VBox
の 詳細 については、下記のリンク先を参照して下さい。
ipywidgets には HBox
と VBox
以外 にもウィジェットの 配置 の レイアウト を 設定 するための ウィジェット が 用意 されており、詳細 な レイアウトの設定 を行うことが できます。興味がある方は下記のリンク先を参照して下さい。
HBox と VBox の使い方
HBox は ウィジェット を 水平(horizontal)方向に 並べて配置 するための ウィジェット で、下記 のプログラムのように、ipywidgets の HBox
の 実引数 に、横方向 に 並べて配置 する ウィジェット を 要素 として持つ list を記述 することで 作成 します。作成 した HBox を display
で表示 することで、HBox に 登録 した ウィジェット が 実行結果 のように、横方向 に 並べて表示 されるようになります。
hbox = widgets.HBox([dropdown_circle, dropdown_cross])
display(hbox)
実行結果(下図は、画像なので操作することはできません)
VBox は ウィジェット を 垂直(vertical)方向に 並べて配置 するための ウィジェット で、下記 のプログラムのように、ipywidgets の VBox
の 実引数 に、縦方向 に 並べて配置 する ウィジェット を 要素 として持つ list を記述 することで 作成 します。作成 した VBox を display
で表示 することで、VBox に 登録 した ウィジェット が 実行結果 のように、縦方向 に 並べて表示 されるようになります。
vbox = widgets.VBox([dropdown_circle, dropdown_cross])
display(vbox)
実行結果(下図は、画像なので操作することはできません)
今回の記事では行いませんが、HBox
や VBox
の 実引数 に記述する list の要素 には、任意 の ウィジェット を 代入 することが できます。HBox
や VBox
も ウィジェットの一種な ので、下記 のプログラムのように、HBox 内 で 横 に 2 つ並べた Dropdown を VBox で 縦に並べて表示 するようなことも できます。
vbox = widgets.VBox([hbox, hbox])
display(vbox)
実行結果(下図は、画像なので操作することはできません)
わかりづらいと思いますので、上記 の 実行結果 の中の ウィジェット が どのような構成 で 配置 されているかを 下図 で示します。
play
メソッドの修正
AI を 選択 するための Dropdown の 作成 と 配置方法 がわかったので、下記 のプログラムのように、play
メソッドを 実行した際 に、それらの Dropdown を作成 し、HBox で 横に並べて配置 するように 修正 します。その際に、リセットボタン を 同じ HBox の 中に入れる ことで、Dropdown の 右に配置 するようにしてみました。
なお、play
メソッドの 仮引数 の 名前 に ai
が 存在する ので、ai モジュール と 名前 が かぶっています。そのため、import ai
で ai モジュール を インポート すると、play
メソッドの ブロックの中 で ai モジュール を 利用 することが できません。そこで、ai モジュール を インポートする際 に、ai_module
という 別の名前 で インポート しました。
-
4 行目:ai モジュールを
ai_module
という 名前 で インポート する -
12 ~ 15 行目:Dropdown の
options
属性の list を 作成 する - 18 ~ 25 行目:〇 と × の AI を選択 する Dropdown を 作成 する
- 31 行目:〇 と × の Dropdown と リセットボタン を 横に配置 する HBox を 作成 する
-
32 行目:
display
で HBox を表示 する
1 import matplotlib.pyplot as plt
2 import ipywidgets as widgets
3 import math
4 import ai as ai_module
5
6 def play(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
7 # seed が None でない場合は、seed を乱数の種として設定する
8 if seed is not None:
9 random.seed(seed)
10
11 # Dropdown の作成の際に必要となる、AI のリストを作成する
12 ai_list = []
13 for i in range(1, 15):
14 ai_name = f"ai{i}s"
15 ai_list.append((ai_name, getattr(ai_module, ai_name)))
16
17 # 〇 と × の Dropdown を作成する
18 dropdown_circle = widgets.Dropdown(
19 options=ai_list,
20 description="〇",
21 )
22 dropdown_cross = widgets.Dropdown(
23 options=ai_list,
24 description="×",
25 )
26
27 # リセットボタンを作成する
28 button = widgets.Button(description="リセット")
29
30 # 〇 と × の dropdown と リセットボタンを横に配置した HBox を作成し、表示する
31 hbox = widgets.HBox([dropdown_circle, dropdown_cross, button])
32 display(hbox)
元と同じなので省略
33
34 Marubatsu.play = play
行番号のないプログラム
from marubatsu import Marubatsu
import matplotlib.pyplot as plt
import ipywidgets as widgets
import math
import ai as ai_module
def play(self, ai, 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)))
# 〇 と × の Dropdown を作成する
dropdown_circle = widgets.Dropdown(
options=ai_list,
description="〇",
)
dropdown_cross = widgets.Dropdown(
options=ai_list,
description="×",
)
# リセットボタンを作成する
button = widgets.Button(description="リセット")
# 〇 と × の 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
修正箇所
from marubatsu import Marubatsu
import matplotlib.pyplot as plt
import ipywidgets as widgets
import math
import ai as ai_module
def play(self, ai, 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)))
# 〇 と × の Dropdown を作成する
+ dropdown_circle = widgets.Dropdown(
+ options=ai_list,
+ description="〇",
+ )
+ dropdown_cross = widgets.Dropdown(
+ options=ai_list,
+ description="×",
+ )
# リセットボタンを作成する
button = widgets.Button(description="リセット")
# 〇 と × の dropdown と リセットボタンを横に配置した HBox を作成し、表示する
+ hbox = widgets.HBox([dropdown_circle, dropdown_cross, button])
+ display(hbox)
元と同じなので省略
Marubatsu.play = play
上記 の 修正後 に、下記 のプログラムで play
メソッドを 実行 すると、実行結果 のように 2 つ の Dropdown と リセットボタン が 横一列に配置 されて 表示 されるようになります。
%matplotlib widget
mb = Marubatsu()
mb.play(ai=[None, None], gui=True);
実行結果(下図は、画像なので操作することはできません)
レイアウトとスタイルの調整
実行結果 から、Dropdown の「説明 の 幅 が 長すぎる」、「項目 の 横幅 が 長すぎる」、リセットボタン の「横幅 が 長すぎる」の 3 点 が 気になる ので、先程説明した ウィジェット の layout
と style
属性を使って Dropdown と ボタン の 表示を調整 します。
- 6、12、19 行目:Dropdown と ボタン の 横幅 を 100 ピクセル に 設定 する
- 7、13 行目:Dropdown の 左の説明 の 横幅 を 20 ピクセル に 設定 する
1 def play(self, ai, 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 )
9 dropdown_cross = widgets.Dropdown(
10 options=ai_list,
11 description="×",
12 layout=widgets.Layout(width="100px"),
13 style={"description_width": "20px"},
14 )
15
16 # リセットボタンを作成する
17 button = widgets.Button(
18 description="リセット",
19 layout=widgets.Layout(width="100px"),
20 )
元と同じなので省略
21
22 Marubatsu.play = play
行番号のないプログラム
def play(self, ai, 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)))
# 〇 と × の 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, 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"},
)
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"),
)
元と同じなので省略
Marubatsu.play = play
上記 の 修正後 に、下記 のプログラムで play
メソッドを 実行 すると、実行結果 のような 表示 が行われます。上記 のプログラムは 試行錯誤 の 結果、ちょうど良い と 筆者が感じた設定 です。人によってデザインの感覚は異なると思いますので、もっと良い設定 があれば、自由に変更 して下さい。
mb.play(ai=[None, None], gui=True);
実行結果(下図は、画像なので操作することはできません)
今回の記事のまとめ
今回の記事では、ipywidgets の Dropdown の 使い方 を説明し、〇×ゲーム の ゲーム盤の上 に AI を 選択するため の Dropdown を 表示 しました。
ただし、今回のプログラムでは、Dropdown を 表示しただけ なので、Dropdown で AI を選択 しても AI が変更される ことは ありません。その処理は次回の記事で実装します。
本記事で入力したプログラム
以下のリンクから、本記事で入力して実行した JupyterLab のファイルを見ることができます。
以下のリンクは、今回の記事で更新した marubatsu.py です。
次回の記事
-
文字の表示 を 行うだけ で、操作を行わない ような ウィジェット が 存在します ↩