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を一から作成する その160 αβ 法の視覚化のための Mbtree_Anim の修正 その 1

Last updated at Posted at 2025-03-20

目次と前回の記事

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

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

リンク 説明
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 の修正

前回の記事では calc_score_for_anim で行った αβ 法の計算手順の視覚化の検討と、視覚化に必要なデータの記録の実装を行いました。今回の記事では Mbtree_Anim を修正して αβ 法の視覚化 を行います。

なお、実際の実装の作業 では、表示内容、表示位置、表示色などについて 様々な試行錯誤を行いました が、その試行錯誤の過程をすべて説明すると記事が長くなりすぎるので 最終的な試行錯誤の結果の実装 について説明することにします。

また、今回の記事の実装は視覚化の方法の一つにすぎません。もっと良い視覚化の方法を思いついた方はその実装にぜひチャレンジしてみて下さい。

視覚化の検討のおさらい

前回の記事で行った αβ 法の視覚化の方法 と、フレームに記録するデータ の検討結果は以下の通りです。

常に表示する内容

常に表示する内容と、表示するために必要な情報は以下の表の通りです。以前の記事で検討したミニマックス法での表示と共通する内容は省略しました。

表示内容 必要な情報
α 値と β 値の初期値
範囲の色分け
範囲の説明
α 値と β 値の初期値

"start" 以外の状態で表示する内容

"start" 以外 の状態で表示する内容と、表示するために必要な情報は以下の通りです。

表示内容 必要な情報
置換表の範囲 置換表の範囲の下界と上界
置換表の登録の区別 置換表に登録されているかどうか

フレームの状態ごとに表示する内容

それぞれのフレームの状態で表示する内容は以下の通りです。上記の "start" 以外の状態で表示する内容は省略しました。なお、ミニマックス法と共通する内容には先頭に(※)を記述しました。

  • "start" の状態
    なし
  • "tt" の状態
    • 置換表にノードの評価値が登録されている場合
      • (※)赤字で「置換表に登録済」を表示
      • ミニマックス法で表示した置換表に登録されていた評価値は 表示しない
      • 置換表による枝狩りが行われている場合
        • $l=u$ の場合は赤字で「置換表による枝狩り(exact value)」を表示
        • $u≦α$ の場合は赤字で「置換表による枝狩り(fail low)」を表示
        • $β≦l$ の場合は赤字で「置換表による枝狩り(fail high)」を表示
      • 置換表による枝狩りが行われていない場合
        • α 値と β 値の初期値が更新される場合は赤字で「α 値または β 値の初期値の更新」を表示
    • 登録されていない場合
      • (※)黒字で「置換表に未登録」を表示
  • "score" の状態
    • (※)数直線上にそのフレームでのノードの評価値を表示
    • (※)数直線上に子ノードの評価値を表示
    • そのフレームでの α 値と β 値
  • "update" の状態
    • (※)数直線上にそのフレームでのノードの評価値を表示
    • ノードの評価値が更新された場合
      • (※)赤字で「評価値の更新」を表示
    • 更新されていない場合
      • (※)黒字で「評価値の更新なし」を表示
    • そのフレームでの α 値と β 値
    • α 狩りが行われた場合は赤字で「α 狩り」と表示
    • β 狩りが行われた場合は赤字で「β 狩り」と表示
  • "end" の状態
    • (※)数直線上にそのフレームでのノードの評価値を表示
    • このノードの評価値の種類によってフレームの状態を以下のように表示する
      • fail low の場合は「評価値の確定(fail low)」を表示
      • exact value の場合は「評価値の確定(exact value)」を表示
      • fail high の場合は「評価値の確定(fail high)」を表示
    • 置換表を利用する場合
      • 置換表による枝狩りが行われた場合
        • (※)黒字で「置換表に登録されていたデータを利用」を表示
      • 置換表による枝狩りが行われていない場合
        • (※)赤字で「置換表への登録」を表示
        • 計算された範囲
        • 登録する範囲

フレームに記録するデータ

calc_score_for_anim が記録する フレームのデータを表す dict の内容 は以下の通りです。

キー キーの値の意味
"alphaorig" α 値の初期値
"betaorig" β 値の初期値
"registered_in_tt" (※)置換表にノードの評価値が登録されていたかどうか
"lower_bound" (※)置換表の範囲の下界
"upper_bound" (※)置換表の範囲の上界
"tt_pruned" 置換表による枝狩りが行われたかどうか
"ab_updated" α 値または β 値の初期値が更新されたかどうか
"ab_pruned" α 狩りまたは β 狩りが行われたかどうか
"score_type" ノードの評価値の種類
"clower_bound" 計算された範囲の下界
"cupper_bound" 計算された範囲の上界
"tlower_bound" 登録する範囲の下界
"tupper_bound" 登録する範囲の上界

数直線の表示の改良

以下のような 数直線の表示の改良 を思いつきましたので、実装することにします。

これまでは 数直線の両端に必ず負の無限大と正の無限大を表す -∞ と ∞ を表示していました が、ルートノードの α 値と β 値 を評価値の最小値と最大値で 初期化する場合は 負の無限大と正の無限大は計算の際に利用しないので それらを表示しないようにする ことにします。

常に表示する内容の表示の実装

実装する内容が多いので、今回の記事では 常に表示する内容の表示を実装 することにします。どのように Mbtree_Anim クラスを修正すれば良いかについて少し考えてみて下さい。

Figure の大きさの変更

上記で 検討した内容を表示するため にはフレームの情報を表示する Figure の大きさが足りない ので、下記のプログラムのように calc_score_for_anim で αβ 法 で計算が行なわれていた場合は Figure の大きさを大きくする ように create_widgets を修正することにします。なお、Figure の大きさは試行錯誤の結果決めたもの です。

  • 15、16 行目calc_score_for_anim で作成されたデータで、αβ 法で計算されている場合のみ作成する Figure の大きさを (7, 2) とするように修正する
 1  from tree import Mbtree_Anim
 2  import ipywidgets as widgets
 3  import matplotlib.pyplot as plt
 4  
 5  def create_widgets(self):
元と同じなので省略
 6      with plt.ioff():
 7          self.fig = plt.figure(figsize=[self.width * self.size,
 8                                          self.height * self.size])
 9          self.ax = self.fig.add_axes([0, 0, 1, 1])
10          self.fig.canvas.toolbar_visible = False
11          self.fig.canvas.header_visible = False
12          self.fig.canvas.footer_visible = False
13          self.fig.canvas.resizable = False 
14          if self.isscore and hasattr(self.mbtree, "ablist_by_score"):
15              if self.mbtree.calculated_by_calc_score_for_anim and not self.mbtree.minimax:
16                  self.abfig = plt.figure(figsize=(7, 2))
17              else:
18                  self.abfig = plt.figure(figsize=(7, 1))
元と同じなので省略
19  
20  Mbtree_Anim.create_widgets = create_widgets
行番号のないプログラム
from tree import Mbtree_Anim
import ipywidgets as widgets
import matplotlib.pyplot as plt

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"):
            if self.mbtree.calculated_by_calc_score_for_anim and not self.mbtree.minimax:
                self.abfig = plt.figure(figsize=(7, 2))
            else:
                self.abfig = plt.figure(figsize=(7, 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
修正箇所
from tree import Mbtree_Anim
import ipywidgets as widgets
import matplotlib.pyplot as plt

def create_widgets(self):
元と同じなので省略
    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=(7, 1))
+           if self.mbtree.calculated_by_calc_score_for_anim and not self.mbtree.minimax:
+               self.abfig = plt.figure(figsize=(7, 2))
+           else:
+               self.abfig = plt.figure(figsize=(7, 1))
元と同じなので省略

Mbtree_Anim.create_widgets = create_widgets

上記の修正後に下記のプログラムで calc_score_by_ab で計算したデータをファイルから読み込んで Mbtree_Anim で表示 すると、実行結果のように これまでと同じ大きさ でフレームの情報を表示する Figure が作成されることが確認できます。

from tree import Mbtree

mbtree = Mbtree.load("../data/abtree_root")
Mbtree_Anim(mbtree, isscore=True)

実行結果(ゲーム木の部分の表示は省略します)

また、下記のプログラムで calc_score_for_animミニマックス法 で計算したデータを Mbtree_Anim で表示 すると、実行結果のように これまでと同じ大きさ でフレームの情報を表示する Figure が作成されることが確認できます。

mbtree.calc_score_for_anim(mbtree.root, minimax=True)
Mbtree_Anim(mbtree, isscore=True)

実行結果

下記のプログラムで calc_score_for_animαβ 法 で計算したデータを Mbtree_Anim で表示 すると、実行結果のように Figure の縦幅が大きな Figure が作成される ことが確認できます。なお、表示する内容の修正はまだ行なっていない ので、現時点では中に表示される内容は実行結果のように 縦に広がって表示されます

mbtree.calc_score_for_anim(mbtree.root, minimax=False)
Mbtree_Anim(mbtree, isscore=True)

実行結果

Axes の表示範囲の修正と数直線の表示の修正

上記の実行結果で 表示内容が縦に広がって表示される ようになったのは、Figure の大きさを変えたにも関わらず、Axes の表示範囲を変えていないから です。そのため、αβ 法で計算された場合は update_frameinfo の中の Axes の表示範囲を設定する処理 を下記のプログラムのように修正する必要があります。また、数直線の表示の修正 は簡単に行えるので下記のプログラムでついでに 修正しました

  • 11 行目:ミニマックス法で計算されているかどうかの判定を何度も行う必要が生じるので、minimax というローカル変数に ミニマックス法で計算されているかどうか を表す self.mbtree.minimax を代入 して プログラムを短く記述できる ようにした
  • 17、18 行目:αβ 法で計算されている場合の Axes の y 座標の表示範囲を広げるように修正した。なお、Figure の幅は変わらないので x 座標の表示範囲に関する処理を修正する必要はない
  • 27 ~ 30 行目:ルートノードの α 値と β 値の初期値を設定しない場合のみ、数直線の負の無限大と正の無限大に -∞ と ∞ を表示するように修正した
 1  from marubatsu import Marubatsu
 2  import matplotlib.patches as patches
 3  
 4  def update_frameinfo(self):
 5      def calc_coord(score):
 6          return min(max(minus_inf, score), plus_inf)
 7          
 8      framedata = self.mbtree.ablist_by_score[self.play.value]
 9      status = framedata["status"]
10      maxnode = self.selectednode.mb.turn == Marubatsu.CIRCLE
11      minimax = self.mbtree.minimax
12  
13      self.abax.clear()
14      self.abax.set_xlim(-4, 23)
15      if minimax:
16          self.abax.set_ylim(-1.5, 1.5)
17      else:
18          self.abax.set_ylim(-4.3, 2.3)
19      self.abax.axis("off")
20  
21      minus_inf = -3 if self.mbtree.shortest_victory else -2
22      plus_inf = 4 if self.mbtree.shortest_victory else 2
23      
24      # 数直線の描画    
25      self.abax.plot(range(minus_inf, plus_inf + 1), [0] * (plus_inf + 1 - minus_inf) , "|-k")
26      for num in range(minus_inf, plus_inf + 1):
27          if num == minus_inf:
28              numtext = "" if self.mbtree.init_ab else "-∞"
29          elif num == plus_inf:
30              numtext = "" if self.mbtree.init_ab else ""
31          else:
32              numtext = num
33          self.abax.text(num, -1, numtext, ha="center") 
元と同じなので省略
34  
35  Mbtree_Anim.update_frameinfo = update_frameinfo
行番号のないプログラム
from marubatsu import Marubatsu
import matplotlib.patches as patches

def update_frameinfo(self):
    def calc_coord(score):
        return min(max(minus_inf, score), plus_inf)
        
    framedata = self.mbtree.ablist_by_score[self.play.value]
    status = framedata["status"]
    maxnode = self.selectednode.mb.turn == Marubatsu.CIRCLE
    minimax = self.mbtree.minimax
    
    self.abax.clear()
    self.abax.set_xlim(-4, 23)
    if minimax:
        self.abax.set_ylim(-1.5, 1.5)
    else:
        self.abax.set_ylim(-4.3, 2.3)
    self.abax.axis("off")

    minus_inf = -3 if self.mbtree.shortest_victory else -2
    plus_inf = 4 if self.mbtree.shortest_victory else 2

    # 数直線の描画    
    self.abax.plot(range(minus_inf, plus_inf + 1), [0] * (plus_inf + 1 - minus_inf) , "|-k")
    for num in range(minus_inf, plus_inf + 1):
        if num == minus_inf:
            numtext = "" if self.mbtree.init_ab else "-∞"
        elif num == plus_inf:
            numtext = "" if self.mbtree.init_ab else ""
        else:
            numtext = num
        self.abax.text(num, -1, numtext, ha="center")        

    # メッセージの表示
    linenum = 4
    textlist = [""] * linenum
    textcolorlist = ["black"] * linenum

    algorithm = "mm 法" if self.mbtree.minimax else "αβ法"
    use_tt = "" if self.mbtree.use_tt else "×"
    shortest_victory = "" if self.mbtree.shortest_victory else "×"
    init_ab = "" if self.mbtree.init_ab else "×"
    textlist[0] = f"{algorithm} 置換表 {use_tt} 最短 {shortest_victory}"
    if not self.mbtree.minimax:
        textlist[0] += f" 初期値 {init_ab}"
    
    textlist[1] = f"深さ {self.selectednode.mb.move_count} "
    if maxnode:
        textlist[1] += "max node"
    else:
        textlist[1] += "min node"
    
    statusdata = {
        "start": {
            "text": "処理の開始",
            "color": "white"
        },
        "tt": {
            "text": "置換表の処理",
            "color": "honeydew"
        },
        "score": {
            "text": "子ノードの評価値",
            "color": "lightyellow"
        },
        "update": {
            "text": "更新処理",
            "color": "lightcyan"
        },
        "end": {
            "text": "評価値の確定",
            "color": "lavenderblush"
        },
    }
    textlist[2] = statusdata[status]["text"]
    facecolor = statusdata[status]["color"]
    
    arrowprops = { "arrowstyle": "->"}
    leftx = -3
    rightx = 4
    centerx = (leftx + rightx) / 2
    # そのフレームでのノードの評価値の表示
    if status in ["score", "update", "end"]:
        score = framedata["score"]
        score_coord = calc_coord(score)
        text_coord = leftx if maxnode else rightx
        ha = "left" if maxnode else "right"
        self.abax.plot(score_coord, 0, "ok")
        self.abax.annotate(f"score = {score}", xy=(score_coord, 0),
                           xytext=(text_coord, 1), arrowprops=arrowprops, ha=ha)        
    # 子ノードの評価値の表示
    if status == "score":
        childscore = framedata["childscore"]
        childscore_coord = calc_coord(childscore)
        text_coord = rightx if maxnode else leftx
        ha = "right" if maxnode else "left"
        self.abax.plot(childscore_coord, 0, "og")
        self.abax.annotate(f"cscore = {childscore}", xy=(childscore_coord, 0),
                           xytext=(text_coord, 1), arrowprops=arrowprops, ha=ha)        
    # 置換表にデータが登録されていたかどうかの表示
    elif status =="tt":
        if framedata["registered_in_tt"]:
            textlist[3] = "置換表に登録済"
            textcolorlist[3] = "red"
            score = framedata["lower_bound"]
            score_coord = calc_coord(score)
            self.abax.plot(score_coord, 0, "om")
            self.abax.annotate(f"置換表の評価値 = {score}", xy=(score_coord, 0),
                            xytext=(centerx, 1), arrowprops=arrowprops, ha="center")        
        else:
            textlist[3] = "置換表に未登録"
    # ノードの評価値が更新されたかどうかの表示
    elif status == "update":
        if framedata["updated"]:
            textlist[3] = "評価値の更新"
            textcolorlist[3] = "red"
        else:
            textlist[3] = "評価値の更新なし"
    # 置換表に登録したかどうかの表示
    elif status == "end":
        if self.mbtree.use_tt:
            if framedata["registered_in_tt"]:
                textlist[3] = "置換表に登録されていたデータを利用"
            else:
                textlist[3] = "置換表への登録"
                textcolorlist[3] = "red"

    self.abfig.set_facecolor(facecolor)
    for i in range(linenum):
        self.abax.text(5, 1 - i * 0.7, textlist[i], c=textcolorlist[i])

    num_calculated = framedata["num_calculated"]
    num_pruned = framedata["num_pruned"]
    num_total = num_calculated + num_pruned
    num_ratio = num_calculated / num_total if num_total != 0 else 0
    prev_framedata = self.mbtree.ablist_by_score[self.prev_frame]
    prev_num_calculated = prev_framedata["num_calculated"]
    prev_num_pruned = prev_framedata["num_pruned"]
    prev_num_total = prev_num_calculated + prev_num_pruned
    diff_num_calculated = num_calculated - prev_num_calculated
    diff_num_pruned = num_pruned - prev_num_pruned
    diff_num_total = num_total - prev_num_total
    diff_num_ratio = diff_num_calculated / diff_num_total if diff_num_total != 0 else 0

    textlist = [ "計算済", "枝狩り", "合計", "割合" ]
    datalist = [ num_calculated, num_pruned, num_total, f"{num_ratio * 100:.1f}%"]
    diff_datalist = [ f"{diff_num_calculated:+d}", f"{diff_num_pruned:+d}", 
                    f"{diff_num_total:+d}", f"{diff_num_ratio * 100:.1f}%"]
    for i in range(4):
        self.abax.text(15, 1 - i * 0.7, textlist[i])
        self.abax.text(19.5, 1 - i * 0.7, datalist[i], ha="right")
        self.abax.text(22.5, 1 - i * 0.7, diff_datalist[i], ha="right")
        
Mbtree_Anim.update_frameinfo = update_frameinfo
修正箇所
from marubatsu import Marubatsu
import matplotlib.patches as patches

def update_frameinfo(self):
    def calc_coord(score):
        return min(max(minus_inf, score), plus_inf)
        
    framedata = self.mbtree.ablist_by_score[self.play.value]
    status = framedata["status"]
    maxnode = self.selectednode.mb.turn == Marubatsu.CIRCLE
+   minimax = self.mbtree.minimax

    self.abax.clear()
    self.abax.set_xlim(-4, 23)
-   self.abax.set_ylim(-1.5, 1.5)
+   if minimax:
+       self.abax.set_ylim(-1.5, 1.5)
+   else:
+       self.abax.set_ylim(-4.3, 2.3)
+   self.abax.axis("off")

    minus_inf = -3 if self.mbtree.shortest_victory else -2
    plus_inf = 4 if self.mbtree.shortest_victory else 2
    
    # 数直線の描画    
    self.abax.plot(range(minus_inf, plus_inf + 1), [0] * (plus_inf + 1 - minus_inf) , "|-k")
    for num in range(minus_inf, plus_inf + 1):
        if num == minus_inf:
-           numtext = "-∞"
+           numtext = "" if self.mbtree.init_ab else "-∞"
        elif num == plus_inf:
-           numtext = ""
+           numtext = "" if self.mbtree.init_ab else ""
        else:
            numtext = num
        self.abax.text(num, -1, numtext, ha="center") 
元と同じなので省略

Mbtree_Anim.update_frameinfo = update_frameinfo

update_frameinfocalc_score_for_anim で作成されたデータを表示するプログラムなので、上記の修正によって calc_score_by_ab で作成されたデータの表示は変わりません。そのため以後の確認作業では特に理由がない限り calc_score_for_anim で計算したデータに対してのみ行うことにします。

上記の修正後に下記のプログラムで ミニマックス法ルートノードの α 値と β 値を初期化しない 場合のデータを Mbtree_Anim で表示すると、実行結果のようにこれまでと同じように フレームの情報が正しく表示される ことが確認できます。余裕がある方は他のフレームでの表示も確認してみて下さい。

mbtree.calc_score_for_anim(mbtree.root, minimax=True)
Mbtree_Anim(mbtree, isscore=True)

実行結果

下記のプログラムで αβ 法ルートノードの α 値と β 値を初期化する 場合のデータを Mbtree_Anim で表示すると、実行結果のように 表示される内容が縦に伸びなくなった ことが確認できます。また、数直線に ―∞ と ∞ が表示されなくなった ことも確認できます。

mbtree.calc_score_for_anim(mbtree.root, minimax=False, init_ab=True)
Mbtree_Anim(mbtree, isscore=True)

実行結果

文字の表示位置の変更

上記の実行結果では、真ん中と右に表示される内容 が Figure の 上部から少し下の位置に表示 されていますが、これは Axes の表示範囲の y 座標の上部を 1.5 から 2.3 に変更したため です。そこで、それらの表示を Figure の上部に移動 することにします。

また、現状では真ん中の部分には最大で 4 行分のメッセージを表示しますが、試行錯誤の結果筆者が考えた αβ 法 の表示方法では 9 行分のメッセージを表示する必要がある1 ので 9 行分のメッセージを表示できるように修正することにします。

下記はそのように update_frameinfo を修正したプログラムです。

  • 3 ~ 8 行目:ミニマックス法と αβ 法のそれぞれの場合について、真ん中のメッセージの行数を linenum に、一番上の行のメッセージの表示の y 座標を linetop に代入する
  • 13 行目:真ん中のメッセージの表示位置を計算する式の中で、一番上の行のメッセージの表示座標を表す数値を linetop に置き換えるように修正した
  • 19 ~ 21 行目:右のメッセージの表示に関しても同様の修正を行った
 1  def update_frameinfo(self):
元と同じなので省略
 2      # メッセージの表示
 3      if minimax:
 4          linenum = 4
 5          linetop = 1
 6      else:
 7          linenum = 9
 8          linetop = 1.7
 9      textlist = [""] * linenum
10      textcolorlist = ["black"] * linenum
元と同じなので省略
11      self.abfig.set_facecolor(facecolor)
12      for i in range(linenum):
13          self.abax.text(5, linetop - i * 0.7, textlist[i], c=textcolorlist[i])
元と同じなので省略
14      textlist = [ "計算済", "枝狩り", "合計", "割合" ]
15      datalist = [ num_calculated, num_pruned, num_total, f"{num_ratio * 100:.1f}%"]
16      diff_datalist = [ f"{diff_num_calculated:+d}", f"{diff_num_pruned:+d}", 
17                      f"{diff_num_total:+d}", f"{diff_num_ratio * 100:.1f}%"]
18      for i in range(4):
19          self.abax.text(15, linetop - i * 0.7, textlist[i])
20          self.abax.text(19.5, linetop - i * 0.7, datalist[i], ha="right")
21          self.abax.text(22.5, linetop - i * 0.7, diff_datalist[i], ha="right")
22          
23  Mbtree_Anim.update_frameinfo = update_frameinfo
行番号のないプログラム
def update_frameinfo(self):
    def calc_coord(score):
        return min(max(minus_inf, score), plus_inf)
        
    framedata = self.mbtree.ablist_by_score[self.play.value]
    status = framedata["status"]
    maxnode = self.selectednode.mb.turn == Marubatsu.CIRCLE
    minimax = self.mbtree.minimax
    
    self.abax.clear()
    self.abax.set_xlim(-4, 23)
    if minimax:
        self.abax.set_ylim(-1.5, 1.5)
    else:
        self.abax.set_ylim(-4.3, 2.3)
    self.abax.axis("off")

    minus_inf = -3 if self.mbtree.shortest_victory else -2
    plus_inf = 4 if self.mbtree.shortest_victory else 2

    # 数直線の描画    
    self.abax.plot(range(minus_inf, plus_inf + 1), [0] * (plus_inf + 1 - minus_inf) , "|-k")
    for num in range(minus_inf, plus_inf + 1):
        if num == minus_inf:
            numtext = "" if self.mbtree.init_ab else "-∞"
        elif num == plus_inf:
            numtext = "" if self.mbtree.init_ab else ""
        else:
            numtext = num
        self.abax.text(num, -1, numtext, ha="center")        

    # メッセージの表示
    if minimax:
        linenum = 4
        linetop = 1
    else:
        linenum = 9
        linetop = 1.7
    textlist = [""] * linenum
    textcolorlist = ["black"] * linenum

    algorithm = "mm 法" if self.mbtree.minimax else "αβ法"
    use_tt = "" if self.mbtree.use_tt else "×"
    shortest_victory = "" if self.mbtree.shortest_victory else "×"
    init_ab = "" if self.mbtree.init_ab else "×"
    textlist[0] = f"{algorithm} 置換表 {use_tt} 最短 {shortest_victory}"
    if not self.mbtree.minimax:
        textlist[0] += f" 初期値 {init_ab}"
    
    textlist[1] = f"深さ {self.selectednode.mb.move_count} "
    if maxnode:
        textlist[1] += "max node"
    else:
        textlist[1] += "min node"
    
    statusdata = {
        "start": {
            "text": "処理の開始",
            "color": "white"
        },
        "tt": {
            "text": "置換表の処理",
            "color": "honeydew"
        },
        "score": {
            "text": "子ノードの評価値",
            "color": "lightyellow"
        },
        "update": {
            "text": "更新処理",
            "color": "lightcyan"
        },
        "end": {
            "text": "評価値の確定",
            "color": "lavenderblush"
        },
    }
    textlist[2] = statusdata[status]["text"]
    facecolor = statusdata[status]["color"]
    
    arrowprops = { "arrowstyle": "->"}
    leftx = -3
    rightx = 4
    centerx = (leftx + rightx) / 2
    # そのフレームでのノードの評価値の表示
    if status in ["score", "update", "end"]:
        score = framedata["score"]
        score_coord = calc_coord(score)
        text_coord = leftx if maxnode else rightx
        ha = "left" if maxnode else "right"
        self.abax.plot(score_coord, 0, "ok")
        self.abax.annotate(f"score = {score}", xy=(score_coord, 0),
                           xytext=(text_coord, 1), arrowprops=arrowprops, ha=ha)        
    # 子ノードの評価値の表示
    if status == "score":
        childscore = framedata["childscore"]
        childscore_coord = calc_coord(childscore)
        text_coord = rightx if maxnode else leftx
        ha = "right" if maxnode else "left"
        self.abax.plot(childscore_coord, 0, "og")
        self.abax.annotate(f"cscore = {childscore}", xy=(childscore_coord, 0),
                           xytext=(text_coord, 1), arrowprops=arrowprops, ha=ha)        
    # 置換表にデータが登録されていたかどうかの表示
    elif status =="tt":
        if framedata["registered_in_tt"]:
            textlist[3] = "置換表に登録済"
            textcolorlist[3] = "red"
            score = framedata["lower_bound"]
            score_coord = calc_coord(score)
            self.abax.plot(score_coord, 0, "om")
            self.abax.annotate(f"置換表の評価値 = {score}", xy=(score_coord, 0),
                            xytext=(centerx, 1), arrowprops=arrowprops, ha="center")        
        else:
            textlist[3] = "置換表に未登録"
    # ノードの評価値が更新されたかどうかの表示
    elif status == "update":
        if framedata["updated"]:
            textlist[3] = "評価値の更新"
            textcolorlist[3] = "red"
        else:
            textlist[3] = "評価値の更新なし"
    # 置換表に登録したかどうかの表示
    elif status == "end":
        if self.mbtree.use_tt:
            if framedata["registered_in_tt"]:
                textlist[3] = "置換表に登録されていたデータを利用"
            else:
                textlist[3] = "置換表への登録"
                textcolorlist[3] = "red"

    self.abfig.set_facecolor(facecolor)
    for i in range(linenum):
        self.abax.text(5, linetop - i * 0.7, textlist[i], c=textcolorlist[i])

    num_calculated = framedata["num_calculated"]
    num_pruned = framedata["num_pruned"]
    num_total = num_calculated + num_pruned
    num_ratio = num_calculated / num_total if num_total != 0 else 0
    prev_framedata = self.mbtree.ablist_by_score[self.prev_frame]
    prev_num_calculated = prev_framedata["num_calculated"]
    prev_num_pruned = prev_framedata["num_pruned"]
    prev_num_total = prev_num_calculated + prev_num_pruned
    diff_num_calculated = num_calculated - prev_num_calculated
    diff_num_pruned = num_pruned - prev_num_pruned
    diff_num_total = num_total - prev_num_total
    diff_num_ratio = diff_num_calculated / diff_num_total if diff_num_total != 0 else 0

    textlist = [ "計算済", "枝狩り", "合計", "割合" ]
    datalist = [ num_calculated, num_pruned, num_total, f"{num_ratio * 100:.1f}%"]
    diff_datalist = [ f"{diff_num_calculated:+d}", f"{diff_num_pruned:+d}", 
                    f"{diff_num_total:+d}", f"{diff_num_ratio * 100:.1f}%"]
    for i in range(4):
        self.abax.text(15, linetop - i * 0.7, textlist[i])
        self.abax.text(19.5, linetop - i * 0.7, datalist[i], ha="right")
        self.abax.text(22.5, linetop - i * 0.7, diff_datalist[i], ha="right")
        
Mbtree_Anim.update_frameinfo = update_frameinfo
修正箇所
def update_frameinfo(self):
元と同じなので省略
    # メッセージの表示
-   linenum = 4
+   if minimax:
+       linenum = 4
+       linetop = 1
+   else:
+       linenum = 9
+       linetop = 1.7
    textlist = [""] * linenum
    textcolorlist = ["black"] * linenum
元と同じなので省略
    self.abfig.set_facecolor(facecolor)
    for i in range(linenum):
-       self.abax.text(5, 1 - i * 0.7, textlist[i], c=textcolorlist[i])
+       self.abax.text(5, linetop - i * 0.7, textlist[i], c=textcolorlist[i])
元と同じなので省略
    textlist = [ "計算済", "枝狩り", "合計", "割合" ]
    datalist = [ num_calculated, num_pruned, num_total, f"{num_ratio * 100:.1f}%"]
    diff_datalist = [ f"{diff_num_calculated:+d}", f"{diff_num_pruned:+d}", 
                    f"{diff_num_total:+d}", f"{diff_num_ratio * 100:.1f}%"]
    for i in range(4):
-       self.abax.text(15, 1 - i * 0.7, textlist[i])
+       self.abax.text(15, linetop - i * 0.7, textlist[i])
-       self.abax.text(19.5, 1 - i * 0.7, datalist[i], ha="right")
+       self.abax.text(19.5, linetop - i * 0.7, datalist[i], ha="right")
-       self.abax.text(22.5, 1 - i * 0.7, diff_datalist[i], ha="right")
+       self.abax.text(22.5, linetop - i * 0.7, diff_datalist[i], ha="right")
        
Mbtree_Anim.update_frameinfo = update_frameinfo

実行結果はこれまでと同様なので省略しますが、上記の修正後に下記のプログラムで ミニマックス法での表示が正しく行なわれる ことが確認できます。

mbtree.calc_score_for_anim(mbtree.root, minimax=True)
Mbtree_Anim(mbtree, isscore=True)

下記のプログラムで αβ 法の場合で計算したデータに対して 真ん中と右の表示が Figure の上部から行なわれる ようになったことが確認できます。なお、ここまでで 最短の勝利を目指す場合の表示の確認を行なっていなかった ので、下記ではその確認も行なっており、数直線の範囲が正しく表示される ことが確認できます。

mbtree.calc_score_for_anim(mbtree.root, minimax=False, shortest_victory=True)
Mbtree_Anim(mbtree, isscore=True)

実行結果

α 値と β 値の初期値と範囲の色分けの表示

本記事では α 値と β 値の初期値 を以下のように表示することにしました。もっと良い表示を思いついた方は自由に変更して下さい。

  • α 値を数直線上に赤い丸で表示し、その注釈を数直線の左上に表示する
  • β 値を数直線上に青い丸で表示し、その注釈を数直線の右上に表示する
  • 注釈は子ノードの評価値などの表示と重ならないように、そのすぐ上に表示する2

また、範囲の色分け は以下のように表示することにしました。色分けの方法は以前の記事calc_score_by_ab で計算したデータを Mbtree_Anim で色分けする方法とほぼ同様 です。

  • exact value の範囲を黄色で塗りつぶす
  • 枝狩りを行わない fail low または fail high の範囲を灰色で塗りつぶす
  • 枝狩りを行う fail low または fail high の範囲を水色で塗りつぶす

下記は そのように update_frameinfo を修正したプログラムです。

  • 6 ~ 21 行目:αβ 法の場合に範囲の色分けを行う。行う処理は以前の記事で説明した処理とほぼ同じなので忘れた方は復習すること。また、この処理は数直線の描画より前に行う必要がある点に注意すること3
  • 29 ~ 35 行目:数直線上に α 値と β 値の初期値を色のついた丸で描画し、上部にその注釈を描画するように修正した
 1  def update_frameinfo(self):
元と同じなので省略
 2      minus_inf = -3 if self.mbtree.shortest_victory else -2
 3      plus_inf = 4 if self.mbtree.shortest_victory else 2
 4  
 5      # 範囲の色分け
 6      if not minimax:
 7          alphaorig = framedata["alphaorig"]
 8          betaorig = framedata["betaorig"]
 9          alphaorig_coord = calc_coord(alphaorig)
10          betaorig_coord = calc_coord(betaorig)
11          color = "lightgray" if maxnode else "aqua"
12          rect = patches.Rectangle(xy=(minus_inf, -0.5), width=alphaorig_coord-minus_inf,
13                                  height=1, fc=color)
14          self.abax.add_patch(rect)
15          rect = patches.Rectangle(xy=(alphaorig_coord, -0.5), width=betaorig_coord-alphaorig_coord,
16                                  height=1, fc="yellow")
17          self.abax.add_patch(rect)
18          color = "aqua" if maxnode else "lightgray"
19          rect = patches.Rectangle(xy=(betaorig_coord, -0.5), width=plus_inf-betaorig_coord,
20                                  height=1, fc=color)
21          self.abax.add_patch(rect)
22      # 数直線の描画    
23      self.abax.plot(range(minus_inf, plus_inf + 1), [0] * (plus_inf + 1 - minus_inf) , "|-k")
元と同じなので省略
24      arrowprops = { "arrowstyle": "->"}
25      leftx = -3
26      rightx = 4
27      centerx = (leftx + rightx) / 2
28      # α 値 と β 値の初期値の表示
29      if not minimax:
30          self.abax.plot(alphaorig_coord, 0, "or")
31          self.abax.annotate(f"α = {alphaorig}", xy=(alphaorig_coord, 0),
32                          xytext=(leftx, 1.7), arrowprops=arrowprops, ha="left")        
33          self.abax.plot(betaorig_coord, 0, "ob")
34          self.abax.annotate(f"β = {betaorig}", xy=(betaorig_coord, 0),
35                          xytext=(rightx, 1.7), arrowprops=arrowprops, ha="right")        
36      # そのフレームでのノードの評価値の表示
37      if status in ["score", "update", "end"]:
元と同じなので省略
38          
39  Mbtree_Anim.update_frameinfo = update_frameinfo
行番号のないプログラム
def update_frameinfo(self):
    def calc_coord(score):
        return min(max(minus_inf, score), plus_inf)
        
    framedata = self.mbtree.ablist_by_score[self.play.value]
    status = framedata["status"]
    maxnode = self.selectednode.mb.turn == Marubatsu.CIRCLE
    minimax = self.mbtree.minimax
    
    self.abax.clear()
    self.abax.set_xlim(-4, 23)
    if minimax:
        self.abax.set_ylim(-1.5, 1.5)
    else:
        self.abax.set_ylim(-4.3, 2.3)
    self.abax.axis("off")

    minus_inf = -3 if self.mbtree.shortest_victory else -2
    plus_inf = 4 if self.mbtree.shortest_victory else 2

    # 範囲の色分け
    if not minimax:
        alphaorig = framedata["alphaorig"]
        betaorig = framedata["betaorig"]
        alphaorig_coord = calc_coord(alphaorig)
        betaorig_coord = calc_coord(betaorig)
        color = "lightgray" if maxnode else "aqua"
        rect = patches.Rectangle(xy=(minus_inf, -0.5), width=alphaorig_coord-minus_inf,
                                 height=1, fc=color)
        self.abax.add_patch(rect)
        rect = patches.Rectangle(xy=(alphaorig_coord, -0.5), width=betaorig_coord-alphaorig_coord,
                                 height=1, fc="yellow")
        self.abax.add_patch(rect)
        color = "aqua" if maxnode else "lightgray"
        rect = patches.Rectangle(xy=(betaorig_coord, -0.5), width=plus_inf-betaorig_coord,
                                 height=1, fc=color)
        self.abax.add_patch(rect)
    # 数直線の描画    
    self.abax.plot(range(minus_inf, plus_inf + 1), [0] * (plus_inf + 1 - minus_inf) , "|-k")
    for num in range(minus_inf, plus_inf + 1):
        if num == minus_inf:
            numtext = "" if self.mbtree.init_ab else "-∞"
        elif num == plus_inf:
            numtext = "" if self.mbtree.init_ab else ""
        else:
            numtext = num
        self.abax.text(num, -1, numtext, ha="center")        

    # メッセージの表示
    if minimax:
        linenum = 4
        linetop = 1
    else:
        linenum = 9
        linetop = 1.7
    textlist = [""] * linenum
    textcolorlist = ["black"] * linenum

    algorithm = "mm 法" if self.mbtree.minimax else "αβ法"
    use_tt = "" if self.mbtree.use_tt else "×"
    shortest_victory = "" if self.mbtree.shortest_victory else "×"
    init_ab = "" if self.mbtree.init_ab else "×"
    textlist[0] = f"{algorithm} 置換表 {use_tt} 最短 {shortest_victory}"
    if not self.mbtree.minimax:
        textlist[0] += f" 初期値 {init_ab}"
    
    textlist[1] = f"深さ {self.selectednode.mb.move_count} "
    if maxnode:
        textlist[1] += "max node"
    else:
        textlist[1] += "min node"
    
    statusdata = {
        "start": {
            "text": "処理の開始",
            "color": "white"
        },
        "tt": {
            "text": "置換表の処理",
            "color": "honeydew"
        },
        "score": {
            "text": "子ノードの評価値",
            "color": "lightyellow"
        },
        "update": {
            "text": "更新処理",
            "color": "lightcyan"
        },
        "end": {
            "text": "評価値の確定",
            "color": "lavenderblush"
        },
    }
    textlist[2] = statusdata[status]["text"]
    facecolor = statusdata[status]["color"]
    
    arrowprops = { "arrowstyle": "->"}
    leftx = -3
    rightx = 4
    centerx = (leftx + rightx) / 2
    # α 値 と β 値の初期値の表示
    if not minimax:
        self.abax.plot(alphaorig_coord, 0, "or")
        self.abax.annotate(f"α = {alphaorig}", xy=(alphaorig_coord, 0),
                           xytext=(leftx, 1.7), arrowprops=arrowprops, ha="left")        
        self.abax.plot(betaorig_coord, 0, "ob")
        self.abax.annotate(f"β = {betaorig}", xy=(betaorig_coord, 0),
                           xytext=(rightx, 1.7), arrowprops=arrowprops, ha="right")        
    # そのフレームでのノードの評価値の表示
    if status in ["score", "update", "end"]:
        score = framedata["score"]
        score_coord = calc_coord(score)
        text_coord = leftx if maxnode else rightx
        ha = "left" if maxnode else "right"
        self.abax.plot(score_coord, 0, "ok")
        self.abax.annotate(f"score = {score}", xy=(score_coord, 0),
                           xytext=(text_coord, 1), arrowprops=arrowprops, ha=ha)        
    # 子ノードの評価値の表示
    if status == "score":
        childscore = framedata["childscore"]
        childscore_coord = calc_coord(childscore)
        text_coord = rightx if maxnode else leftx
        ha = "right" if maxnode else "left"
        self.abax.plot(childscore_coord, 0, "og")
        self.abax.annotate(f"cscore = {childscore}", xy=(childscore_coord, 0),
                           xytext=(text_coord, 1), arrowprops=arrowprops, ha=ha)        
    # 置換表にデータが登録されていたかどうかの表示
    elif status =="tt":
        if framedata["registered_in_tt"]:
            textlist[3] = "置換表に登録済"
            textcolorlist[3] = "red"
            score = framedata["lower_bound"]
            score_coord = calc_coord(score)
            self.abax.plot(score_coord, 0, "om")
            self.abax.annotate(f"置換表の評価値 = {score}", xy=(score_coord, 0),
                               xytext=(centerx, 1), arrowprops=arrowprops, ha="center")        
        else:
            textlist[3] = "置換表に未登録"
    # ノードの評価値が更新されたかどうかの表示
    elif status == "update":
        if framedata["updated"]:
            textlist[3] = "評価値の更新"
            textcolorlist[3] = "red"
        else:
            textlist[3] = "評価値の更新なし"
    # 置換表に登録したかどうかの表示
    elif status == "end":
        if self.mbtree.use_tt:
            if framedata["registered_in_tt"]:
                textlist[3] = "置換表に登録されていたデータを利用"
            else:
                textlist[3] = "置換表への登録"
                textcolorlist[3] = "red"

    self.abfig.set_facecolor(facecolor)
    for i in range(linenum):
        self.abax.text(5, linetop - i * 0.7, textlist[i], c=textcolorlist[i])

    num_calculated = framedata["num_calculated"]
    num_pruned = framedata["num_pruned"]
    num_total = num_calculated + num_pruned
    num_ratio = num_calculated / num_total if num_total != 0 else 0
    prev_framedata = self.mbtree.ablist_by_score[self.prev_frame]
    prev_num_calculated = prev_framedata["num_calculated"]
    prev_num_pruned = prev_framedata["num_pruned"]
    prev_num_total = prev_num_calculated + prev_num_pruned
    diff_num_calculated = num_calculated - prev_num_calculated
    diff_num_pruned = num_pruned - prev_num_pruned
    diff_num_total = num_total - prev_num_total
    diff_num_ratio = diff_num_calculated / diff_num_total if diff_num_total != 0 else 0

    textlist = [ "計算済", "枝狩り", "合計", "割合" ]
    datalist = [ num_calculated, num_pruned, num_total, f"{num_ratio * 100:.1f}%"]
    diff_datalist = [ f"{diff_num_calculated:+d}", f"{diff_num_pruned:+d}", 
                    f"{diff_num_total:+d}", f"{diff_num_ratio * 100:.1f}%"]
    for i in range(4):
        self.abax.text(15, linetop - i * 0.7, textlist[i])
        self.abax.text(19.5, linetop - i * 0.7, datalist[i], ha="right")
        self.abax.text(22.5, linetop - i * 0.7, diff_datalist[i], ha="right")
        
Mbtree_Anim.update_frameinfo = update_frameinfo
修正箇所
def update_frameinfo(self):
元と同じなので省略
    minus_inf = -3 if self.mbtree.shortest_victory else -2
    plus_inf = 4 if self.mbtree.shortest_victory else 2

    # 範囲の色分け
+   if not minimax:
+       alphaorig = framedata["alphaorig"]
+       betaorig = framedata["betaorig"]
+       alphaorig_coord = calc_coord(alphaorig)
+       betaorig_coord = calc_coord(betaorig)
+       color = "lightgray" if maxnode else "aqua"
+       rect = patches.Rectangle(xy=(minus_inf, -0.5), width=alphaorig_coord-minus_inf,
+                                height=1, fc=color)
+       self.abax.add_patch(rect)
+       rect = patches.Rectangle(xy=(alphaorig_coord, -0.5), width=betaorig_coord-alphaorig_coord,
+                                height=1, fc="yellow")
+       self.abax.add_patch(rect)
+       color = "aqua" if maxnode else "lightgray"
+       rect = patches.Rectangle(xy=(betaorig_coord, -0.5), width=plus_inf-betaorig_coord,
+                                height=1, fc=color)
+       self.abax.add_patch(rect)
    # 数直線の描画    
    self.abax.plot(range(minus_inf, plus_inf + 1), [0] * (plus_inf + 1 - minus_inf) , "|-k")
元と同じなので省略
    arrowprops = { "arrowstyle": "->"}
    leftx = -3
    rightx = 4
    centerx = (leftx + rightx) / 2
    # α 値 と β 値の初期値の表示
+   if not minimax:
+       self.abax.plot(alphaorig_coord, 0, "or")
+       self.abax.annotate(f"α = {alphaorig}", xy=(alphaorig_coord, 0),
+                          xytext=(leftx, 1.7), arrowprops=arrowprops, ha="left")        
+       self.abax.plot(betaorig_coord, 0, "ob")
+       self.abax.annotate(f"β = {betaorig}", xy=(betaorig_coord, 0),
+                          xytext=(rightx, 1.7), arrowprops=arrowprops, ha="right")        
    # そのフレームでのノードの評価値の表示
    if status in ["score", "update", "end"]:
元と同じなので省略
        
Mbtree_Anim.update_frameinfo = update_frameinfo

実行結果はこれまでと同様なので省略しますが、上記の修正後に下記のプログラムで ミニマックス法での表示が正しく行なわれる ことが確認できます。

mbtree.calc_score_for_anim(mbtree.root, minimax=True)
Mbtree_Anim(mbtree, isscore=True)

下記のプログラムで αβ 法 の場合で計算したデータに対して 数直線上に α 値と β 値の初期値が表示 され、上部に注釈が表示される ことが確認できます。また、下図では 数直線上の全ての範囲が exact value なので 全ての範囲が黄色で塗りつぶされる ことが確認できます。

mbtree.calc_score_for_anim(mbtree.root, minimax=False, shortest_victory=True)
Mbtree_Anim(mbtree, isscore=True)

実行結果

ルートノードの α 値と β 値の初期値を設定 した場合はルートノードでも fail low と fail high の範囲が存在し、下記のプログラムの実行結果のように max ノードでの fail low と fail high の範囲が正しく色分けされる ことが確認できます。

mbtree.calc_score_for_anim(mbtree.root, minimax=False, shortest_victory=True, init_ab=True)
Mbtree_Anim(mbtree, isscore=True)

実行結果

また、上段の > をクリックすると下図のように min ノードでの fail low と fail high の範囲の色が 上図と逆 になり、正しく色分けされる ことが確認できます。

注釈の表示に関する問題点と修正

実は上記のプログラムには一つ 問題があります。どのような問題があるかについて少し考えてみて下さい。

上図で 下段の > をクリックすると、下図のように "score" のフレームが表示 されますが、その際に α 値と β 値の初期値の注釈の矢印 と、このノードの評価値や子ノードの評価値重なって見づらくなってしまう という問題があります。この問題を解決する方法について少し考えてみて下さい。

この問題を解決する方法として、本記事では α 値と β 値の初期値の注釈 から数直線への 矢印 を、そのノードの評価値や子ノードの評価値が数直線上に表示されない "start" と "tt" のフレームのみで表示する ことにします。また、α 値と β 値の初期値 は色分けされた範囲の 境目なので、矢印で示さなくてもその値が どこにあるかが見た目からわかる ので、数直線上の丸印"start" と "tt" のフレームのみで表示 することにします。

なお、ミニマックス法置換表にノードの評価値が登録されている場合"tt" のフレーム数直線上にその評価値を表示 するため同様の問題が発生するのではないかと思う人がいるかもしれません。αβ 法 の場合で 置換表にミニマックス値の範囲が登録されていた場合 は次回の記事で説明する 別の方法で表示 することで 表示が重ならないようにします

下記はそのように update_frameinfo を修正したプログラムです。

  • 8 ~ 14 行目:"start" と "tt" のフレームの場合は先程と同様の表示を行うように修正する
  • 15 ~ 17 行目:それ以外の状態のフレームの場合は α 値と β 値の初期値のメッセージだけを表示するように修正する
 1  def update_frameinfo(self):
元と同じなので省略
 2      arrowprops = { "arrowstyle": "->"}
 3      leftx = -3
 4      rightx = 4
 5      centerx = (leftx + rightx) / 2
 6      # α 値 と β 値の初期値の表示
 7      if not minimax:
 8          if status == "start" or status == "tt":
 9              self.abax.plot(alphaorig_coord, 0, "or")
10              self.abax.annotate(f"α = {alphaorig}", xy=(alphaorig_coord, 0),
11                                 xytext=(leftx, 1.7), arrowprops=arrowprops, ha="left")        
12              self.abax.plot(betaorig_coord, 0, "ob")
13              self.abax.annotate(f"β = {betaorig}", xy=(betaorig_coord, 0),
14                                 xytext=(rightx, 1.7), arrowprops=arrowprops, ha="right")        
15          else:
16              self.abax.text(leftx, 1.7, f"α = {alphaorig}", ha="left")   
17              self.abax.text(rightx, 1.7, f"β = {betaorig}", ha="right")       
元と同じなので省略
18          
19  Mbtree_Anim.update_frameinfo = update_frameinfo
行番号のないプログラム
def update_frameinfo(self):
    def calc_coord(score):
        return min(max(minus_inf, score), plus_inf)
        
    framedata = self.mbtree.ablist_by_score[self.play.value]
    status = framedata["status"]
    maxnode = self.selectednode.mb.turn == Marubatsu.CIRCLE
    minimax = self.mbtree.minimax
    
    self.abax.clear()
    self.abax.set_xlim(-4, 23)
    if minimax:
        self.abax.set_ylim(-1.5, 1.5)
    else:
        self.abax.set_ylim(-4.3, 2.3)
    self.abax.axis("off")

    minus_inf = -3 if self.mbtree.shortest_victory else -2
    plus_inf = 4 if self.mbtree.shortest_victory else 2

    # 範囲の色分け
    if not minimax:
        alphaorig = framedata["alphaorig"]
        betaorig = framedata["betaorig"]
        alphaorig_coord = calc_coord(alphaorig)
        betaorig_coord = calc_coord(betaorig)
        color = "lightgray" if maxnode else "aqua"
        rect = patches.Rectangle(xy=(minus_inf, -0.5), width=alphaorig_coord-minus_inf,
                                height=1, fc=color)
        self.abax.add_patch(rect)
        rect = patches.Rectangle(xy=(alphaorig_coord, -0.5), width=betaorig_coord-alphaorig_coord,
                                height=1, fc="yellow")
        self.abax.add_patch(rect)
        color = "aqua" if maxnode else "lightgray"
        rect = patches.Rectangle(xy=(betaorig_coord, -0.5), width=plus_inf-betaorig_coord,
                                height=1, fc=color)
        self.abax.add_patch(rect)
    # 数直線の描画    
    self.abax.plot(range(minus_inf, plus_inf + 1), [0] * (plus_inf + 1 - minus_inf) , "|-k")
    for num in range(minus_inf, plus_inf + 1):
        if num == minus_inf:
            numtext = "" if self.mbtree.init_ab else "-∞"
        elif num == plus_inf:
            numtext = "" if self.mbtree.init_ab else ""
        else:
            numtext = num
        self.abax.text(num, -1, numtext, ha="center")        

    # メッセージの表示
    if minimax:
        linenum = 4
        linetop = 1
    else:
        linenum = 9
        linetop = 1.7
    textlist = [""] * linenum
    textcolorlist = ["black"] * linenum

    algorithm = "mm 法" if self.mbtree.minimax else "αβ法"
    use_tt = "" if self.mbtree.use_tt else "×"
    shortest_victory = "" if self.mbtree.shortest_victory else "×"
    init_ab = "" if self.mbtree.init_ab else "×"
    textlist[0] = f"{algorithm} 置換表 {use_tt} 最短 {shortest_victory}"
    if not self.mbtree.minimax:
        textlist[0] += f" 初期値 {init_ab}"
    
    textlist[1] = f"深さ {self.selectednode.mb.move_count} "
    if maxnode:
        textlist[1] += "max node"
    else:
        textlist[1] += "min node"
    
    statusdata = {
        "start": {
            "text": "処理の開始",
            "color": "white"
        },
        "tt": {
            "text": "置換表の処理",
            "color": "honeydew"
        },
        "score": {
            "text": "子ノードの評価値",
            "color": "lightyellow"
        },
        "update": {
            "text": "更新処理",
            "color": "lightcyan"
        },
        "end": {
            "text": "評価値の確定",
            "color": "lavenderblush"
        },
    }
    textlist[2] = statusdata[status]["text"]
    facecolor = statusdata[status]["color"]
    
    arrowprops = { "arrowstyle": "->"}
    leftx = -3
    rightx = 4
    centerx = (leftx + rightx) / 2
    # α 値 と β 値の初期値の表示
    if not minimax:
        if status == "start" or status == "tt":
            self.abax.plot(alphaorig_coord, 0, "or")
            self.abax.annotate(f"α = {alphaorig}", xy=(alphaorig_coord, 0),
                               xytext=(leftx, 1.7), arrowprops=arrowprops, ha="left")        
            self.abax.plot(betaorig_coord, 0, "ob")
            self.abax.annotate(f"β = {betaorig}", xy=(betaorig_coord, 0),
                               xytext=(rightx, 1.7), arrowprops=arrowprops, ha="right")        
        else:
            self.abax.text(leftx, 1.7, f"α = {alphaorig}", ha="left")   
            self.abax.text(rightx, 1.7, f"β = {betaorig}", ha="right")       
    # そのフレームでのノードの評価値の表示
    if status in ["score", "update", "end"]:
        score = framedata["score"]
        score_coord = calc_coord(score)
        text_coord = leftx if maxnode else rightx
        ha = "left" if maxnode else "right"
        self.abax.plot(score_coord, 0, "ok")
        self.abax.annotate(f"score = {score}", xy=(score_coord, 0),
                           xytext=(text_coord, 1), arrowprops=arrowprops, ha=ha)        
    # 子ノードの評価値の表示
    if status == "score":
        childscore = framedata["childscore"]
        childscore_coord = calc_coord(childscore)
        text_coord = rightx if maxnode else leftx
        ha = "right" if maxnode else "left"
        self.abax.plot(childscore_coord, 0, "og")
        self.abax.annotate(f"cscore = {childscore}", xy=(childscore_coord, 0),
                           xytext=(text_coord, 1), arrowprops=arrowprops, ha=ha)        
    # 置換表にデータが登録されていたかどうかの表示
    elif status =="tt":
        if framedata["registered_in_tt"]:
            textlist[3] = "置換表に登録済"
            textcolorlist[3] = "red"
            score = framedata["lower_bound"]
            score_coord = calc_coord(score)
            self.abax.plot(score_coord, 0, "om")
            self.abax.annotate(f"置換表の評価値 = {score}", xy=(score_coord, 0),
                            xytext=(centerx, 1), arrowprops=arrowprops, ha="center")        
        else:
            textlist[3] = "置換表に未登録"
    # ノードの評価値が更新されたかどうかの表示
    elif status == "update":
        if framedata["updated"]:
            textlist[3] = "評価値の更新"
            textcolorlist[3] = "red"
        else:
            textlist[3] = "評価値の更新なし"
    # 置換表に登録したかどうかの表示
    elif status == "end":
        if self.mbtree.use_tt:
            if framedata["registered_in_tt"]:
                textlist[3] = "置換表に登録されていたデータを利用"
            else:
                textlist[3] = "置換表への登録"
                textcolorlist[3] = "red"

    self.abfig.set_facecolor(facecolor)
    for i in range(linenum):
        self.abax.text(5, linetop - i * 0.7, textlist[i], c=textcolorlist[i])

    num_calculated = framedata["num_calculated"]
    num_pruned = framedata["num_pruned"]
    num_total = num_calculated + num_pruned
    num_ratio = num_calculated / num_total if num_total != 0 else 0
    prev_framedata = self.mbtree.ablist_by_score[self.prev_frame]
    prev_num_calculated = prev_framedata["num_calculated"]
    prev_num_pruned = prev_framedata["num_pruned"]
    prev_num_total = prev_num_calculated + prev_num_pruned
    diff_num_calculated = num_calculated - prev_num_calculated
    diff_num_pruned = num_pruned - prev_num_pruned
    diff_num_total = num_total - prev_num_total
    diff_num_ratio = diff_num_calculated / diff_num_total if diff_num_total != 0 else 0

    textlist = [ "計算済", "枝狩り", "合計", "割合" ]
    datalist = [ num_calculated, num_pruned, num_total, f"{num_ratio * 100:.1f}%"]
    diff_datalist = [ f"{diff_num_calculated:+d}", f"{diff_num_pruned:+d}", 
                    f"{diff_num_total:+d}", f"{diff_num_ratio * 100:.1f}%"]
    for i in range(4):
        self.abax.text(15, linetop - i * 0.7, textlist[i])
        self.abax.text(19.5, linetop - i * 0.7, datalist[i], ha="right")
        self.abax.text(22.5, linetop - i * 0.7, diff_datalist[i], ha="right")
        
Mbtree_Anim.update_frameinfo = update_frameinfo
修正箇所
def update_frameinfo(self):
元と同じなので省略
    arrowprops = { "arrowstyle": "->"}
    leftx = -3
    rightx = 4
    centerx = (leftx + rightx) / 2
    # α 値 と β 値の初期値の表示
    if not minimax:
-       self.abax.plot(alphaorig_coord, 0, "or")
-       self.abax.annotate(f"α = {alphaorig}", xy=(alphaorig_coord, 0),
-                          xytext=(leftx, 1.7), arrowprops=arrowprops, ha="left")        
-       self.abax.plot(betaorig_coord, 0, "ob")
-       self.abax.annotate(f"β = {betaorig}", xy=(betaorig_coord, 0),
-                          xytext=(rightx, 1.7), arrowprops=arrowprops, ha="right")        
+       if status == "start" or status == "tt":
+           self.abax.plot(alphaorig_coord, 0, "or")
+           self.abax.annotate(f"α = {alphaorig}", xy=(alphaorig_coord, 0),
+                              xytext=(leftx, 1.7), arrowprops=arrowprops, ha="left")        
+           self.abax.plot(betaorig_coord, 0, "ob")
+           self.abax.annotate(f"β = {betaorig}", xy=(betaorig_coord, 0),
+                              xytext=(rightx, 1.7), arrowprops=arrowprops, ha="right")        
+       else:
+           self.abax.text(leftx, 1.7, f"α = {alphaorig}", ha="left")   
+           self.abax.text(rightx, 1.7, f"β = {betaorig}", ha="right")       
元と同じなので省略
        
Mbtree_Anim.update_frameinfo = update_frameinfo

実行結果は同じなので省略しますが、下記のプログラムで 置換表付き αβ 法 の場合で計算したデータに対して Mbtree_Anim を表示すると "start" のフレームに対して先程と同じ表示 が行われます。

mbtree.calc_score_for_anim(mbtree.root, minimax=False, use_tt=True, 
                           init_ab=True, shortest_victory=True)
Mbtree_Anim(mbtree, isscore=True)

次に 下段の > をクリックすると下図のように "tt" のフレーム の状態の数直線の部分が "start" のフレームと同様の方法で表示 されることが確認できます。

もう一度 下段の > をクリックすると下図のように "start" のフレーム の状態が下記のように 意図通りに表示される ことが確認できます。表示は省略しますが "update"、"end" のフレームでも下図と同様の表示が行われることを確認して下さい。

  • 数直線上に α 値と β 値の初期値の丸が表示されない
  • 上部の α 値と β 値の初期値の表示から数直線上に矢印が表示されない

なお、現時点では置換表にミニマックス値の範囲が登録されている場合は下図のように表示が重なりますが、下図に表示されている置換表の評価値 はミニマックス方の場合の表示なので αβ 法の場合は正しくありません。この場合の正しい表示と、表示が重ならないようにする工夫については次回の記事で実装します。

範囲の説明の表示

本記事では 範囲の説明の表示 を右の 枝狩りが行われた数などの下に表示 することにします。その理由は、真ん中の下 には次回の記事で 別のメッセージを表示する からです。

また、それぞれの範囲の説明 には上から順に fail lowexact valuefail high を表示することにします。なお、今回の記事 では 範囲の説明には常に同じ表示 を行いますが、次回の記事で update_ab での範囲の説明の表示のように、状況に応じて範囲の説明内容や表示の色を変える という工夫を行う予定です。

なお、以前の記事update_ab範囲の表示を実装する際 には、常に一番上 に α 狩りまたは β 狩りを行う 水色の範囲を表示 していましたが、update_frameinfo では常に上から順に fail lowexact valuefail high の表示を行うことにしたので max ノード と min ノード で表示する範囲の 色の順番が逆 になります。

下記はそのように update_frameinfo を修正したプログラムです。

  • 12 ~ 24 行目:αβ 法の場合に範囲の説明の表示を行う
  • 13 ~ 17 行目:それぞれの範囲の色を計算して facecolorlist に代入する
  • 18、19 行目:それぞれの範囲の説明と色を変数に代入する。現時点では範囲の説明と色は常に同じだが、次回の記事で状況に応じて説明と色を変えるようにする予定である
  • 20 ~ 24 行目:それぞれの範囲の色を表す長方形と、説明文を表示する
 1  def update_frameinfo(self):
元と同じなので省略
 2      textlist = [ "計算済", "枝狩り", "合計", "割合" ]
 3      datalist = [ num_calculated, num_pruned, num_total, f"{num_ratio * 100:.1f}%"]
 4      diff_datalist = [ f"{diff_num_calculated:+d}", f"{diff_num_pruned:+d}", 
 5                      f"{diff_num_total:+d}", f"{diff_num_ratio * 100:.1f}%"]
 6      for i in range(4):
 7          self.abax.text(15, linetop - i * 0.7, textlist[i])
 8          self.abax.text(19.5, linetop - i * 0.7, datalist[i], ha="right")
 9          self.abax.text(22.5, linetop - i * 0.7, diff_datalist[i], ha="right")
10          
11      # 範囲の説明の表示
12      if not minimax:
13          facecolorlist = [
14              "lightgray" if maxnode else "aqua", 
15              "yellow",
16              "aqua" if maxnode else "lightgray", 
17          ]
18          textlist = ["fail low", "exact value", "fail high"]
19          textcolorlist = ["black", "black", "black"]
20          for i in range(3):
21              rect = patches.Rectangle(xy=(15, linetop - 0.1 - (i + 5) * 0.7), 
22                                       width=0.8, height=0.5, fc=facecolorlist[i], ec="k")
23              self.abax.add_patch(rect)
24              self.abax.text(16.2, linetop - (i + 5) * 0.7, textlist[i], c=textcolorlist[i])  
25           
26  Mbtree_Anim.update_frameinfo = update_frameinfo
行番号のないプログラム
def update_frameinfo(self):
    def calc_coord(score):
        return min(max(minus_inf, score), plus_inf)
        
    framedata = self.mbtree.ablist_by_score[self.play.value]
    status = framedata["status"]
    maxnode = self.selectednode.mb.turn == Marubatsu.CIRCLE
    minimax = self.mbtree.minimax
    
    self.abax.clear()
    self.abax.set_xlim(-4, 23)
    if minimax:
        self.abax.set_ylim(-1.5, 1.5)
    else:
        self.abax.set_ylim(-4.3, 2.3)
    self.abax.axis("off")

    minus_inf = -3 if self.mbtree.shortest_victory else -2
    plus_inf = 4 if self.mbtree.shortest_victory else 2

    # 範囲の色分け
    if not minimax:
        alphaorig = framedata["alphaorig"]
        betaorig = framedata["betaorig"]
        alphaorig_coord = calc_coord(alphaorig)
        betaorig_coord = calc_coord(betaorig)
        color = "lightgray" if maxnode else "aqua"
        rect = patches.Rectangle(xy=(minus_inf, -0.5), width=alphaorig_coord-minus_inf,
                                height=1, fc=color)
        self.abax.add_patch(rect)
        rect = patches.Rectangle(xy=(alphaorig_coord, -0.5), width=betaorig_coord-alphaorig_coord,
                                height=1, fc="yellow")
        self.abax.add_patch(rect)
        color = "aqua" if maxnode else "lightgray"
        rect = patches.Rectangle(xy=(betaorig_coord, -0.5), width=plus_inf-betaorig_coord,
                                height=1, fc=color)
        self.abax.add_patch(rect)
    # 数直線の描画    
    self.abax.plot(range(minus_inf, plus_inf + 1), [0] * (plus_inf + 1 - minus_inf) , "|-k")
    for num in range(minus_inf, plus_inf + 1):
        if num == minus_inf:
            numtext = "" if self.mbtree.init_ab else "-∞"
        elif num == plus_inf:
            numtext = "" if self.mbtree.init_ab else ""
        else:
            numtext = num
        self.abax.text(num, -1, numtext, ha="center")        

    # メッセージの表示
    if minimax:
        linenum = 4
        linetop = 1
    else:
        linenum = 9
        linetop = 1.7
    textlist = [""] * linenum
    textcolorlist = ["black"] * linenum

    algorithm = "mm 法" if self.mbtree.minimax else "αβ法"
    use_tt = "" if self.mbtree.use_tt else "×"
    shortest_victory = "" if self.mbtree.shortest_victory else "×"
    init_ab = "" if self.mbtree.init_ab else "×"
    textlist[0] = f"{algorithm} 置換表 {use_tt} 最短 {shortest_victory}"
    if not self.mbtree.minimax:
        textlist[0] += f" 初期値 {init_ab}"
    
    textlist[1] = f"深さ {self.selectednode.mb.move_count} "
    if maxnode:
        textlist[1] += "max node"
    else:
        textlist[1] += "min node"
    
    statusdata = {
        "start": {
            "text": "処理の開始",
            "color": "white"
        },
        "tt": {
            "text": "置換表の処理",
            "color": "honeydew"
        },
        "score": {
            "text": "子ノードの評価値",
            "color": "lightyellow"
        },
        "update": {
            "text": "更新処理",
            "color": "lightcyan"
        },
        "end": {
            "text": "評価値の確定",
            "color": "lavenderblush"
        },
    }
    textlist[2] = statusdata[status]["text"]
    facecolor = statusdata[status]["color"]
    
    arrowprops = { "arrowstyle": "->"}
    leftx = -3
    rightx = 4
    centerx = (leftx + rightx) / 2
    # α 値 と β 値の初期値の表示
    if not minimax:
        if status == "start" or status == "tt":
            self.abax.plot(alphaorig_coord, 0, "or")
            self.abax.annotate(f"α = {alphaorig}", xy=(alphaorig_coord, 0),
                           xytext=(leftx, 1.7), arrowprops=arrowprops, ha="left")        
            self.abax.plot(betaorig_coord, 0, "ob")
            self.abax.annotate(f"β = {betaorig}", xy=(betaorig_coord, 0),
                           xytext=(rightx, 1.7), arrowprops=arrowprops, ha="right")        
        else:
            self.abax.text(leftx, 1.7, f"α = {alphaorig}", ha="left")   
            self.abax.text(rightx, 1.7, f"β = {betaorig}", ha="right")       
    # そのフレームでのノードの評価値の表示
    if status in ["score", "update", "end"]:
        score = framedata["score"]
        score_coord = calc_coord(score)
        text_coord = leftx if maxnode else rightx
        ha = "left" if maxnode else "right"
        self.abax.plot(score_coord, 0, "ok")
        self.abax.annotate(f"score = {score}", xy=(score_coord, 0),
                           xytext=(text_coord, 1), arrowprops=arrowprops, ha=ha)        
    # 子ノードの評価値の表示
    if status == "score":
        childscore = framedata["childscore"]
        childscore_coord = calc_coord(childscore)
        text_coord = rightx if maxnode else leftx
        ha = "right" if maxnode else "left"
        self.abax.plot(childscore_coord, 0, "og")
        self.abax.annotate(f"cscore = {childscore}", xy=(childscore_coord, 0),
                           xytext=(text_coord, 1), arrowprops=arrowprops, ha=ha)        
    # 置換表にデータが登録されていたかどうかの表示
    elif status =="tt":
        if framedata["registered_in_tt"]:
            textlist[3] = "置換表に登録済"
            textcolorlist[3] = "red"
            score = framedata["lower_bound"]
            score_coord = calc_coord(score)
            self.abax.plot(score_coord, 0, "om")
            self.abax.annotate(f"置換表の評価値 = {score}", xy=(score_coord, 0),
                            xytext=(centerx, 1), arrowprops=arrowprops, ha="center")        
        else:
            textlist[3] = "置換表に未登録"
    # ノードの評価値が更新されたかどうかの表示
    elif status == "update":
        if framedata["updated"]:
            textlist[3] = "評価値の更新"
            textcolorlist[3] = "red"
        else:
            textlist[3] = "評価値の更新なし"
    # 置換表に登録したかどうかの表示
    elif status == "end":
        if self.mbtree.use_tt:
            if framedata["registered_in_tt"]:
                textlist[3] = "置換表に登録されていたデータを利用"
            else:
                textlist[3] = "置換表への登録"
                textcolorlist[3] = "red"

    self.abfig.set_facecolor(facecolor)
    for i in range(linenum):
        self.abax.text(5, linetop - i * 0.7, textlist[i], c=textcolorlist[i])

    num_calculated = framedata["num_calculated"]
    num_pruned = framedata["num_pruned"]
    num_total = num_calculated + num_pruned
    num_ratio = num_calculated / num_total if num_total != 0 else 0
    prev_framedata = self.mbtree.ablist_by_score[self.prev_frame]
    prev_num_calculated = prev_framedata["num_calculated"]
    prev_num_pruned = prev_framedata["num_pruned"]
    prev_num_total = prev_num_calculated + prev_num_pruned
    diff_num_calculated = num_calculated - prev_num_calculated
    diff_num_pruned = num_pruned - prev_num_pruned
    diff_num_total = num_total - prev_num_total
    diff_num_ratio = diff_num_calculated / diff_num_total if diff_num_total != 0 else 0

    textlist = [ "計算済", "枝狩り", "合計", "割合" ]
    datalist = [ num_calculated, num_pruned, num_total, f"{num_ratio * 100:.1f}%"]
    diff_datalist = [ f"{diff_num_calculated:+d}", f"{diff_num_pruned:+d}", 
                    f"{diff_num_total:+d}", f"{diff_num_ratio * 100:.1f}%"]
    for i in range(4):
        self.abax.text(15, linetop - i * 0.7, textlist[i])
        self.abax.text(19.5, linetop - i * 0.7, datalist[i], ha="right")
        self.abax.text(22.5, linetop - i * 0.7, diff_datalist[i], ha="right")
        
    # 範囲の説明の表示
    if not minimax:
        facecolorlist = [
            "lightgray" if maxnode else "aqua", 
            "yellow",
            "aqua" if maxnode else "lightgray", 
        ]
        textlist = ["fail low", "exact value", "fail high"]
        textcolorlist = ["black", "black", "black"]
        for i in range(3):
            rect = patches.Rectangle(xy=(15, linetop - 0.1 - (i + 5) * 0.7), 
                                     width=0.8, height=0.5, fc=facecolorlist[i], ec="k")
            self.abax.add_patch(rect)
            self.abax.text(16.2, linetop - (i + 5) * 0.7, textlist[i], c=textcolorlist[i])  
         
Mbtree_Anim.update_frameinfo = update_frameinfo
修正箇所
def update_frameinfo(self):
元と同じなので省略
    textlist = [ "計算済", "枝狩り", "合計", "割合" ]
    datalist = [ num_calculated, num_pruned, num_total, f"{num_ratio * 100:.1f}%"]
    diff_datalist = [ f"{diff_num_calculated:+d}", f"{diff_num_pruned:+d}", 
                    f"{diff_num_total:+d}", f"{diff_num_ratio * 100:.1f}%"]
    for i in range(4):
        self.abax.text(15, linetop - i * 0.7, textlist[i])
        self.abax.text(19.5, linetop - i * 0.7, datalist[i], ha="right")
        self.abax.text(22.5, linetop - i * 0.7, diff_datalist[i], ha="right")
        
    # 範囲の説明の表示
+   if not minimax:
+       facecolorlist = [
+           "lightgray" if maxnode else "aqua", 
+           "yellow",
+           "aqua" if maxnode else "lightgray", 
+       ]
+       textlist = ["fail low", "exact value", "fail high"]
+       textcolorlist = ["black", "black", "black"]
+       for i in range(3):
+           rect = patches.Rectangle(xy=(15, linetop - 0.1 - (i + 5) * 0.7), 
+                                    width=0.8, height=0.5, fc=facecolorlist[i], ec="k")
+           self.abax.add_patch(rect)
+           self.abax.text(16.2, linetop - (i + 5) * 0.7, textlist[i], c=textcolorlist[i])   
         
Mbtree_Anim.update_frameinfo = update_frameinfo

下記のプログラムで αβ 法 の場合で計算したデータに対して Mbtree_Anim を表示すると、実行結果のように 右下に範囲の説明が正しく表示される ようになったことが確認できます。

mbtree.calc_score_for_anim(mbtree.root, minimax=False, use_tt=True, 
                           init_ab=True, shortest_victory=True)
Mbtree_Anim(mbtree, isscore=True)

実行結果

また、上段の > ボタンを 2 回クリック して min ノードを表示 すると下図のように min ノードでの範囲の色 の順番が上図と逆になり、正しく表示される 事が確認できます。

今回の記事のまとめ

今回の記事では、αβ 法の視覚化 のための Mbtree_Anim の修正 のうち、常に表示する内容の表示 の実装を行いました。次回の記事では残りの表示の実装を行います。

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

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

次回の記事

  1. どのように表示するかについては次回の記事で説明します

  2. Axes の表示範囲の y 座標の上部を少し上にずらしたのはそのためです

  3. 数直線の描画より後で範囲の色分けを行うと、数直線の上に範囲の色分けが表示されてしまうため数直線が表示されなくなってしまうからです

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?