目次と前回の記事
これまでに作成したモジュール
以下のリンクから、これまでに作成したモジュールを見ることができます。
リンク | 説明 |
---|---|
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 のバグの修正と改良
前回の記事で αβ 法による評価値の計算を紹介しましたが、αβ 法 が行う処理の手順は 直観的にわかりづらい と思った人が多いのではないかと思います。そこで、以前の記事で Mbtree_Anim クラスを作成して幅優先アルゴリズムや深さ優先アルゴリズムで ゲーム木が作成される手順 や 評価値が計算される手順 を アニメーションで視覚化 したように、αβ 法によって評価値が計算される手順も Mbtree_Anim で視覚化できるようにすることにします。
その前に、Mbtree_Anim にバグがあることが判明したので、今回の記事ではその修正を行うことにします。また、Mbtree_Anim の表示の改良を行うことにします。
Mbtree_Anim クラスで新たに発生したバグ
下記は、以前の記事で dfscore.mbtree
に保存した、深さ優先アルゴリズムで評価値を計算した ゲーム木の評価値の計算過程を Mbtree_Anim で表示 するプログラムです1。
from tree import Mbtree, Mbtree_Anim
mbtree = Mbtree.load("../data/dfscore")
Mbtree_Anim(mbtree, isscore=True)
実行結果
実行結果から、以下の問題がある事がかわかります。
- 一番上 の局面の 評価値の表示 が、Figure の外に はみ出して見えなくなっている
- 評価値の 文字が大きすぎて、上部が一つ上の局面に 重なっている
バグの原因の検証
この問題は、以前の記事で dfscore.mbtree を Mbtree_Anim で表示した際には、局面の上に 評価値を表示していなかった ため、発生していませんでした。
その後の記事 で Mbtree クラスの draw_subtree
メソッドを修正して 局面の上に評価値を表示する ように修正した時点でこの バグが発生していた のですが、その際に Mbtree_Anim での表示 が正しく行われることを 確認していなかった ため、今回の記事まで そのようなバグがあることが 判明していませんでした。
一番上の局面の評価値の 表示がはみ出る問題 は、以前の記事 で同じ問題に 対処した方法と同じ方法 で修正することができます。
評価値の文字の表示 は Mbtree クラスの draw_subtree
メソッドで行っており、評価値の 文字の大きさは仮引数 size
に代入された値 で決まります。現状の Mbtree_Anim クラスは update_gui
メソッド内で draw_subtree
メソッドを呼び出す際 に、仮引数 size
に対応する 実引数を記述していないため、適切な大きさで評価値が表示されません2。そのため、この問題は size=self.size
を実引数に記述して draw_subtree
メソッドを呼び出すように修正することで解決することができます。
Mbtree_Anim クラスの __init__
メソッドの修正
まず、Mbtree_Anim クラスの __init__
メソッドを下記のプログラムのように修正します。この修正について忘れた方は、以前の記事を復習してください。
-
6 行目:局面の上に評価値を表示するようになったので、その分だけ表示の高さを表す
height
属性の値を 64 から 65 に増やす -
8 行目:クラスの外で
__init__
メソッドを定義できるように、super
を修正する。この修正について忘れた方は 以前の記事 を復習すること
1 def __init__(self, mbtree:Mbtree, isscore:bool=False, size:float=0.15):
2 self.mbtree = mbtree
3 self.isscore = isscore
4 self.size = size
5 self.width = 50
6 self.height = 65
7 self.nodelist = self.mbtree.nodelist_by_score if isscore else self.mbtree.nodelist
8 self.nodenum = len(self.nodelist)
9 super(Mbtree_Anim, self).__init__()
10
11 Mbtree_Anim.__init__ = __init__
行番号のないプログラム
def __init__(self, mbtree:Mbtree, isscore:bool=False, size:float=0.15):
self.mbtree = mbtree
self.isscore = isscore
self.size = size
self.width = 50
self.height = 65
self.nodelist = self.mbtree.nodelist_by_score if isscore else self.mbtree.nodelist
self.nodenum = len(self.nodelist)
super(Mbtree_Anim, self).__init__()
Mbtree_Anim.__init__ = __init__
修正箇所
def __init__(self, mbtree:Mbtree, isscore:bool=False, size:float=0.15):
self.mbtree = mbtree
self.isscore = isscore
self.size = size
self.width = 50
- self.height = 64
+ self.height = 65
self.nodelist = self.mbtree.nodelist_by_score if isscore else self.mbtree.nodelist
self.nodenum = len(self.nodelist)
- super().__init__()
+ super(Mbtree_Anim, self).__init__()
Mbtree_Anim.__init__ = __init__
Mbtree_Anim クラスの update_gui
メソッドの修正
次に、update_gui
メソッドを下記のプログラムのように修正します。
- 4 行目:Axes の y 軸方向の表示範囲を -1 だけずらして、一番上の局面の評価値が表示されるように修正する。この修正について忘れた方は 以前の記事 を復習すること
-
7 行目:
draw_subtre
メソッドを呼び出す際に、キーワード引数size=self.size
を記述するようにすることで、評価値の文字が適切な大きさで表示されるように修正する
1 def update_gui(self):
2 self.ax.clear()
3 self.ax.set_xlim(-1, self.width - 1)
4 self.ax.set_ylim(-1, self.height - 1)
元と同じなので省略
5 self.mbtree.draw_subtree(centernode=self.centernode, selectednode=self.selectednode,
6 anim_frame=self.play.value, isscore=self.isscore,
7 ax=self.ax, maxdepth=maxdepth, size=self.size)
元と同じなので省略
8
9 Mbtree_Anim.update_gui = update_gui
行番号のないプログラム
def update_gui(self):
self.ax.clear()
self.ax.set_xlim(-1, self.width - 1)
self.ax.set_ylim(-1, self.height - 1)
self.ax.invert_yaxis()
self.ax.axis("off")
self.selectednode = self.nodelist[self.play.value]
self.centernode = self.selectednode
if self.mbtree.algo == "bf":
if self.centernode.depth > 0:
self.centernode = self.centernode.parent
while self.centernode.depth > 6:
self.centernode = self.centernode.parent
if self.centernode.depth <= 4:
maxdepth = self.centernode.depth + 1
elif self.centernode.depth == 5:
maxdepth = 7
else:
maxdepth = 9
self.mbtree.draw_subtree(centernode=self.centernode, selectednode=self.selectednode,
anim_frame=self.play.value, isscore=self.isscore,
ax=self.ax, maxdepth=maxdepth, size=self.size)
disabled = self.play.value == 0
self.set_button_status(self.prev_button, disabled=disabled)
disabled = self.play.value == self.nodenum - 1
self.set_button_status(self.next_button, disabled=disabled)
Mbtree_Anim.update_gui = update_gui
修正箇所
def update_gui(self):
self.ax.clear()
self.ax.set_xlim(-1, self.width - 1)
- self.ax.set_ylim(0, self.height)
+ self.ax.set_ylim(-1, self.height - 1)
元と同じなので省略
self.mbtree.draw_subtree(centernode=self.centernode, selectednode=self.selectednode,
anim_frame=self.play.value, isscore=self.isscore,
- ax=self.ax, maxdepth=maxdepth, size=self.size)
+ ax=self.ax, maxdepth=maxdepth)
元と同じなので省略
Mbtree_Anim.update_gui = update_gui
上記の修正後に下記のプログラムを実行すると、実行結果のように問題が修正されたことが確認できます。
Mbtree_Anim(mbtree, isscore=True)
実行結果
下記のプログラムのように キーワード引数 size=0.5
を記述して 別の大きさで表示 した場合でも、実行結果のように評価値の文字が適切な大きさで表示されることが確認できます。
Mbtree_Anim(mbtree, isscore=True, size=0.5)
実行結果(大きすぎるので上部のみの画像です)
Mbtree_Anim の改良
現状の Mbtree_Anim では、まだ 評価値を計算していない局面 を 灰色で表示 しますが、上図のように 評価値を計算していない局面 に 評価値が表示 されてしまう点が 不自然 です。そこで、評価値を計算していない局面 の 評価値を表示しない ように改良することにします。
局面の 評価値の表示 は、Node クラスの draw_node
メソッドの 仮引数 show_score
に True
が代入 されている場合に行われます。draw_node
メソッドは draw_subtree
メソッド内で呼び出されている ので、draw_subtree
内で draw_node
を呼び出す際 に、キーワード引数 show_score
に適切な値を代入する ようにプログラムを修正すれば良いことがわかります。
draw_node
メソッドは、draw_subtree
内の 5 箇所で、下記のプログラムのように記述されて呼び出されているので、それらのプログラムを ノードの評価値が計算されている場合は show_score=True
、そうでない場合は show_score=False
を実引数に記述して draw_node
を呼び出すように修正すればよいことがわかります。どのように修正すれば良いかについて少し考えてみて下さい。
darkness = calc_darkness(node)
rect = node.draw_node(ax=ax, maxdepth=maxdepth, emphasize=emphasize, darkness=darkness,
show_score=show_score, size=size, lw=lw, dx=dx, dy=dy)
calc_darkness
の復習
上記の darkness
は、局面の表示の暗さ を表す値で、calc_darkness
によって計算 されます。Mbtree_Anim から draw_subtree
を呼び出して ゲーム木の部分木を表示する際 には、評価値が計算されていない局面を暗く 表示し、計算された局面を明るく 表示します。
従って、darkness
の値が暗い場合に show_score=False
を、そうでない場合は show_score=True
を記述して draw_node
を呼び出すようにすれば良いことがわかります。
calc_darkness
は以前の記事で定義した関数ですが、忘れた方も多いと思いますので、その処理を復習することにします。下記は、calc_darkness
の定義です。
1 def calc_darkness(node):
2 if show_bestmove:
3 if node.parent is None:
4 return 0
5 elif node.mb.last_move in node.parent.bestmoves:
6 return 0
7 else:
8 return 0.2
9
10 if anim_frame is None:
11 return 0
12 index = node.score_index if isscore else node.id
13 return 0.5 if index > anim_frame else 0
まず、Mbtree_Anim から draw_subtree
が呼び出された際の calc_darkness
の処理を復習 します。Mbtree_Anim
では、以下のプログラムで draw_subtree
を呼び出します。
self.mbtree.draw_subtree(centernode=self.centernode, selectednode=self.selectednode,
anim_frame=self.play.value, isscore=self.isscore, ax=self.ax,
maxdepth=maxdepth)
また、draw_subtree
の定義 は以下のプログラムのようになっているので、Mbtree_Anim から draw_subtree
を呼び出した場合は、仮引数 show_bestmove
には False
が、anim_frame
にはアニメーションの フレーム番号を表す数値 が代入されます。
def draw_subtree(self, centernode=None, selectednode=None, ax=None, anim_frame=None,
isscore=False, show_bestmove=False, show_score=True, size=0.25, lw=0.8,
maxdepth=2):
従って、Mbtree_Anim から draw_subtree
を呼び出した場合は、calc_darkness
の中の 下記のプログラムだけが実行 されます。評価値の計算過程を表示 する場合は、Mbtree_Anim のインスタンスを作成する際に isscore=True
を記述するので、下記の 2 行目で ノードの評価値が計算されていない場合は 0.5 が、計算されている場合は 0 を返す という処理が行われていることがわかります。
1 index = node.score_index if isscore else node.id
2 return 0.5 if index > anim_frame else 0
推奨しないプログラムの修正方法
上記から、ノードの 評価値が計算されていない場合 は darkness
に 0.5 が代入 されるので、draw_subtree
内で draw_node
を呼び出す前 に、下記のプログラムの 2、3 行目を追加して、ノードの評価値が計算されていないことを表す darkness
に 0.5 が代入されている場合 に show_score
に False
を代入 すればよいと思った人がいるかもしれませんが、このような修正は行わないほうが良い でしょう。その理由について少し考えてみて下さい。
1 darkness = calc_darkness(node)
2 if darkness == 0.5:
3 show_score = False
4 rect = node.draw_node(ax=ax, maxdepth=maxdepth, emphasize=emphasize, darkness=darkness,
5 show_score=show_score, size=size, lw=lw, dx=dx, dy=dy)
修正箇所
darkness = calc_darkness(node)
+ if darkness == 0.5:
+ show_score = False
rect = node.draw_node(ax=ax, maxdepth=maxdepth, emphasize=emphasize, darkness=darkness,
show_score=show_score, size=size, lw=lw, dx=dx, dy=dy)
上記の修正で、確かにプログラムは正しく動作しますが、以下のような問題があります。
第一に、現状の calc_darkness
が 0.5 を返り値として返す のは、Mbtree_Anim から draw_subtree
が呼び出された際に、ノードの評価値が計算されていない場合だけ ですが、下記のプログラムの 8 行目のように、それ以外の場合で 0.5 を返す ように修正すると プログラムが正しく動作しなくなります。その理由について少し考えてみて下さい。
1 def calc_darkness(node):
2 if show_bestmove:
3 if node.parent is None:
4 return 0
5 elif node.mb.last_move in node.parent.bestmoves:
6 return 0
7 else:
8 return 0.5
9
10 if anim_frame is None:
11 return 0
12 index = node.score_index if isscore else node.id
13 return 0.5 if index > anim_frame else 0
修正箇所
def calc_darkness(node):
if show_bestmove:
if node.parent is None:
return 0
elif node.mb.last_move in node.parent.bestmoves:
return 0
else:
- return 0.2
+ return 0.5
if anim_frame is None:
return 0
index = node.score_index if isscore else node.id
return 0.5 if index > anim_frame else 0
例えば、Mbtree_GUI から draw_subtree
を呼び出す場合などで、show_bestmove
の値が True
になる場合 があります。その場合は 最善手でない着手が行われた局面 に対して 8 行目の処理が実行され、返り値として 0.5 が返される ことになります。その結果、最善手でない着手が行われた局面 の 評価値が表示されなくなってしまう という問題が発生します。
このように、0.5 という数値 で評価値が計算されていないノードであるかどうかを 判定する と、別の理由で 0.5 という数値が計算 された場合と 処理が混同 してしまいます。
第二に、評価値が計算されていない局面の 暗さは 0.5 でなくても構わない ので、例えば 暗さを 0.5 から 0.6 に変更しようと思った場合 に行う 修正 が、calc_darkness
の最後の行を下記のプログラムのように 変更するだけでは済まない という問題があります。
return 0.6 if index > anim_frame else 0
具体的には、draw_node
を呼び出す直前 の下記の 2 行目のプログラムの 条件式の 0.5 を 0.6 に修正する必要 があります。さらに、draw_subtree
内では draw_node
を 5 箇所で呼び出している ので、その全ての場所 で下記の 2 行目のような 修正を行う必要がある点が面倒 です。また、修正し忘れによるバグが発生する可能性が高い という問題もあります。
1 darkness = calc_darkness(node)
2 if darkness == 0.6:
3 show_score = False
4 rect = node.draw_node(ax=ax, maxdepth=maxdepth, emphasize=emphasize, darkness=darkness,
5 show_score=show_score, size=size, lw=lw, dx=dx, dy=dy)
修正箇所
darkness = calc_darkness(node)
- if darkness == 0.5:
+ if darkness == 0.6:
show_score = False
rect = node.draw_node(ax=ax, maxdepth=maxdepth, emphasize=emphasize, darkness=darkness,
show_score=show_score, size=size, lw=lw, dx=dx, dy=dy)
calc_darkness
の修正
上記の問題が発生する原因は、局面の表示の暗さ と、評価値の表示の有無 を 同じ darkness
という変数の値で 無理に表現しようとした点 にあります。calc_darkness
が 局面の表示の暗さ と、評価値の表示の有無 の 両方を計算して返す ようにすることで、うまく処理を行うことができるようになります。本記事では、0 番の要素 にこれまでに計算していた 局面の暗さを表す数値、1 番の要素 に 評価値を表示するか どうかを表す bool 型のデータを持つ tuple を返り値として返す ように calc_darkness
を修正することにします。下記はそのように calc_darkness
を修正したプログラムです。
-
4、6、8、10 行目:Mbtree_Anim 以外から
draw_subtree
が呼び出された場合は、評価値の表示の有無はshow_score
の値によって決まる ので、その値をそのまま tuple の 1 番の要素 として返すように修正する -
13 行目:評価値が計算されていないノードの場合は、tuple の 1 番の要素に
False
を、そうでない場合はTrue
を返すように修正する
1 def calc_darkness(node):
2 if show_bestmove:
3 if node.parent is None:
4 return 0, show_score
5 elif node.mb.last_move in node.parent.bestmoves:
6 return 0, show_score
7 else:
8 return 0.2, show_score
9
10 if anim_frame is None:
11 return 0, show_score
12 index = node.score_index if isscore else node.id
13 return (0.5, False) if index > anim_frame else (0, True)
修正箇所
def calc_darkness(node):
if show_bestmove:
if node.parent is None:
- return 0
+ return 0, show_score
elif node.mb.last_move in node.parent.bestmoves:
- return 0
+ return 0, show_score
else:
- return 0.2
+ return 0.2, show_score
if anim_frame is None:
- return 0
+ return 0, show_score
index = node.score_index if isscore else node.id
- return 0.5 if index > anim_frame else 0
+ return (0.5, False) if index > anim_frame else (0, True)
行う処理が変わったので、calc_darkness
の名前を calc_darkness_and_showmove
のように修正したほうが良いかもしれませんが、関数名が長くなるので本記事では名前を変更しないことにします。変更したい人は自由に変更して下さい。
13 行目を下記のプログラムのように、返り値を表す tuple の ()
を省略 して記述すると エラーが発生する 点に注意して下さい。
return 0.5, False if index > anim_frame else 0, show_score
その理由は、上記のプログラムが下記のプログラムのように、0.5
、False if index > anim_frame else 0
、show_score
という 3 つの要素を持つ tuple を返すからです。
return (0.5, False if index > anim_frame else 0, show_score)
そのことは、下記のプログラムで確認できます。
index = 1
anim_frame = 0
show_score = True
retval = 0.5, False if index > anim_frame else 0, show_score
print(retval)
実行結果
(0.5, False, True)
show_subtree
の修正
次に、show_subtree
を下記のプログラムのように修正します。
-
3、9、12、15 行目:
cald_darkness
の返り値をdarkness
とshow_score
に代入するように修正する -
7 行目 の
draw_node
は 以前の記事で記述したmaxdepth
の深さの局面から最善手を着手し続けた場合のノードの表示を行う 処理なので、Mbtree_Anim からdraw_subtree
を呼び出した場合は 実行されない。そのため、7 行目の直前でcalc_darkness
を呼び出してdarkness
とshow_score
の計算を 行う必要はない。また、このノードは常に明るく表示するため、7 行目のdraw_node
にはキーワード引数darkness
は記述されていない
1 def draw_subtree(self, centernode=None, selectednode=None, ax=None, anim_frame=None,
2 isscore=False, show_bestmove=False, show_score=True, size=0.25, lw=0.8, maxdepth=2):
元と同じなので省略
3 darkness, show_score = calc_darkness(node)
4 rect = node.draw_node(ax=ax, maxdepth=maxdepth, emphasize=emphasize, darkness=darkness,
5 show_score=show_score, size=size, lw=lw, dx=dx, dy=dy)
元と同じなので省略
6 emphasize = bestnode is selectednode
7 rect = bestnode.draw_node(ax=ax, maxdepth=bestnode.depth, emphasize=emphasize,
8 show_score=show_score, size=size, lw=lw, dx=dx, dy=dy)
元と同じなので省略
9 darkness, show_score = calc_darkness(sibling)
10 rect = sibling.draw_node(ax, maxdepth=sibling.depth, size=size, darkness=darkness,
11 show_score=show_score, lw=lw, dx=dx, dy=dy)
元と同じなので省略
12 darkness, show_score = calc_darkness(parent)
13 rect = parent.draw_node(ax, maxdepth=maxdepth, darkness=darkness,
14 show_score=show_score, size=size, lw=lw, dx=dx, dy=0)
元と同じなので省略
15 darkness, show_score = calc_darkness(node)
16 rect = node.draw_node(ax, maxdepth=node.depth, darkness=darkness,
17 show_score=show_score, size=size, lw=lw, dx=dx, dy=0)
18 self.nodes_by_rect[rect] = node
19
20 Mbtree.draw_subtree = draw_subtree
行番号のないプログラム
def draw_subtree(self, centernode=None, selectednode=None, ax=None, anim_frame=None,
isscore=False, show_bestmove=False, show_score=True, size=0.25, lw=0.8, maxdepth=2):
def calc_darkness(node):
if show_bestmove:
if node.parent is None:
return 0, show_score
elif node.mb.last_move in node.parent.bestmoves:
return 0, show_score
else:
return 0.2, show_score
if anim_frame is None:
return 0, show_score
index = node.score_index if isscore else node.id
return (0.5, False) if index > anim_frame else (0, True)
self.nodes_by_rect = {}
if centernode is None:
centernode = self.root
self.calc_node_height(N=centernode, maxdepth=maxdepth)
if show_bestmove:
width = 5 * 10
else:
width = 5 * (maxdepth + 1)
height = centernode.height
parent = centernode.parent
if parent is not None:
height += (len(parent.children) - 1) * 4
parent.height = height
if ax is None:
fig, ax = plt.subplots(figsize=(width * size, (height + 1) * size))
ax.set_xlim(0, width)
ax.set_ylim(-1, height)
ax.invert_yaxis()
ax.axis("off")
if show_bestmove:
bestx = 5 * maxdepth + 4
bestwidth = 50 - bestx
ax.add_artist(patches.Rectangle(xy=(bestx, -1), width=bestwidth,
height=height + 1, fc="lightgray"))
nodelist = [centernode]
depth = centernode.depth
while len(nodelist) > 0 and depth <= maxdepth:
dy = 0
if parent is not None:
dy = parent.children.index(centernode) * 4
childnodelist = []
for node in nodelist:
if node is None:
dy += 4
childnodelist.append(None)
else:
dx = 5 * node.depth
emphasize = node is selectednode
darkness, show_score = calc_darkness(node)
rect = node.draw_node(ax=ax, maxdepth=maxdepth, emphasize=emphasize, darkness=darkness,
show_score=show_score, size=size, lw=lw, dx=dx, dy=dy)
self.nodes_by_rect[rect] = node
if show_bestmove and depth == maxdepth:
bestnode = node
while len(bestnode.bestmoves) > 0:
bestmove = bestnode.bestmoves[0]
bestnode = bestnode.children_by_move[bestmove]
dx = 5 * bestnode.depth
bestnode.height = 4
emphasize = bestnode is selectednode
rect = bestnode.draw_node(ax=ax, maxdepth=bestnode.depth, emphasize=emphasize,
show_score=show_score, size=size, lw=lw, dx=dx, dy=dy)
self.nodes_by_rect[rect] = bestnode
dy += node.height
if len(node.children) > 0:
childnodelist += node.children
else:
childnodelist.append(None)
depth += 1
nodelist = childnodelist
if parent is not None:
dy = 0
for sibling in parent.children:
if sibling is not centernode:
sibling.height = 4
dx = 5 * sibling.depth
darkness, show_score = calc_darkness(sibling)
rect = sibling.draw_node(ax, maxdepth=sibling.depth, size=size, darkness=darkness,
show_score=show_score, lw=lw, dx=dx, dy=dy)
self.nodes_by_rect[rect] = sibling
dy += sibling.height
dx = 5 * parent.depth
darkness, show_score = calc_darkness(parent)
rect = parent.draw_node(ax, maxdepth=maxdepth, darkness=darkness,
show_score=show_score, size=size, lw=lw, dx=dx, dy=0)
self.nodes_by_rect[rect] = parent
node = parent
while node.parent is not None:
node = node.parent
node.height = height
dx = 5 * node.depth
darkness, show_score = calc_darkness(node)
rect = node.draw_node(ax, maxdepth=node.depth, darkness=darkness,
show_score=show_score, size=size, lw=lw, dx=dx, dy=0)
self.nodes_by_rect[rect] = node
Mbtree.draw_subtree = draw_subtree
修正箇所
def draw_subtree(self, centernode=None, selectednode=None, ax=None, anim_frame=None,
isscore=False, show_bestmove=False, show_score=True, size=0.25, lw=0.8, maxdepth=2):
元と同じなので省略
- darkness = calc_darkness(node)
+ darkness, show_score = calc_darkness(node)
rect = node.draw_node(ax=ax, maxdepth=maxdepth, emphasize=emphasize, darkness=darkness,
show_score=show_score, size=size, lw=lw, dx=dx, dy=dy)
元と同じなので省略
emphasize = bestnode is selectednode
rect = bestnode.draw_node(ax=ax, maxdepth=bestnode.depth, emphasize=emphasize,
show_score=show_score, size=size, lw=lw, dx=dx, dy=dy)
元と同じなので省略
- darkness = calc_darkness(sibling)
+ darkness, show_score = calc_darkness(sibling)
rect = sibling.draw_node(ax, maxdepth=sibling.depth, size=size, darkness=darkness,
show_score=show_score, lw=lw, dx=dx, dy=dy)
元と同じなので省略
- darkness = calc_darkness(parent)
+ darkness, show_score = calc_darkness(parent)
rect = parent.draw_node(ax, maxdepth=maxdepth, darkness=darkness,
show_score=show_score, size=size, lw=lw, dx=dx, dy=0)
元と同じなので省略
- darkness = calc_darkness(node)
+ darkness, show_score = calc_darkness(node)
rect = node.draw_node(ax, maxdepth=node.depth, darkness=darkness,
show_score=show_score, size=size, lw=lw, dx=dx, dy=0)
self.nodes_by_rect[rect] = node
Mbtree.draw_subtree = draw_subtree
上記の修正後に下記のプログラムを実行すると、実行結果のように、評価値が計算されていない局面の上に評価値が表示されなくなったことが確認できます。
Mbtree_Anim(mbtree, isscore=True)
実行結果
また、アニメーションのフレームを進めると、下図のように評価値が計算されている局面の上に評価値が表示されることが確認できます。
ゲーム木の作成の経過の表示の確認
上記で Mbtree_Anim の実引数に isscore=True
を記述して、評価値の計算過程を表示 するアニメーションが正しく表示されることが確認できました。
プログラムを修正した結果、Mbtree_Anim の実引数に isscore=True
を記述しない場合に 表示される ゲーム木の作成の過程の表示 に バグが発生している可能性がある ので、下記のプログラムを実行して 確認する ことにします。
Mbtree_Anim(mbtree)
実行結果
実行結果から、最初に作成されたルートノードが明るく表示 され、それ以外の まだ作成されていないノードが暗く表示される ことが確認できます。その際に、作成されているノードの上に評価値が表示されていますが、この 評価値は表示しないほうが良い でしょう。その理由について少し考えてみて下さい。
評価値を計算しないほうが良い理由
評価値が計算される順番 は、ゲーム木の ノードが作成される順番と大きく異なります。例えば ルートノードの評価値が計算される のは、他の全てのノードの評価値が計算された後 なので、最初に ルートノードが作成された時点で ルートノードの評価値が表示される のは順番が 間違っています。上記のアニメーションでは ノードが作成された順番で評価値が表示される ため、評価値の計算の順番に対する誤解が生じる可能性が高い でしょう。
そこで、誤解が生じないようにするために、Mbtree_Anim で ゲーム木の生成の過程 のアニメーションを表示する場合は、評価値を表示しない ように修正することにします。どのように修正すれば良いかについて少し考えてみて下さい。
Mbtree クラスの draw_subtree
の修正
行いたい修正は、calc_darkness
で局面を 明るく表示すると判定 した際に、isscore
が True
の場合は評価値を表示 し、そうでない場合は評価値を表示しない ような 返り値を計算する というものです。
calc_darkness
で 局面を明るく表示すると判定 した場合に 返す値 は、下記のプログラムの else の直後の (0, True)
という tuple です。
return (0.5, False) if index > anim_frame else (0, True)
この部分を isscore
が True
の場合に (0, True)
、False
の場合に (0, False)
を返すようにするためには、下記のプログラムのように修正します。
return (0.5, False) if index > anim_frame else (0, isscore)
下記は、そのように draw_subtree
を修正したプログラムです。
- 5 行目:上記のように修正する
1 def draw_subtree(self, centernode=None, selectednode=None, ax=None, anim_frame=None,
2 isscore=False, show_bestmove=False, show_score=True, size=0.25, lw=0.8, maxdepth=2):
3 def calc_darkness(node):
元と同じなので省略
4 index = node.score_index if isscore else node.id
5 return (0.5, False) if index > anim_frame else (0, isscore)
元と同じなので省略
6
7 Mbtree.draw_subtree = draw_subtree
行番号のないプログラム
def draw_subtree(self, centernode=None, selectednode=None, ax=None, anim_frame=None,
isscore=False, show_bestmove=False, show_score=True, size=0.25, lw=0.8, maxdepth=2):
def calc_darkness(node):
if show_bestmove:
if node.parent is None:
return 0, show_score
elif node.mb.last_move in node.parent.bestmoves:
return 0, show_score
else:
return 0.2, show_score
if anim_frame is None:
return 0, show_score
index = node.score_index if isscore else node.id
return (0.5, False) if index > anim_frame else (0, isscore)
self.nodes_by_rect = {}
if centernode is None:
centernode = self.root
self.calc_node_height(N=centernode, maxdepth=maxdepth)
if show_bestmove:
width = 5 * 10
else:
width = 5 * (maxdepth + 1)
height = centernode.height
parent = centernode.parent
if parent is not None:
height += (len(parent.children) - 1) * 4
parent.height = height
if ax is None:
fig, ax = plt.subplots(figsize=(width * size, (height + 1) * size))
ax.set_xlim(0, width)
ax.set_ylim(-1, height)
ax.invert_yaxis()
ax.axis("off")
if show_bestmove:
bestx = 5 * maxdepth + 4
bestwidth = 50 - bestx
ax.add_artist(patches.Rectangle(xy=(bestx, -1), width=bestwidth,
height=height + 1, fc="lightgray"))
nodelist = [centernode]
depth = centernode.depth
while len(nodelist) > 0 and depth <= maxdepth:
dy = 0
if parent is not None:
dy = parent.children.index(centernode) * 4
childnodelist = []
for node in nodelist:
if node is None:
dy += 4
childnodelist.append(None)
else:
dx = 5 * node.depth
emphasize = node is selectednode
darkness, show_score = calc_darkness(node)
rect = node.draw_node(ax=ax, maxdepth=maxdepth, emphasize=emphasize, darkness=darkness,
show_score=show_score, size=size, lw=lw, dx=dx, dy=dy)
self.nodes_by_rect[rect] = node
if show_bestmove and depth == maxdepth:
bestnode = node
while len(bestnode.bestmoves) > 0:
bestmove = bestnode.bestmoves[0]
bestnode = bestnode.children_by_move[bestmove]
dx = 5 * bestnode.depth
bestnode.height = 4
emphasize = bestnode is selectednode
rect = bestnode.draw_node(ax=ax, maxdepth=bestnode.depth, emphasize=emphasize,
show_score=show_score, size=size, lw=lw, dx=dx, dy=dy)
self.nodes_by_rect[rect] = bestnode
dy += node.height
if len(node.children) > 0:
childnodelist += node.children
else:
childnodelist.append(None)
depth += 1
nodelist = childnodelist
if parent is not None:
dy = 0
for sibling in parent.children:
if sibling is not centernode:
sibling.height = 4
dx = 5 * sibling.depth
darkness, show_score = calc_darkness(sibling)
rect = sibling.draw_node(ax, maxdepth=sibling.depth, size=size, darkness=darkness,
show_score=show_score, lw=lw, dx=dx, dy=dy)
self.nodes_by_rect[rect] = sibling
dy += sibling.height
dx = 5 * parent.depth
darkness, show_score = calc_darkness(parent)
rect = parent.draw_node(ax, maxdepth=maxdepth, darkness=darkness,
show_score=show_score, size=size, lw=lw, dx=dx, dy=0)
self.nodes_by_rect[rect] = parent
node = parent
while node.parent is not None:
node = node.parent
node.height = height
dx = 5 * node.depth
darkness, show_score = calc_darkness(node)
rect = node.draw_node(ax, maxdepth=node.depth, darkness=darkness,
show_score=show_score, size=size, lw=lw, dx=dx, dy=0)
self.nodes_by_rect[rect] = node
Mbtree.draw_subtree = draw_subtree
修正箇所
def draw_subtree(self, centernode=None, selectednode=None, ax=None, anim_frame=None,
isscore=False, show_bestmove=False, show_score=True, size=0.25, lw=0.8, maxdepth=2):
def calc_darkness(node):
元と同じなので省略
index = node.score_index if isscore else node.id
- return (0.5, False) if index > anim_frame else (0, True)
+ return (0.5, False) if index > anim_frame else (0, isscore)
元と同じなので省略
Mbtree.draw_subtree = draw_subtree
上記の修正後に下記のプログラムを実行すると、実行結果のように ゲーム木の作成過程を表示 した場合に 明るいノードの上に評価値が表示されなくなった ことが確認できます。
Mbtree_Anim(mbtree)
実行結果
また、実行結果は先ほどと同じなので省略しますが、下記のプログラムを実行して 評価値の計算過程を表示 した場合は、アニメーションのフレームを進めると 明るいノードの上に評価値が表示される ことが確認できます。
Mbtree_Anim(mbtree, isscore=True)
今回の記事のまとめ
今回の記事では、Mbtree_Anim クラスのバグの修正と改良を行いました。
本記事で入力したプログラム
リンク | 説明 |
---|---|
marubatsu.ipynb | 本記事で入力して実行した JupyterLab のファイル |
tree.py | 本記事で更新した tree_new.py |
次回の記事