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を一から作成する その145 Mbtee_Anim での選択中のノードに対するフレームの移動

Last updated at Posted at 2025-01-05

目次と前回の記事

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

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

リンク 説明
marubatsu.py Marubatsu、Marubatsu_GUI クラスの定義
ai.py AI に関する関数
test.py テストに関する関数
util.py ユーティリティ関数の定義。現在は gui_play のみ定義されている
tree.py ゲーム木に関する Node、Mbtree クラスの定義
gui.py GUI に関する処理を行う基底クラスとなる GUI クラスの定義

AI の一覧とこれまでに作成したデータファイルについては、下記の記事を参照して下さい。

選択中のノードに対するフレームの移動

今回の記事も前回の記事に引き続き Mbtree_Anim による αβ 法の評価値の計算手順のアニメーションの改良を行います。現状では 特定のノード評価値の計算手順 を、その 子ノードの評価値だけに注目しながら確認するのが困難 であるという欠点があります。

わかりづらいと思いますので、具体例を挙げて説明します。例えば、下図の 6 フレーム目の赤枠のノードの 子ノードの評価値が計算される のは、その下の図の 9、21、25 フレーム目 で、赤枠のノードの 評価値が確定 するのは一番下の図の 27 フレーム目 です。そのため、それらのフレームを表示する ためには、求める情報が表示されているか どうかを 確認しながら「>」ボタンを何度もクリック する必要があります。

また、上図のような深さが深いノードの場合は「>」ボタンを何度かクリックするだけで済みますが、深さが浅いノードの場合 は、次に求める情報が表示される フレームがかなり先 になるため、「>」ボタンでそのようなフレームを表示 することは 非常に困難 です。

実際に、最も浅いルートノードの場合は下図のように 最初 の子ノードの評価値が計算されるのが 9351 フレーム目その次 の子ノードの評価値が計算されるのが 14327 フレーム目 なので、これらのフレームを「>」ボタンだけで探し出すのは 現実的ではありません

この問題を解決するために、Mbtree_Anim の上部に 以下のような操作を行うためのボタンを追加 することにします。

  • 選択中のノード処理が開始されたフレームに移動 する
  • 選択中のノードの、前の子ノードの評価値が計算されたフレーム に移動する。前の子ノードが存在しない場合は処理が開始されたフレームに移動する
  • 選択中のノードの、次の子ノードの評価値が計算されたフレーム に移動する。次の子ノードが存在しない場合は評価値が確定したフレームに移動する
  • 選択中のノードの 評価値が確定したフレーム に移動する

Mbtree_Anim クラスの create_widgets メソッドの修正

まず、下記のプログラムのように create_widgets メソッドを修正して 上記のボタンを作成する ようにします。ボタンの表示 はそれぞれ <<、<、>、>> にし、ボタンの説明 を表示する ラベルを作成 しました。

  • 11 行目:αβ 法での評価値の計算の手順のアニメーションであるかどうかは 7 行目で判定しており、その場合は abfig 属性に None ではない値が代入されるので、abfig 属性が None ではない場合にボタンなどを作成するようにする
  • 12 行目:ボタンの説明を表示する Label ウィジェット を作成する
  • 13 ~ 16:4 つのボタンを作成する
 1  import ipywidgets as widgets
 2  import matplotlib.pyplot as plt
 3  from tree import Mbtree_Anim
 4  
 5  def create_widgets(self):
元と同じなので省略
 6      with plt.ioff():
元と同じなので省略
 7          if self.isscore and hasattr(self.mbtree, "ablist_by_score"):
 8              self.abfig = plt.figure(figsize=(5, 1))
元と同じなので省略            
 9          else:
10              self.abfig = None
元と同じなので省略            
11      if self.abfig is not None:
12          self.node_label = widgets.Label("選択中のノード内の移動")
13          self.node_first_button = self.create_button("<<", width=40)
14          self.node_prev_button = self.create_button("<", width=30)
15          self.node_next_button = self.create_button(">", width=30)
16          self.node_last_button = self.create_button(">>", width=40)
17  
18  Mbtree_Anim.create_widgets = create_widgets
行番号のないプログラム
import ipywidgets as widgets
import matplotlib.pyplot as plt
from tree import Mbtree_Anim

def create_widgets(self):
    self.play = widgets.Play(max=self.nodenum - 1, interval=500)
    self.prev_button = self.create_button("<", width=30)
    self.next_button = self.create_button(">", width=30)
    self.frame_slider = widgets.IntSlider(max=self.nodenum - 1, description="frame")
    self.interval_slider = widgets.IntSlider(value=500, min=1, max=2000, description="interval")
    widgets.jslink((self.play, "value"), (self.frame_slider, "value"))    
    widgets.jslink((self.play, "interval"), (self.interval_slider, "value"))

    with plt.ioff():
        self.fig = plt.figure(figsize=[self.width * self.size,
                                        self.height * self.size])
        self.ax = self.fig.add_axes([0, 0, 1, 1])
        self.fig.canvas.toolbar_visible = False
        self.fig.canvas.header_visible = False
        self.fig.canvas.footer_visible = False
        self.fig.canvas.resizable = False 
        if self.isscore and hasattr(self.mbtree, "ablist_by_score"):
            self.abfig = plt.figure(figsize=(5, 1))
            self.abax = self.abfig.add_axes([0, 0, 1, 1])
            self.abfig.canvas.toolbar_visible = False
            self.abfig.canvas.header_visible = False
            self.abfig.canvas.footer_visible = False
            self.abfig.canvas.resizable = False 
        else:
            self.abfig = None
            
    if self.abfig is not None:
        self.node_label = widgets.Label("選択中のノード内の移動")
        self.node_first_button = self.create_button("<<", width=40)
        self.node_prev_button = self.create_button("<", width=30)
        self.node_next_button = self.create_button(">", width=30)
        self.node_last_button = self.create_button(">>", width=40)

Mbtree_Anim.create_widgets = create_widgets
修正箇所
import ipywidgets as widgets
import matplotlib.pyplot as plt
from tree import Mbtree_Anim

def create_widgets(self):
元と同じなので省略
    with plt.ioff():
元と同じなので省略
        if self.isscore and hasattr(self.mbtree, "ablist_by_score"):
            self.abfig = plt.figure(figsize=(5, 1))
元と同じなので省略            
        else:
            self.abfig = None
元と同じなので省略            
+   if self.abfig is not None:
+       self.node_label = widgets.Label("選択中のノード内の移動")
+       self.node_first_button = self.create_button("<<", width=40)
+       self.node_prev_button = self.create_button("<", width=30)
+       self.node_next_button = self.create_button(">", width=30)
+       self.node_last_button = self.create_button(">>", width=40)

Mbtree_Anim.create_widgets = create_widgets

Mbtree_Anim クラスの display_widgets メソッドの修正

次に、下記のプログラムのように display_widgets メソッドを修正して作成したボタンとラベルを表示するようにします。

  • 6 ~ 8 行目abfig 属性の値が None でない場合に先程作成したラベルとボタンを横に並べた Hbox を作成し、それを表示するように修正する
 1  def display_widgets(self):
 2      hbox = widgets.HBox([self.play, self.prev_button, self.next_button, self.frame_slider, self.interval_slider])
 3      if self.abfig is None:
 4          display(widgets.VBox([hbox, self.fig.canvas])) 
 5      else:
 6          hbox2 = widgets.HBox([self.node_label, self.node_first_button, self.node_prev_button,
 7                                self.node_next_button, self.node_last_button])
 8          display(widgets.VBox([hbox, hbox2, self.abfig.canvas, self.fig.canvas])) 
 9         
10  Mbtree_Anim.display_widgets = display_widgets
行番号のないプログラム
def display_widgets(self):
    hbox = widgets.HBox([self.play, self.prev_button, self.next_button, self.frame_slider, self.interval_slider])
    if self.abfig is None:
        display(widgets.VBox([hbox, self.fig.canvas])) 
    else:
        hbox2 = widgets.HBox([self.node_label, self.node_first_button, self.node_prev_button,
                              self.node_next_button, self.node_last_button])
        display(widgets.VBox([hbox, hbox2, self.abfig.canvas, self.fig.canvas])) 
        
Mbtree_Anim.display_widgets = display_widgets
修正箇所
def display_widgets(self):
    hbox = widgets.HBox([self.play, self.prev_button, self.next_button, self.frame_slider, self.interval_slider])
    if self.abfig is None:
        display(widgets.VBox([hbox, self.fig.canvas])) 
    else:
-       display(widgets.VBox([hbox, self.abfig.canvas, self.fig.canvas])) 
+       hbox2 = widgets.HBox([self.node_label, self.node_first_button, self.node_prev_button,
+                             self.node_next_button, self.node_last_button])
+       display(widgets.VBox([hbox, hbox2, self.abfig.canvas, self.fig.canvas])) 
        
Mbtree_Anim.display_widgets = display_widgets

上記の修正後に下記のプログラムで calc_score_by_ab によって αβ 法で評価値を計算した mbtree に対して Mbtree_Anim を実行すると、実行結果のように 作成したラベルとボタンが表示される ようになったことが確認できます。

from tree import Mbtree

mbtree = Mbtree(algo="df")
mbtree.calc_score_by_ab(mbtree.root)
Mbtree_Anim(mbtree, isscore=True)

実行結果

Mbtree_Anim クラスの create_event_handler メソッドの修正

次に、create_event_handler の中に 4 つのボタン をクリックした際に実行する イベントハンドラを定義 する必要があります。それぞれのボタンに対するイベントハンドラにどのようなプログラムを記述すれば良いかについて少し考えてみて下さい。

選択中のノードの処理が開始されたフレームの計算方法

既に定義済の、アニメーションの フレームを 1 つ前に移動 するボタンの イベントハンドラ は下記のプログラムように定義されています。このプログラムでは、アニメーションのフレーム を表す self.play.value の値を 1 減らしゲーム木の描画を更新 するために update_gui メソッドを呼び出しています。

def on_prev_button_clicked(b=None):
    self.play.value -= 1
    self.update_gui()

選択中のノードの処理が開始されたフレームに移動する << ボタン をクリックした際に実行する イベントハンドラ は、上記のプログラムを参考に下記のプログラムのように定義できます。下記の「self.play.value に選択中のノードの処理が開始されたフレームの番号を代入する」の部分にどのようなプログラムを記述すれば良いかについて少し考えてみて下さい。

def on_node_first_button_clicked(b=None):
    self.play.value に選択中のノードの処理が開始されたフレームの番号を代入する
    self.update_gui()

前回の記事でノードに対して行われる処理を下記の表の 4 つに分類 し、アニメーションのフレームとして下記の 4 種類の処理を行ったフレームのデータを記録 しました。また、アニメーションの各フレームで行われた処理を記録する ablist_by_score の要素の中に フレームの状態を表す文字列を記録 しました。

行われた処理 状態を表す文字列
ノードの処理が開始 "start"
子ノードの評価値の計算の直後 "score"
α 値または β 値に対する処理 "update"
ノードの処理が終了して評価値が確定 "end"

αβ 法 では、ノードに対する処理下記の順番 で行われます。なお、以降の説明 では 行う処理 を上記の表の 状態を表す文字列で表記 します。

  1. "start" の処理
  2. "score""update" の処理
    • 2 の処理は子ノードの数だけ繰り返す
    • ただし、枝狩りが行われた場合は残りの子ノードに対する 2 の処理は省略する
  3. "end" の処理

上記から、すべてのノード は必ず "start" 状態が記録されたフレーム ではじまり"end" の状態が記録されたフレーム で終わる ことがわかります。

選択中のノードの処理が開始 されたフレームの 状態は "start" なので、そのフレームは以下のアルゴリズムで探すことができます。

  1. 現在のフレーム の状態が "start" の場合は そのフレーム が求めるフレームである
  2. そうでない場合は、現在のフレームから 1 つずつフレームをさかのぼって 以下の条件を すべて満たすフレームが見つかるまで チェックし、見つかったフレームが求めるフレームである
    • そのフレームで行われる処理が、現在のフレームと同じノード の処理を行っている
    • そのフレームで行われる 処理の状態"start" である

<< ボタンをクリックした際のイベントハンドラの定義

下記はそのように << ボタン をクリックした際のイベントハンドラを on_node_first_button_clicked という名前で定義したプログラムです。

  • 2 行目:現在のフレームの番号を frame に代入する
  • 3 行目:現在のフレームの選択中のノードを selectednode に代入する
  • 4 行目:現在のフレームの状態を selectedstatus に代入する。なお ablist_by_score の要素に代入されている tuple の内容(α 値, β 値, 子ノードの評価値, フレームの状態を表す文字列) なので、フレームの状態は tuple の 3 番の要素に代入されている
  • 5、6 行目:現在のフレームの状態が "start" の場合はフレームの移動を行う必要がないので return 文を実行してイベントハンドラの処理を終了する
  • 7 ~ 12 行目:求めるフレームが見つかるまで無限ループで繰り返し処理を行う。なお、先程説明したように、ノードの処理は 必ず "start" の状態で始まる ので、求めるフレームは 必ず見つかり無限ループから抜ける ことは保証されている
  • 8 行目frame を 1 減らして一つ前のフレームにさかのぼる
  • 9、10 行目:さかのぼったフレームのノードと状態を nodestatus に代入する
  • 11、12 行目nodeselectednode と等しく、status"start" であればそれが求めるフレームなので break 文で無限ループを抜ける
  • 13、14 行目:繰り返し処理を抜けた時点の frame に求めるフレームの番号が代入されているので、それを self.play.value に代入してフレームを移動し、update_gui メソッドを呼び出してゲーム木の描画を更新する
 1  def on_node_first_button_clicked(b=None):
 2      frame = self.play.value
 3      selectednode = self.mbtree.nodelist_by_score[frame]
 4      selectedstatus = self.mbtree.ablist_by_score[frame][3]
 5      if selectedstatus == "start":
 6          return
 7      while True:
 8          frame -= 1
 9          node = self.mbtree.nodelist_by_score[frame]
10          status = self.mbtree.ablist_by_score[frame][3]
11          if node == selectednode and status == "start":
12              break
13      self.play.value = frame
14      self.update_gui()

前の子ノードの評価値が計算されたフレームの計算方法

下記は先ほど示した αβ 法でのノードに対する処理の順番を再掲したものです。

  1. "start" の処理
  2. "score""update" の処理
    • 2 の処理は子ノードの数だけ繰り返す
    • ただし、枝狩りが行われた場合は残りの子ノードに対する 2 の処理は省略する
  3. "end" の処理

上記から、選択中のノードの前の子ノードの評価値が計算されたフレームが 存在する場合 は、そのフレームを以下のアルゴリズムで求めることができます。先ほどのアルゴリズムとの違い は、「現在のフレーム"start" であることを チェックしない」点と、下記の 太字 の「処理の状態"start" ではなく、"score" であることをチェックする」点です。

  • 現在のフレームから 1 つずつフレームをさかのぼって以下の条件をすべて満たすフレームが見つかるまでチェックし、見つかったフレームが求めるフレームである
    • そのフレームで行われる処理が、現在のフレームと同じノードの処理を行っている
    • そのフレームで行われる処理の状態が "score" である

前の子ノードが 存在しない場合処理が開始されたフレームに移動する ので、先程と同じアルゴリズム で求めることができます。

実は、前の子ノードが存在するかどうかで アルゴリズムを分ける必要はなくチェックする条件 を「処理の状態が "start" または "score"」のようにすることで、下記のアルゴリズムで 一つにまとめる ことができます。その理由について少し考えてみて下さい。

なお、下記の 太字の部分以外 は、最初の選択中のノードの処理が開始されたフレームを求めるアルゴリズムと 全く同じ です。

  1. 現在のフレームの状態が "start" の場合はそのフレームが求めるフレームである
  2. そうでない場合は、現在のフレームから 1 つずつフレームをさかのぼって以下の条件をすべて満たすフレームが見つかるまでチェックし、見つかったフレームが求めるフレームである
    • そのフレームで行われる処理が、現在のフレームと同じノードの処理を行っている
    • そのフレームで行われる処理の状態が "start" または "score" である

上記のようにまとめることができる理由は以下の通りです。

  • 前の子ノードが 存在する場合 は、フレームをさかのぼった場合に同じノードで 状態が "score" になるフレームが、同じノードで 状態が "start" になるフレーム よりも前に見つかる。従って、"start" または "score" のフレームを探した際に みつかるのは "score" のフレーム になる
  • 前の子ノードが 存在しない場合 は、フレームをさかのぼった場合に同じノードで 状態が "score" になるフレームは 存在しない。従って、"start" または "score" のフレームを探した際に みつかるのは "start" のフレーム になる

< ボタンをクリックした際のイベントハンドラの定義

下記はそのように on_node_prev_button_clicked を定義したプログラムです。11 行目 が変わった 以外on_node_first_button_clicked全く同じ です。

 1  def on_node_first_button_clicked(b=None):
 2      frame = self.play.value
 3      selectednode = self.mbtree.nodelist_by_score[frame]
 4      selectedstatus = self.mbtree.ablist_by_score[frame][3]
 5      if selectedstatus == "start":
 6          return
 7      while True:
 8          frame -= 1
 9          node = self.mbtree.nodelist_by_score[frame]
10          status = self.mbtree.ablist_by_score[frame][3]
11          if node == selectednode and (status == "start" or status == "score"):
12              break
13      self.play.value = frame
14      self.update_gui()
on_node_first_button_clickedとの違い
-def on_node_first_button_clicked(b=None):
+def on_node_prev_button_clicked(b=None):
    frame = self.play.value
    selectednode = self.mbtree.nodelist_by_score[frame]
    selectedstatus = self.mbtree.ablist_by_score[frame][3]
    if selectedstatus == "start":
        return
    while True:
        frame -= 1
        node = self.mbtree.nodelist_by_score[frame]
        status = self.mbtree.ablist_by_score[frame][3]
-       if node == selectednode and status == "start":
+       if node == selectednode and (status == "start" or status == "score"):
            break
    self.play.value = frame
    self.update_gui()

選択中のノードの評価値が確定したフレームの計算方法

評価値が確定 したフレームは現在のフレームから 後ろに向かって "end" の状態 のフレームを 探せばよい ので、先程と同様の考え方で下記のアルゴリズムで計算できます。選択中のノードの処理が開始されたフレームのアルゴリズムとの違いは 太字 の「"start""end" になった」点と、「フレームをさかのぼるのではなく 後ろに向かって に辿る」点だけです。

  1. 現在のフレームの状態が "end" の場合はそのフレームが求めるフレームである
  2. そうでない場合は、現在のフレームから 1 つずつフレームを 後ろに向かって辿って 以下の条件をすべて満たすフレームが見つかるまでチェックし、見つかったフレームが求めるフレームである
    • そのフレームで行われる処理が、現在のフレームと同じノードの処理を行っている
    • そのフレームで行われる処理の状態が "end" である

従って、>> ボタンをクリックした際のイベントハンドラは下記のプログラムのように定義できます。

 1  def on_node_last_button_clicked(b=None):
 2      frame = self.play.value
 3      selectednode = self.mbtree.nodelist_by_score[frame]
 4      selectedstatus = self.mbtree.ablist_by_score[frame][3]
 5      if selectedstatus == "end":
 6          return
 7      while True:
 8          frame += 1
 9          node = self.mbtree.nodelist_by_score[frame]
10          status = self.mbtree.ablist_by_score[frame][3]
11          if node == selectednode and status == "end":
12              break
13      self.play.value = frame
14      self.update_gui()
on_node_first_button_clickedとの違い
-def on_node_first_button_clicked(b=None):
+def on_node_prev_button_clicked(b=None):
    frame = self.play.value
    selectednode = self.mbtree.nodelist_by_score[frame]
    selectedstatus = self.mbtree.ablist_by_score[frame][3]
-   if selectedstatus == "start":
+   if selectedstatus == "end":
        return
    while True:
-       frame -= 1
+       frame += 1
        node = self.mbtree.nodelist_by_score[frame]
        status = self.mbtree.ablist_by_score[frame][3]
-       if node == selectednode and status == "start":
+       if node == selectednode and status == "end":
            break
    self.play.value = frame
    self.update_gui()

次の子ノードの評価値が計算されたフレームの計算方法

同様に、次の子ノードの評価値が計算されたフレームは下記のアルゴリズムで計算できます。上記との違いは 太字 の「"end" または "score"」の部分だけです。

  1. 現在のフレームの状態が "end" の場合はそのフレームが求めるフレームである
  2. そうでない場合は、現在のフレームから 1 つずつフレームを後ろに向かって辿って以下の条件をすべて満たすフレームが見つかるまでチェックし、見つかったフレームが求めるフレームである
    • そのフレームで行われる処理が、現在のフレームと同じノードの処理を行っている
    • そのフレームで行われる処理の状態が "end" または "score" である

従って、> ボタンをクリックした際のイベントハンドラは下記のプログラムのように定義できます。

 1 def on_node_last_button_clicked(b=None):
 2     frame = self.play.value
 3     selectednode = self.mbtree.nodelist_by_score[frame]
 4     selectedstatus = self.mbtree.ablist_by_score[frame][3]
 5     if selectedstatus == "end":
 6         return
 7     while True:
 8         frame += 1
 9         node = self.mbtree.nodelist_by_score[frame]
10         status = self.mbtree.ablist_by_score[frame][3]
11         if node == selectednode and (status == "end" or status =="score"):
12             break
13     self.play.value = frame
14     self.update_gui()
on_node_last_button_clickedとの違い
-def on_node_last_button_clicked(b=None):
+def on_node_next_button_clicked(b=None):
    frame = self.play.value
    selectednode = self.mbtree.nodelist_by_score[frame]
    selectedstatus = self.mbtree.ablist_by_score[frame][3]
    if selectedstatus == "end":
        return
    while True:
        frame += 1
        node = self.mbtree.nodelist_by_score[frame]
        status = self.mbtree.ablist_by_score[frame][3]
-       if node == selectednode and status == "end":
+       if node == selectednode and (status == "end" or status =="score"):
            break
    self.play.value = frame
    self.update_gui()

フレームを探して移動する関数の定義

上記で定義した 4 つのイベントハンドラ は、以下の点を除くと全く同じ処理を行う ので、異なる部分 のデータを 仮引数に代入 して処理を行う 関数を定義 することにします。

  • 5 行目で selectedstatus と比較するデータ
  • 8 行目で frame に加算する値
  • 11 行目で status の条件

またその違いは以下の表のようになっています。

<< < > >>
5 行目 "start" "start" "end" "end"
8 行目 -1 -1 +1 +1
11 行目 "start" "start" または "score" "end" または "score" "end"

定義する関数の名前は change_frame、それぞれの仮引数の名前は edge_status1diff2status_list としました。

11 行目のデータ複数存在する場合がある ので list や tuple で表現するのが一般的3ですが、そのような場合は上記の 11 行目 のプログラムで == 演算子を利用することはできません。どのように 11 行目を記述すれば良いかを少し考えてみて下さい。

ある値が複数の値のいずれかに一致するか を判定する際に、11 行目では == 演算子と or 演算子を利用していますが、複数の値を ["end", "score"] のように list などの シーケンス型 で記述した場合は in 演算子 を利用して status in ["end", "score"] のように記述できます。従って、change_frame は下記のプログラムのように定義できます。on_node_first_button_clicked との違いは仮引数と 5、8、11 行目だけです。

 1  def change_frame(edge_status, diff, status_list):
 2      frame = self.play.value
 3      selectednode = self.mbtree.nodelist_by_score[frame]
 4      selectedstatus = self.mbtree.ablist_by_score[frame][3]
 5      if selectedstatus == edge_status:
 6          return
 7      while True:
 8          frame += diff
 9          node = self.mbtree.nodelist_by_score[frame]
10          status = self.mbtree.ablist_by_score[frame][3]
11          if node == selectednode and status in status_list:
12              break
13      self.play.value = frame
14      self.update_gui()
行番号のないプログラム
def change_frame(edge_status, diff, status_list):
    frame = self.play.value
    selectednode = self.mbtree.nodelist_by_score[frame]
    selectedstatus = self.mbtree.ablist_by_score[frame][3]
    if selectedstatus == edge_status:
        return
    while True:
        frame += diff
        node = self.mbtree.nodelist_by_score[frame]
        status = self.mbtree.ablist_by_score[frame][3]
        if node == selectednode and status in status_list:
            break
    self.play.value = frame
    self.update_gui()
on_node_first_button_clickedとの違い
-def on_node_first_button_clicked(b=None):
+def change_frame(edge_status, diff, status_list):
    frame = self.play.value
    selectednode = self.mbtree.nodelist_by_score[frame]
    selectedstatus = self.mbtree.ablist_by_score[frame][3]
-   if selectedstatus == "start":
+   if selectedstatus == edge_status:
        return
    while True:
-       frame -= 1
+       frame += diff
        node = self.mbtree.nodelist_by_score[frame]
        status = self.mbtree.ablist_by_score[frame][3]
-       if node == selectednode and status == "start":
+       if node == selectednode and status in status_list:
            break
    self.play.value = frame
    self.update_gui()

Mbtree_Anim の create_event_handler メソッドの修正

下記は change_frame を利用して 4 つのイベントハンドラの定義と登録 を行うように create_event_handler を修正したプログラムです。

  • 2 ~ 15 行目:上記の change_frame をローカル関数として定義する
  • 17 ~ 27 行目:4 つのボタンのイベントハンドラを、適切な実引数を記述して change_frame を呼び出すように定義する
  • 29 ~ 33 行目:αβ 法による評価値の計算手順を表示することを表す abfig 属性が None でない場合にボタンとイベントハンドラを結び付ける。なお、利用しないイベントハンドラを定義してもプログラムの動作には影響を与えないので、イベントハンドラの定義を 29 行目の if 文のブロックの中に記述する必要はない
 1 def create_event_handler(self):
元と同じなので省略
 2     def change_frame(edge_status, diff, status_list):
 3         frame = self.play.value
 4         selectednode = self.mbtree.nodelist_by_score[frame]
 5         selectedstatus = self.mbtree.ablist_by_score[frame][3]
 6         if selectedstatus == edge_status:
 7             return
 8         while True:
 9             frame += diff
10             node = self.mbtree.nodelist_by_score[frame]
11             status = self.mbtree.ablist_by_score[frame][3]
12             if node == selectednode and status in status_list:
13                 break
14         self.play.value = frame
15         self.update_gui()
16             
17     def on_node_first_button_clicked(b=None):
18         change_frame("start", -1, ["start"])
19             
20     def on_node_prev_button_clicked(b=None):
21         change_frame("start", -1, ["start", "score"])
22 
23     def on_node_next_button_clicked(b=None):
24         change_frame("end", 1, ["end", "score"])
25         
26     def on_node_last_button_clicked(b=None):
27         change_frame("end", 1, ["end"])
28     
29     if self.abfig is not None:
30         self.node_first_button.on_click(on_node_first_button_clicked)
31         self.node_prev_button.on_click(on_node_prev_button_clicked)
32         self.node_next_button.on_click(on_node_next_button_clicked)
33         self.node_last_button.on_click(on_node_last_button_clicked)
34         
35 Mbtree_Anim.create_event_handler = create_event_handler
行番号のないプログラム
def create_event_handler(self):
    def on_play_changed(changed):
        self.update_gui()
        
    def on_prev_button_clicked(b=None):
        self.play.value -= 1
        self.update_gui()
        
    def on_next_button_clicked(b=None):
        self.play.value += 1
        self.update_gui()

    self.prev_button.on_click(on_prev_button_clicked)
    self.next_button.on_click(on_next_button_clicked)

    self.play.observe(on_play_changed, names="value")
    
    def change_frame(edge_status, diff, status_list):
        frame = self.play.value
        selectednode = self.mbtree.nodelist_by_score[frame]
        selectedstatus = self.mbtree.ablist_by_score[frame][3]
        if selectedstatus == edge_status:
            return
        while True:
            frame += diff
            node = self.mbtree.nodelist_by_score[frame]
            status = self.mbtree.ablist_by_score[frame][3]
            if node == selectednode and status in status_list:
                break
        self.play.value = frame
        self.update_gui()
            
    def on_node_first_button_clicked(b=None):
        change_frame("start", -1, ["start"])
            
    def on_node_prev_button_clicked(b=None):
        change_frame("start", -1, ["start", "score"])

    def on_node_next_button_clicked(b=None):
        change_frame("end", 1, ["end", "score"])
        
    def on_node_last_button_clicked(b=None):
        change_frame("end", 1, ["end"])
    
    if self.abfig is not None:
        self.node_first_button.on_click(on_node_first_button_clicked)
        self.node_prev_button.on_click(on_node_prev_button_clicked)
        self.node_next_button.on_click(on_node_next_button_clicked)
        self.node_last_button.on_click(on_node_last_button_clicked)
        
Mbtree_Anim.create_event_handler = create_event_handler
        
修正箇所
def create_event_handler(self):
元と同じなので省略
+   def change_frame(edge_status, diff, status_list):
+       frame = self.play.value
+       selectednode = self.mbtree.nodelist_by_score[frame]
+       selectedstatus = self.mbtree.ablist_by_score[frame][3]
+       if selectedstatus == edge_status:
+           return
+       while True:
+           frame += diff
+           node = self.mbtree.nodelist_by_score[frame]
+           status = self.mbtree.ablist_by_score[frame][3]
+           if node == selectednode and status in status_list:
+               break
+       self.play.value = frame
+       self.update_gui()
            
+   def on_node_first_button_clicked(b=None):
+       change_frame("start", -1, ["start"])
            
+   def on_node_prev_button_clicked(b=None):
+       change_frame("start", -1, ["start", "score"])

+   def on_node_next_button_clicked(b=None):
+       change_frame("end", 1, ["end", "score"])
        
+   def on_node_last_button_clicked(b=None):
+       change_frame("end", 1, ["end"])
    
+   if self.abfig is not None:
+       self.node_first_button.on_click(on_node_first_button_clicked)
+       self.node_prev_button.on_click(on_node_prev_button_clicked)
+       self.node_next_button.on_click(on_node_next_button_clicked)
+       self.node_last_button.on_click(on_node_last_button_clicked)
        
Mbtree_Anim.create_event_handler = create_event_handler

ボタンの動作の確認

上記の修正後に下記のプログラムを実行し、4 つのボタンの動作が正しく行われるかどうかを確認 します。

Mbtree_Anim(mbtree, isscore=True)

下記はルートノードの処理が開始された最初の 0 番のフレームで > ボタンをクリック した場合の図で、ルートノードの 最初の子ノードの評価値が計算された 9351 フレーム目が正しく表示 されます。また、子ノードの評価値が計算されたフレーム であることを表す 薄黄色の背景色 で表示されることが確認できます。

もう一度 > ボタン をクリックすると、下図のように その次の子ノード の評価値が計算された 14327 フレーム目薄黄色の背景色で表示 されます。

図は省略しますが、上図で < ボタン をクリックすると 9351 フレーム目 が、さらに < ボタン をクリックするとルートノードの 処理が開始された 0 フレーム目 が表示されます。

>> ボタン をクリックするとルートノードの 評価値が確定 した下図の 73185 フレーム目薄紫色の背景色 で表示されます。また、ルートノードが選択 されている場合に > ボタンを何度かクリック すると、最後には下図のフレームが表示 されます。

図は省略しますが、<< ボタン をクリックするか、何度も < ボタンをクリック するとルートノードの 処理が開始 された 0 フレーム目 が 白色の背景色 で表示されます。

上記ではルートノードが選択された状態で 4 つのボタンが正しく動作するかどうかを確認しまして。余裕がある方は 他のノードが選択された様々なフレーム で 4 つのボタンをクリックして、動作が正しく行われるかどうかを確認 してみて下さい。

ボタンの表示に関する改良

ノードの 評価値が確定したフレーム では > ボタン>> ボタン をクリックしても フレームは移動しません が、上図のように > ボタンと >> ボタンが 緑色で表示される のでそれらのボタンを押した際にフレームが 移動するように見えてしまうという問題 があります。同様に ノードの処理を開始 したフレームでも下図のようにクリックしてもフレームが移動しない < ボタンと << ボタン緑色で表示される という問題があります。

そこで、ボタンをクリックしても フレームが移動しない場合 は、そのボタンを 灰色で表示して操作できないようにする という改良を行うことにします。

Mbtree_GUI クラスの update_gui メソッドの修正

<< ボタン< ボタン をクリックしても フレームが移動しない のはその フレームの状態が "start" の場合です。>> ボタン> ボタン を押してもフレームが移動しないのはその フレームの状態が "end" の場合 です。従って、update_gui メソッドを下記のプログラムのように修正します。

  • 4 行目abfig 属性が None ではない場合に、現在のフレームの状態を ablist_by_score から取り出して status に代入する
  • 5 ~ 7 行目set_button_status を利用して、status"start" の場合に << ボタンと < ボタンを灰色で表示して操作できないようにする
  • 8 ~ 10 行目set_button_status を利用して、status"end" の場合に > ボタンと >> ボタンを灰色で表示して操作できないようにする
 1  def update_gui(self):
元と同じなので省略
 2      if self.abfig is not None:
 3          self.update_ab()
 4          status = self.mbtree.ablist_by_score[self.play.value][3]
 5          disabled = status == "start"
 6          self.set_button_status(self.node_first_button, disabled=disabled) 
 7          self.set_button_status(self.node_prev_button, disabled=disabled)
 8          disabled = status == "end"
 9          self.set_button_status(self.node_next_button, disabled=disabled)  
10          self.set_button_status(self.node_last_button, disabled=disabled)
11 
12      disabled = self.play.value == 0
13      self.set_button_status(self.prev_button, disabled=disabled)
14      disabled = self.play.value == self.nodenum - 1
15      self.set_button_status(self.next_button, disabled=disabled)
16     
17  Mbtree_Anim.update_gui = update_gui
行番号のないプログラム
def update_gui(self):
    self.ax.clear()
    self.ax.set_xlim(-1, self.width - 1)
    self.ax.set_ylim(-1, self.height - 1)   
    self.ax.invert_yaxis()
    self.ax.axis("off")   
    
    self.selectednode = self.nodelist[self.play.value]
    self.centernode = self.selectednode
    if self.mbtree.algo == "bf":
        if self.centernode.depth > 0:
            self.centernode = self.centernode.parent
    while self.centernode.depth > 6:
        self.centernode = self.centernode.parent
    if self.centernode.depth <= 4:
        maxdepth = self.centernode.depth + 1
    elif self.centernode.depth == 5:
        maxdepth = 7
    else:
        maxdepth = 9
    self.mbtree.draw_subtree(centernode=self.centernode, selectednode=self.selectednode,
                            anim_frame=self.play.value, isscore=self.isscore, 
                            ax=self.ax, maxdepth=maxdepth, size=self.size)
    if self.abfig is not None:
        self.update_ab()
        status = self.mbtree.ablist_by_score[self.play.value][3]
        disabled = status == "start"
        self.set_button_status(self.node_first_button, disabled=disabled)                           
        self.set_button_status(self.node_prev_button, disabled=disabled)
        disabled = status == "end"
        self.set_button_status(self.node_next_button, disabled=disabled)                           
        self.set_button_status(self.node_last_button, disabled=disabled)

    disabled = self.play.value == 0
    self.set_button_status(self.prev_button, disabled=disabled)
    disabled = self.play.value == self.nodenum - 1
    self.set_button_status(self.next_button, disabled=disabled)
    
Mbtree_Anim.update_gui = update_gui 
修正箇所
def update_gui(self):
元と同じなので省略
+   if self.abfig is not None:
+       self.update_ab()
+       status = self.mbtree.ablist_by_score[self.play.value][3]
+       disabled = status == "start"
+       self.set_button_status(self.node_first_button, disabled=disabled)                           
+       self.set_button_status(self.node_prev_button, disabled=disabled)
+       disabled = status == "end"
+       self.set_button_status(self.node_next_button, disabled=disabled)                           
+       self.set_button_status(self.node_last_button, disabled=disabled)

    disabled = self.play.value == 0
    self.set_button_status(self.prev_button, disabled=disabled)
    disabled = self.play.value == self.nodenum - 1
    self.set_button_status(self.next_button, disabled=disabled)
    
Mbtree_Anim.update_gui = update_gui

上記の修正後に下記のプログラムを実行し、ルートノードの処理を開始したフレームと、評価値が確定したフレームを表示すると、実行結果のように ボタンの色が正しく表示される ことが確認できます。

Mbtree_Anim(mbtree, isscore=True)

 

今回の記事のまとめ

今回の記事では Mbtree_Anim に 4 つのボタンを追加することで、選択中のノードに対するフレームの移動 を行うことができるようにするという改良を行いました。

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

リンク 説明
marubatsu.ipynb 本記事で入力して実行した JupyterLab のファイル
tree.py 本記事で更新した tree_new.py

次回の記事

  1. "start""end" のフレームは、そのノードの端(edge)にあるフレームの状態(status)なので edge_status という名前にしました

  2. 差分を表す difference の略です

  3. 集合を表す set で表現してもかまいません

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?