目次と前回の記事
これまでに作成したモジュール
以下のリンクから、これまでに作成したモジュールを見ることができます。
リンク | 説明 |
---|---|
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_frameinfo
は calc_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 low、exact value、fail high を表示することにします。なお、今回の記事 では 範囲の説明には常に同じ表示 を行いますが、次回の記事で update_ab
での範囲の説明の表示のように、状況に応じて範囲の説明内容や表示の色を変える という工夫を行う予定です。
なお、以前の記事で update_ab
で 範囲の表示を実装する際 には、常に一番上 に α 狩りまたは β 狩りを行う 水色の範囲を表示 していましたが、update_frameinfo
では常に上から順に fail low、exact value、fail 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 |
次回の記事