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を一から作成する その74 Dropdown による AI を選択するドロップダウンメニューの作成

Last updated at Posted at 2024-04-21

目次と前回の記事

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

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

ルールベースの AI の一覧

ルールベースの AI の一覧については、下記の記事を参照して下さい。

今回の記事の内容

前回の記事で リセットボタン実装 しましたが、異なる AI対戦行う 場合は、キーワード引数 ai の内容を 変更 して、改めて play メソッドを 実行する必要 がある点が 面倒 です。そこで、play メソッドを 実行しなくてもGUIAI選択できる ようにします。

ipywidgets の Dropdown ウィジェットの使い方

GUIAI を選択 できるようにするためには、複数の項目 の中から 1 つを選択 するための GUI が必要になります。ipywidgets には、そのようなウィジェットとして、ドロップダウンメニュー を表す Dropdown や、ラジオボタン を表す RadioButtons などがあります。

選択を行うための ipywidgets のウィジェット

Dropdown は、下図左 のような、選択中項目表示 される ウィジェット で、この上マウスを押す と、下図右 のような ドロップダウンメニュー表示 されます。下図は画像なので操作できないので、実物を見たい方 は、下記リンク先参照 して下さい。

 

RadioButtons は、下図 のように、すべての項目表示 され、左の ラジオボタン項目を選択 する ウィジェット です。実物を見たい方 は、下記リンク先参照 して下さい。

RadioButtons は、選択 できる 項目の数多くなる と、表示範囲広くなる という 欠点 があります。これまでai1 から ai14s まで 多数の AI作成 してきたので、本記事では、Dropdown を使って AI を選択 することにします。

本記事では扱いませんが、ipywidgets には、他にも 選択を行う ための ウィジェット多数用意 されています。興味がある方は、下記のリンク先を参照して下さい。

Dropdown の作成方法

Dropdown は、ipywidgetsDropdown という 関数 を使って 作成 します。例えば、先程 紹介した 画像Dropdown は、下記 のプログラムで 作成 し、ボタンウィジェット同様 に、display を使って 画面に表示 します。Dropdown を呼び出す際に記述する 実引数 については、この後で説明 します。実際に 実行 し、表示された Dropdown の上マウスを押すメニューが表示 され、項目選択できる ことを 確認 して下さい。

import ipywidgets as widgets

dropdown = widgets.Dropdown(
    options=['1', '2', '3'],
    value='2',
    description='Number:',
)
display(dropdown)

実行結果(下図は、画像なので操作することはできません)

Dropdown の属性と Dropdown の仮引数

Dropdown によってよって 作成 された オブジェクト には、下記 のような 属性 があります。それぞれの 属性の意味 についてはこの 後で詳しく説明 します。なお、layoutstyle 属性は、すべてウィジェット共通 します。

属性 意味 データ型
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)

実行結果(下図は、画像なので操作することはできません)

また、Dropdowndisplay表示した後 で、下記 のプログラムのように、Dropdown属性を変更 すると、作成した Dropdown表示内容 が下記の実行結果のように 変化します。なお、description 属性は、Dropdown左に表示 される 説明 を表します。

dropdown2.description = '数字:'

実行結果(先程表示した Dropdown の表示内容が変化します)

Dropdown の主要な属性

Dropdown主要な属性 について 説明 します

options

Dropdown選択 できる 各項目 を表す list です。先程の例 では、options=['1', '2', '3'] のように、'1''2''3' という 3 つの文字列list要素記述 されたので、それら選択できる ようになります。

Dropdown では、選択 できる 項目表す値 と、メニューに その項目表示 する際の 文字列異なるものにする ことが できます。例えば、メニュー のそれぞれの 項目"令和元年""令和二年""令和三年" のように 日本語で表示 するが、それらの 項目を選択 した際には、201920202021 のような 西暦 を表す 数値選択される ようにすることができます。このような、メニュー表示 する 文字列 の事を ラベル と呼びます。

ラベル設定 した Dropdown は、下記 のプログラムのように、それぞれの項目(ラベル, 値) という tuple記述 します。下記 のプログラムを実行すると、下図左 のような Dropdown が表示され、この上マウスを押す と、下図右 のような、設定 した ラベル表示 される ドロップダウンメニュー表示 されます。

dropdown3 = widgets.Dropdown(
    options=[("令和元年", 2019), ("令和二年", 2020), ("令和三年", 2021)],
    description="年:", 
)
display(dropdown3)

実行結果(下図は、画像なので操作することはできません)

 

なお、dropdowndropdown2 のように ラベル設定しない 場合は、それぞれの 項目ラベル には、自動的項目の値文字列型 のデータに 変換した値設定 されます。

valueindexlabel

Dropdown には、選択中の項目 を表す、下記の表の valueindexlabel という 3 つの属性 があります。

属性名 意味
value 選択中項目
index 選択中項目 を表す options 属性要素インデックス
label 選択中項目ラベル

Dropdown作成時 に、Dropdownこれらキーワード引数記述しなかった場合 は、先頭の項目選択 された状態の Dropdown が作成 されます。

例えば、先程作成 した dropdown3各項目インデックスラベル以下 のように 設定 されています。

インデックス ラベル
0 "平成元年" 2019
1 "平成二年" 2020
2 "平成三年" 2021

dropdown3メニューの選択好きな項目変更した後 で、下記 のプログラムを 実行 すると、indexlabelvalue 属性に、現在選択中項目対応 する 上記の表表示 されます。下記実行結果 は、平成三年選択した際 のものです。

print(dropdown3.index, dropdown3.label, dropdown3.value)

実行結果

2 令和三年 2021

また、これらの属性いずれか値を代入 することで、Dropdown選択項目変える ことが できますその際 に、残りの 2 つ属性の値自動的新しく選択 された 項目の値変更 されます。

下記 は、index 属性に 0代入 するプログラムです。実行結果 のように、Dropdown選択項目 の表示が 平成元年 に変更され、labelvalue 属性の値も 変更 されます。

dropdown3.index = 0
print(dropdown3.index, dropdown3.label, dropdown3.value)

実行結果(下図は、画像なので操作することはできません)

0 令和元年 2019

下記 は、label 属性に "令和二年"代入 するプログラムです。実行結果 のように、Dropdown選択項目令和二年 に変更され、indexvalue 属性の値も 変更 されます。

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)の 設定 を表す 属性 です。レイアウト は、ipywidgetsLayout という 関数作成 される オブジェクト設定 します。

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 を作成 するためには、どの AIDropdown選択できる ようにするかを 決める必要 があります。せっかくなので、ルール 1 から ルール 14 までの すべての AI選択できる ようにすることにします。ただし、ai1ai1s のように、同じルールAI複数存在 する場合は、s がつく方選択できる ようにします。従って、ai1s ~ ai14s14 種類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関数の名前ais1 から 14 まで整数 が __ 入る__ という、規則正しい名前 になっています。そのこと と、オブジェクトの中 から 指定した名前 を持つ 属性を計算 する getattr という 組み込み関数利用 することで、上記 のプログラムを 簡潔に記述 することが できます

getattr を利用した AI の関数の取得方法

getattr は、以前の記事説明 していますが、これまでに作成 した marubatsu.pyai.py では 利用していない ので、改めて説明 します。

getattr は、任意の オブジェクト 対して 利用できる関数 で、1 つ目実引数オブジェクト を、2 つ目実引数属性の名前記述 することで、指定した属性返り値 として 返す処理 を行います。

Python では、インポート した モジュール内定義 された 変数関数など を、モジュール名.名前 のように 記述 して 参照 することが できます. を使って 記述している ことからわかるように、モジュール変数関数 などは、インポート した モジュール を表す オブジェクトの属性 として 管理 されています。

従って、例えば ai.ai1s は、ai という モジュール"ai1s" という 名前属性代入 されているので、下記 のプログラムのように、getattr利用 することで、ai.ai1s参照 して、特定の変数代入 することが できます

ai1s = getattr(ai, "ai1s")

上記の ai1sai.ai1s には 同じ関数オブジェクト代入 されているので、下記 のプログラムのように、ゲーム開始時局面 を表す mb実引数 に記述して 呼び出すこと で、どちらも 同じ着手選択 されます。なお、ai1s は、「左上から順空いているマス探し最初に見つかったマス着手 する」という AI なので、(0, 0) が選択されます。

from marubatsu import Marubatsu

mb = Marubatsu()

print(ai1s(mb))
print(ai.ai1s(mb))

実行結果

(0, 0)
(0, 0)

listdict の場合は [] を使って 要素の値参照 することができますが、オブジェクト の場合は [] を使って 属性の値参照 することは できませんgetattr は、listdict における []同じ役割 を果たすものだと 考えると良い でしょう。

getattr詳細 については、下記のリンク先を参照して下さい。

getattr を利用した AI の関数の list の計算

先程説明したように、AI関数の名前 は、ais1 から 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>

上記 のプログラムでは、optionsDropdown選択できる項目のみ要素 として持つ list代入 しました。先程options の所で 説明 したように、そのような場合 は、画面に表示 されるそれぞれの 項目のラベル には、項目文字列型 のデータに 変換した値自動的に設定 されます。関数文字列 のデータに 変換 すると、上記 のような 表示 になるので、先程作成 した AI関数項目 として持つ Dropdownメニュー には、先程の図 のような 内容表示 されます。

AI の名前を表すラベルの設定

先程説明したように、Dropdown では、メニュー表示 する 文字列ラベル と呼び、options要素下記 のような tuple を代入 することで、ラベル項目 とは 別の文字列設定 することが できます

(ラベルを表す文字列, 項目の値)

従って、例えば、ai1sai2sai3s3 つAI の名前ラベル にした Dropdown は、下記 のプログラムで作成できます。

dropdown11 = widgets.Dropdown(
    options=[("ai1s", ai.ai1s), ("ai2s", ai.ai2s), ("ai3s", ai.ai3s)]
)
display(dropdown11)

実行結果(下図は、画像なので操作することはできません)

上図左表示 される Dropdown で、上図右Dropdown の上マウスを押した 場合に 表示 される メニューの図 です。メニューに表示 されるのは "ai1s" という ラベル ですが、このラベル選択した際Dropdown実際選択する項目 は、ai.ai1s という 関数 になります。そのことは、Dropdownai1選択した後 で、下記 のプログラムで 確認 することが できます。先ほど説明したように、Dropdownvalue 属性 には、選択中項目の値代入 されています。そのため、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どちらの手番担当する かを、Dropdowndescription 属性を使って 区別 することにします。下記 は、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 には、作成した ウィジェット配置レイアウト設定 するための HBoxVBox という ウィジェット用意 されています。

HBoxVBox詳細 については、下記のリンク先を参照して下さい。

ipywidgets には HBoxVBox 以外 にもウィジェットの 配置レイアウト設定 するための ウィジェット用意 されており、詳細レイアウトの設定 を行うことが できます。興味がある方は下記のリンク先を参照して下さい。

HBox と VBox の使い方

HBoxウィジェット水平(horizontal)方向に 並べて配置 するための ウィジェット で、下記 のプログラムのように、ipywidgetsHBox実引数 に、横方向並べて配置 する ウィジェット要素 として持つ list を記述 することで 作成 します。作成 した HBoxdisplay で表示 することで、HBox登録 した ウィジェット実行結果 のように、横方向並べて表示 されるようになります。

hbox = widgets.HBox([dropdown_circle, dropdown_cross])
display(hbox)

実行結果(下図は、画像なので操作することはできません)

VBoxウィジェット垂直(vertical)方向に 並べて配置 するための ウィジェット で、下記 のプログラムのように、ipywidgetsVBox実引数 に、縦方向並べて配置 する ウィジェット要素 として持つ list を記述 することで 作成 します。作成 した VBoxdisplay で表示 することで、VBox登録 した ウィジェット実行結果 のように、縦方向並べて表示 されるようになります。

vbox = widgets.VBox([dropdown_circle, dropdown_cross])
display(vbox)

実行結果(下図は、画像なので操作することはできません)

今回の記事では行いませんが、HBoxVBox実引数 に記述する list の要素 には、任意ウィジェット代入 することが できますHBoxVBoxウィジェットの一種な ので、下記 のプログラムのように、HBox 内2 つ並べた DropdownVBox縦に並べて表示 するようなことも できます

vbox = widgets.VBox([hbox, hbox])
display(vbox)

実行結果(下図は、画像なので操作することはできません)

わかりづらいと思いますので、上記実行結果 の中の ウィジェットどのような構成配置 されているかを 下図 で示します。

play メソッドの修正

AI選択 するための Dropdown作成配置方法 がわかったので、下記 のプログラムのように、play メソッドを 実行した際 に、それらの Dropdown を作成 し、HBox横に並べて配置 するように 修正 します。その際に、リセットボタン同じ HBox中に入れる ことで、Dropdown右に配置 するようにしてみました。

なお、play メソッドの 仮引数名前ai存在する ので、ai モジュール名前かぶっています。そのため、import aiai モジュールインポート すると、play メソッドの ブロックの中ai モジュール利用 することが できません。そこで、ai モジュールインポートする際 に、ai_module という 別の名前インポート しました。

  • 4 行目ai モジュールを ai_module という 名前インポート する
  • 12 ~ 15 行目Dropdownoptions 属性の list作成 する
  • 18 ~ 25 行目×AI を選択 する Dropdown作成 する
  • 31 行目×Dropdownリセットボタン横に配置 する HBox作成 する
  • 32 行目displayHBox を表示 する
 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 点気になる ので、先程説明した ウィジェットlayoutstyle 属性を使って 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);

実行結果(下図は、画像なので操作することはできません)

今回の記事のまとめ

今回の記事では、ipywidgetsDropdown使い方 を説明し、〇×ゲームゲーム盤の上AI選択するためDropdown表示 しました。

ただし、今回のプログラムでは、Dropdown表示しただけ なので、Dropdown で AI を選択 しても AI が変更される ことは ありません。その処理は次回の記事で実装します。

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

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

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

次回の記事

  1. 文字の表示行うだけ で、操作を行わない ような ウィジェット存在します

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?