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を一から作成する その142 数直線によるαβ法のα値などの視覚化

Last updated at Posted at 2024-12-26

目次と前回の記事

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

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

リンク 説明
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 で表示する際に、下図のように α 値、β 値、計算した子ノードの評価値、行った処理のメッセージを表示することでわかりやすくするという工夫を行いました。

しかし、α 値や β 値などの数字を上図のように 文字で表記 するのはあまり 直観的ではありません。そこで、今回の記事では α 値や β 値などを 数直線上に表示 することで、直観的にわかりやすく表示 するという工夫を行なうことにします。

数直線の描画

データの種類が 1 種類である、1 次元の数値図で表現する方法 として、直線に等間隔に数値を割り当てるという、数直線 が良く使われます。数直線上に複数の数値を記号で表記 することで、その 大小関係など直観的にわかりやすく表現 することができます。

下図は数直線の例で、数直線上に -1 と 2 という数値を 色のついた円 で表記しています。

matplotlib では 2 次元のグラフの軸を表示する機能を持ちますが、筆者が調べた範囲では 1 次元の数直線の軸を表示する機能は持っていないようです1。そのため、本記事では数直線を一からプログラムで描画することにします。

また、前回の記事では α 値や β 値などを Mbtree_Anim の部分木の上部に描画しましたが、数直線をそのように描画すると、数直線を描画する際に必要となる 座標の計算が面倒になる ので、別の Figure を作成 して そちらに描画 することにします。

数直線の表示範囲の設定

数直線は負の無限大から正の無限大までの数値を表す直線ですが、実際に 無限の長さの直線を描画 することは 不可能 です。そこで、数直線を描画する際には、どの範囲の数値を描画するかを決める 必要があります。

α 値β 値数直線上に描画 するためには、α 値と β 値の取りうる範囲 を表す数直線を描画する必要があります。しかし、α 値と β 値の初期値負の無限大と正の無限大 なので、そのような範囲の数直線を描画する際には 工夫が必要 になります。本記事では、描画する 数直線の両端負の無限大正の無限大みなす という工夫を行なうことにします。

次に、負の無限大と正の無限大 以外α 値と β 値の取りうる範囲 を考えます。α 値と β 値は、決着がついた局面の評価値から計算されるので、その範囲は 決着がついた局面の評価値の範囲 になります。本記事では決着がついた局面の評価値として、-1 ~ 1 までの範囲で計算する方法と、以前の記事で説明した、最も早く勝利できる合法手を計算する下記の表のような -2 ~ 3 までの範囲で計算する方法を紹介しました。

局面の深さ 5 6 7 8 9 評価値の計算式
〇 が勝利した場合の評価値 3 2 1 (11 - 局面の深さ)÷ 2
× が勝利した場合の評価値 -2 -1 (局面の深さ - 10)÷ 2

そこで、本記事では上記の 両方の評価値の範囲に対応できる ように、下記と下図のような数直線を描画し、その上に α 値や β 値などを描画することにします。

  • 数直線の描画範囲は -3 ~ 4 までの範囲とする。ただし、-3 の左端を負の無限大、4 の右端を正の無限大とみなすことにする
  • 数直線の整数の部分に目盛りを描画する
  • 目盛りの下に整数の値を描画する。ただし、-3 には -∞ を、4 には ∞ を描画する

一般的には数直線は正の方向の端に矢印を表記しますが、本記事で数直線を描画する方法で右端に矢印を描画するのは面倒なので本記事では表記しないことにします。

また、下に描画された数値を見れば、どちらの方向が正の方向であるかは明白なので、矢印を描画しなくても大きな問題にはならないと思います。

数直線と目盛りの描画

数直線と目盛りの描画は Axes の plot などの線を描画するメソッドなどを利用して、別々に描画するという方法がありますが、その場合は 一つ一つの目盛りの両端の座標を指定 し目盛りの線を描画する必要がある点が 面倒です。本記事では別の方法として plot メソッドの マーカーを描画する機能 を利用することで、数直線と目盛りの 両方を同時にまとめて描画 するという方法を紹介します。

plot メソッドによるマーカーの描画

折れ線を描画する Axes の plot メソッドは、それぞれの折れ線の 頂点に 円や四角形などの マーカー(marker) と呼ばれる 図形を描画 することができます。マーカーは、plot メソッドを呼び出す際に、キーワード引数 marker にマーカーの 種類を表す文字列を記述 することで描画します。例えば 下記のプログラムのように、marker="o" を記述すると 円のマーカー が、marker="^" を記述すると 三角形のマーカー が描画されます。

import matplotlib.pyplot as plt
import japanize_matplotlib

fig, ax = plt.subplots()
ax.plot([0, 1, 2], [0, 2, 1], marker="o")
ax.plot([0, 1, 2], [1, 0, 2], marker="^")

実行結果

マーカーの種類を表す文字列の一覧については、下記のリンク先を参照して下さい2

マーカーの大きさ は下記のプログラムのように、キーワード引数 markersize または ms を記述することで設定することができます。

fig, ax = plt.subplots()
ax.plot([0, 1, 2], [0, 2, 1], marker="o", markersize="20")
ax.plot([0, 1, 2], [1, 0, 2], marker="^", ms="10")

実行結果

マーカー、線の種類、線の色の一括設定

plot メソッドでは マーカー線の種類線の色 を下記の表のキーワード引数で設定することができます。

キーワード引数 意味
markerstyle または ms マーカーの種類
linestyle または ls 線の種類
color または c 線の色

また、それぞれの設定は 1 または 2 文字の文字列 で記述できるようになっています。設定できる文字列の種類については、下記のリンク先(下の方にあります)を参照して下さい。

上記の 3 種類の設定は良く行われるので、それらの 3 つの設定1 つの実引数で簡潔に記述 できるようになっています。具体的には plot メソッドの 3 つ目の実引数 にそれぞれの 設定を表す文字列を並べた 文字列を記述することで設定します。詳細は、上記のリンク先の format strings の所を参照して下さい。

例えば、下記のプログラムのように "o--r" と記述した場合は、実行結果のように 円のマーカー(o)破線(--)赤色(r) で折れ線が、"s:g" と記述した場合は 正方形のマーカー(s)点線(:)緑色(g) で折れ線が描画されます。

fig, ax = plt.subplots()
ax.plot([0, 1, 2], [0, 2, 1], "o--r")
ax.plot([0, 1, 2], [1, 0, 2], "s:g")

実行結果

3 つの設定は、マーカーの種類、線種、色の 順番で記述 することが 推奨されています が、それぞれの設定で使われる 文字列は重複しない ようになっているので、順番を変えても構わない ようです。例えば "o-r""ro-" は同じ意味を持ちます。

また 3 つの設定を 全て記述する必要はありません。例えば下記のプログラムの 2 行目のように "r" で色の設定だけを記述 した場合は マーカーなしの赤色の実線 が描画されます。設定を記述しなかった場合は下記のようなルールで描画されます。

  • 色の設定を省略した場合は、色が自動的に設定される
  • マーカーの設定を省略した場合は、マーカーは描画されない
  • 線種の設定を省略した場合は、以下のように描画される
    • マーカーの設定を記述していない場合は実線が描画される
    • マーカーの設定を記述した場合は線は描画されない

下記のプログラムは色の設定だけと、マーカーの設定だけを記述した例です。3 行目では線種が省略され、マーカーが設定されているので実行結果のようにマーカーのみが描画されて線は描画されません。

fig, ax = plt.subplots()
ax.plot([0, 1, 2], [0, 2, 1], "r")
ax.plot([0, 1, 2], [1, 0, 2], "o")

実行結果のように

実例はこのすぐ後で紹介するので、ここではプログラムは省略しますが、マーカーと実線を描画 する場合は o-r のように、実線を表す "-" を記述する必要があります。

数直線と目盛りの描画

目盛りplot でマーカーを表す実引数に "|" を記述することによって描画される 縦棒のマーカー で、下記のプログラムのように うまく描画 することができます。なお、1、3、4 行目の Figure の大きさAxes の表示範囲の設定 は、今後の作業で Figure に数直線以外の様々な描画を行う処理を加えた際にうまく表示されるように、筆者が試行錯誤して設定 した値です。実際には、数直線を描画するプログラムを作成する際に、最初からこれらの値をうまく決めることはできません。これらの値を変更することで、表示される Figure の大きさ や、中の 表示のバランスが変わる ので、それらを変えたい人は自由に調整して下さい。

  • 1 行目:Figure を作成する
  • 2 行目:Figure と同じ大きさの Axes を add_axes を利用して作成する3
  • 3 ~ 5 行目:Axes の表示範囲を設定し、軸の表示を行わないようにする
  • 7 行目:折れ線の頂点の x 座標の一覧 として range を利用して -3 ~ 4 までの整数 を表すデータを4y 座標の一覧 として * 演算子 を利用して 8 つの 0 を要素として持つ list を記述することで、(-3, 0)、(-2, 0)、・・・、(4, 0) の座標を結ぶ 数直線が描画 される。また、3 つ目の実引数"|-k" を記述することで、線が 黒色(k)実線(-) で描画され、頂点に目盛りを表す 縦棒のマーカー(|) が描画される。縦棒の長さを変えたい場合は、キーワード引数 markersize を記述して自由に調整すること
1  fig = plt.figure(figsize=(5, 1))
2  ax = fig.add_axes([0, 0, 1, 1])
3  ax.set_xlim(-4, 14)
4  ax.set_ylim(-1.5, 1.5)
5  ax.axis("off")
6
7  ax.plot(range(-3, 5), [0] * 8, "|-k")
行番号のないプログラム
fig = plt.figure(figsize=(5, 1))
ax = fig.add_axes([0, 0, 1, 1])
ax.clear()
ax.axis("off")
ax.set_xlim(-4, 14)
ax.set_ylim(-1.5, 1.5)

ax.plot(range(-3, 5), [0] * 8, "|-k")

実行結果

目盛りの下の数値の描画

matplotlib には軸の付近に軸の数値を描画するメソッドがありますが、そのメソッドでは本記事の数直線のように、両端の数字を ∞ のような 軸の実際の値と異なる値を表示 することは できません。そこで、本記事では下記のプログラムのように、Axes の text メソッドで それぞれの数字座標を指定して描画 することにします。

  • 1、2 行目:数直線上で 負の無限大正の無限大表す数直 をそれぞれ変数に代入する
  • 3 行目:数直線と目盛りを描画する処理を、上記の変数を利用したプログラムに修正する。この 修正の目的数直線の範囲を簡単に変更できるようにする ことである
  • 4 行目:負の無限大を表す数値から、正の無限大を表す数値までの整数に対する繰り返し処理を行う
  • 5 ~ 10 行目:数直線の下に描画する文字列を計算する
  • 11 行目:上記で計算した文字列を、Axes の text メソッドを利用して目盛りの下に描画する。文字の x 座標は対応する目盛りの座標を指定し、実引数に ha="center" を記述することでその位置に中央揃えで描画する。y 座標の -1 は筆者が試行錯誤して設定した値なので、変更したい場合は自由に変更すること
元と同じなので省略
 1  minus_inf = -3
 2  plus_inf = 4
 3  ax.plot(range(minus_inf, plus_inf + 1), [0] * (plus_inf + 1 - minus_inf) , "|-k")
 4  for num in range(minus_inf, plus_inf + 1):
 5      if num == minus_inf:
 6          numtext = "-∞"
 7      elif num == plus_inf:
 8          numtext = ""
 9      else:
10          numtext = num
11      ax.text(num, -1, numtext, ha="center")    
行番号のないプログラム
fig = plt.figure(figsize=(5, 1))
ax = fig.add_axes([0, 0, 1, 1])
ax.axis("off")
ax.set_xlim(-4, 14)
ax.set_ylim(-1.5, 1.5)

minus_inf = -3
plus_inf = 4
ax.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 = "-∞"
    elif num == plus_inf:
        numtext = ""
    else:
        numtext = num
    ax.text(num, -1, numtext, ha="center")
修正箇所
元と同じなので省略
-ax.plot(range(-3, 5), [0] * 8, "|-k")
+minus_inf = -3
+plus_inf = 4
+ax.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 = "-∞"
+   elif num == plus_inf:
+       numtext = ""
+   else:
+       numtext = num
+   ax.text(num, -1, numtext, ha="center")   

実行結果

上記のプログラムを実行すると、実行結果のように数直線の下に目盛りの数値が意図したとおりに描画されるようになることが確認できます。

α 値、β 値、子ノードの評価値の描画

次に、数直線上に α 値、β 値、子ノードの評価値を描画 します。その際に、どのように描画するかを決める必要があるのでその方法について少し考えてみて下さい。

それぞれの値を表すマーカーの描画

本記事では、それぞれを 色のついた円で描画 することにします。色のついた 1 つの円 は、plot メソッドの マーカー を使って描画することができます。これまでは plot メソッドの実引数に複数の 頂点の座標の一覧 を表すデータを記述しましたが、下記のプログラムのように 1 つ目と 2 つ目の実引数に 1 つの座標 を表す x 座標と y 座標の データを記述 し、3 つ目の実引数で マーカーの指定を記述 することでその点にマーカーを描画することができます。

fig, ax = plt.subplots()
ax.plot(1, 1, "or") # 赤い円のマーカー
ax.plot(3, 1, "^g") # 緑の三角形のマーカー
ax.plot(2, 2, "sk") # 黒い四角形のマーカー

実行結果

下記は、α 値β 値子ノードの評価値 をそれぞれ alphabetascore という変数に代入し、それらを数直線上に 円のマーカーで描画 するプログラムです。本記事では、それぞれの色を赤、青、緑色で描画していますが、色を変えたい人は自由に変更して下さい。

  • 1 ~ 3 行目:α 値、β 値、子ノードの評価値を表す変数に、それぞれ何らかの値を代入する。本記事では -2、正の無限大、0 を代入した
  • 4 行目α 値 を数直線上に 描画する座標(coordinate)を計算 して alphacoord に代入する。alpha とは別の変数 に計算した 値を代入する理由 は、この後で α 値の文字を描画する予定だから である。計算は組み込み関数 max を利用して、alpha に数直線上での負の無限大を表す minus_inf より小さい値が代入されていた場合は、minus_inf の値にする。なお、αβ 法の性質 から α 値が 評価値の最大値である 3 より大きくなることはない ので、alphacoord = min(plus_inf, alphacoord) のような計算を行う必要はない
  • 5 行目:上記と同様の処理を beta に対しても行う
  • 6 ~ 8 行目:数直線上に、α 値、β 値、子ノードの評価値の値を表す円のマーカーを、それぞれ赤、青、緑色で描画する
1  alpha = -2
2  beta = float("inf")
3  score = 0
元と同じなので省略   
4  alphacoord = max(minus_inf, alpha)
5  betacoord = min(plus_inf, beta)
6  ax.plot(alphacoord, 0, "or")
7  ax.plot(betacoord, 0, "ob")
8  ax.plot(score, 0, "og")
行番号のないプログラム
alpha = -2
beta = float("inf")
score = 0

fig = plt.figure(figsize=(5, 1))
ax = fig.add_axes([0, 0, 1, 1])
ax.axis("off")
ax.set_xlim(-4, 14)
ax.set_ylim(-1.5, 1.5)

minus_inf = -3
plus_inf = 4
ax.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 = "-∞"
    elif num == plus_inf:
        numtext = ""
    else:
        numtext = num
    ax.text(num, -1, numtext, ha="center")
    
alphacoord = max(minus_inf, alpha)
betacoord = min(plus_inf, beta)
ax.plot(alphacoord, 0, "or")
ax.plot(betacoord, 0, "ob")
ax.plot(score, 0, "og")
修正箇所
alpha = -2
score = 0
beta = float("inf")
元と同じなので省略   
+alphacoord = max(minus_inf, alpha)
+betacoord = min(plus_inf, beta)
+ax.plot(alphacoord, 0, "or")
+ax.plot(scorecoord, 0, "og")
+ax.plot(beta, 0, "ob")

実行結果

上記のプログラムを実行すると、実行結果のように数直線上の α 値、β 値、子ノードの評価値を表す位置に マーカーが描画 されます。その際に、正の無限大 が設定された β 値正しい位置に描画される ことが確認できます。興味がある方は、1 ~ 3 行目を別の値を代入するように変更しても5正しい位置に描画されることを確認して下さい。

凡例の描画

数直線上に描画した 3 つのマーカーは色で区別することはできますが、それぞれが どの値に対応するかわかりづらい という問題があります。そこで、グラフなどでは一般的に下図の右にある 凡例 を描画するという工夫が良く行われます。

matplotlib では、凡例を下記の方法で簡単に描画することができます。

  • plot メソッドに、凡例に描画する文字列を表す キーワード引数 label を記述する
  • Axes の legend メソッドを呼び出すと、上記のキーワード引数 label で指定したデータが記述された 凡例が描画 される

凡例に描画する文字列を表すキーワード引数 label は、plot 以外の様々なグラフを描画するメソッドでも共通して利用できます。

下記は先ほどの α 値などのマーカーを描画した数直線の図に、凡例を描画するように修正したプログラムです。

  • 1 ~ 3 行目:それぞれの plot メソッドにキーワード引数 label を記述する
  • 4 行目:Axes の legend メソッドを呼び出して凡例を描画する
元と同じなので省略
1  ax.plot(alpha, 0, "or", label="alpha")
2  ax.plot(beta, 0, "ob", label="beta")
3  ax.plot(score, 0, "og", label="score")
4  ax.legend()
行番号のないプログラム
alpha = -2
beta = float("inf")
score = 0

fig = plt.figure(figsize=(5, 1))
ax = fig.add_axes([0, 0, 1, 1])
ax.axis("off")
ax.set_xlim(-4, 14)
ax.set_ylim(-1.5, 1.5)

minus_inf = -3
plus_inf = 4
ax.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 = "-∞"
    elif num == plus_inf:
        numtext = ""
    else:
        numtext = num
    ax.text(num, -1, numtext, ha="center")
    
alphacoord = max(minus_inf, alpha)
betacoord = min(plus_inf, beta)
ax.plot(alphacoord, 0, "or", label="alpha")
ax.plot(betacoord, 0, "ob", label="beta")
ax.plot(score, 0, "og", label="score")
plt.legend()
修正箇所
元と同じなので省略
-ax.plot(alphacoord, 0, "or")
+ax.plot(alphacoord, 0, "or", label="alpha")
-ax.plot(betacoord, 0, "ob")
+ax.plot(betacoord, 0, "ob", label="beta")
-ax.plot(score, 0, "og")
+ax.plot(score, 0, "og", label="score")
+plt.legend()

実行結果

上記のプログラムを実行すると、実行結果のように凡例が右に描画されます。

legend メソッドによって描画される 凡例の位置自動的 に空いている場所が探されて 配置が行われます が、自分で凡例の描画位置を設定 することもできます。詳しくは、下記の Axes の legend メソッドのドキュメントを参照して下さい。

注釈の描画

上記の凡例は、折れ線グラフや棒グラフのような場合はわかりやすいのですが、上記のような 点に対する凡例 の場合は 思ったほどわかりやすくありません

そのような場合は、Axesannotate というメソッドを利用することで、矢印を使って 特定の座標に対する 注釈(annotation)を描画する という方法があります。

Axes の annotate メソッドは、主に以下の表のような仮引数を持ちます。

仮引数 意味
text 注釈として描画する文字列
xy 注釈の対象となる座標を表す tuple
xytext 注釈の文字列を描画する座標を表す tuple
arrowprops 注釈と注釈の対象となる座標を結ぶ線の矢印の設定を表すデータ。
{ "arrowstyle": "->"} という dict を記述することで矢印が描画される。
なお、この仮引数に対応する実引数を省略すると矢印の線は描画されない
ha text メソッドと同様の方法で、文字列の描画の揃えを指定できる

下記は annotate メソッドを利用して、数直線上の α 値β 値子ノードの評価値注釈を描画 するプログラムです。なお、本記事では注釈の中に具体的な値を描画するようにしましたが、具体的な値が必要ないと思った方は描画しないように修正して下さい。

  • 1 行目annotate メソッドの仮引数 arrowprops に代入する値は、3 つの annotate メソッドを呼び出す処理で共通なので、変数に代入しておく
  • 3、4 行目annotate メソッドを呼びだして、「α = α 値」という注釈を描画する処理を記述する。注釈の 描画位置 は、負の無限大 の座標の 真上 に、中央揃えで描画 するように設定した。なお、先程数直線上の α 値を描画する座標を alphacoord という、alpha とは別の変数に代入するようにしたのは、α 値が負の無限大 だった場合に「α = -3」ではなく 「α = -inf」という注釈を描画 できるようにするためである
  • 6、7、9、10 行目:β 値と子ノードの評価値に対しても同様の処理を行う。なお、β 値の注釈は正の無限大の座標の真上に、子ノードの評価値の注釈は数直線の中央の座標である (minus_inf + plus_inf) / 2 の真上に中央揃えで描画するように設定した
元と同じなので省略
 1  arrowprops = { "arrowstyle": "->"}
 2  ax.plot(alphacoord, 0, "or")
 3  ax.annotate(f"α = {alpha}", xy=(alphacoord, 0), xytext=(minus_inf, 1),
 4                arrowprops=arrowprops, ha="center")
 5  ax.plot(betacoord, 0, "ob")
 6  ax.annotate(f"β = {beta}", xy=(betacoord, 0), xytext=(plus_inf, 1),
 7                arrowprops=arrowprops, ha="center")
 8  ax.plot(score, 0, "og")
 9  ax.annotate(f"score = {score}", xy=(score, 0), xytext=((minus_inf + plus_inf) / 2, 1),
10                arrowprops=arrowprops, ha="center")
行番号のないプログラム
alpha = -2
beta = float("inf")
score = 0

fig = plt.figure(figsize=(5, 1))
ax = fig.add_axes([0, 0, 1, 1])
ax.axis("off")
ax.set_xlim(-4, 14)
ax.set_ylim(-1.5, 1.5)

minus_inf = -3
plus_inf = 4
ax.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 = "-∞"
    elif num == plus_inf:
        numtext = ""
    else:
        numtext = num
    ax.text(num, -1, numtext, ha="center")
    
alphacoord = max(minus_inf, alpha)
betacoord = min(plus_inf, beta)
arrowprops = { "arrowstyle": "->"}
ax.plot(alphacoord, 0, "or")
ax.annotate(f"α = {alpha}", xy=(alphacoord, 0), xytext=(minus_inf, 1),
              arrowprops=arrowprops, ha="center")
ax.plot(betacoord, 0, "ob")
ax.annotate(f"β = {beta}", xy=(betacoord, 0), xytext=(plus_inf, 1),
              arrowprops=arrowprops, ha="center")
ax.plot(score, 0, "og")
ax.annotate(f"score = {score}", xy=(score, 0), xytext=((minus_inf + plus_inf) / 2, 1),
              arrowprops=arrowprops, ha="center")
修正箇所
元と同じなので省略
+arrowprops = { "arrowstyle": "->"}
ax.plot(alphacoord, 0, "or")
+ax.annotate(f"α = {alpha}", xy=(alphacoord, 0), xytext=(minus_inf, 1),
+              arrowprops=arrowprops, ha="center")
ax.plot(betacoord, 0, "ob")
+ax.annotate(f"β = {beta}", xy=(betacoord, 0), xytext=(plus_inf, 1),
+              arrowprops=arrowprops, ha="center")
ax.plot(score, 0, "og")
+ax.annotate(f"score = {score}", xy=(score, 0), xytext=((minus_inf + plus_inf) / 2, 1),
+              arrowprops=arrowprops, ha="center")

annotate メソッドに関する詳細については、下記のリンク先を参照して下さい。

実行結果

上記のプログラムを実行すると、実行結果のように注釈が描画されるようになります。

Mbtree_Anim の修正

次に、上記の 数直線の図 が Mbtree_Anim で描画される ゲーム木の Figure の上に描画 されるように Mbtree_Anim を修正することにします。

Mbtree クラスの calc_score_by_ab の修正

Mbtree_Anim の処理で、それぞれの アニメーションのフレーム で α 値、β 値、子ノードの評価値を数直線上に描画 するためには、それらの値を ab_list_by_score 属性の 要素に記録 しておく必要があります。しかし、現状では ab_list_by_score の要素には (α 値, β 値, メッセージ) という tuple が代入されており、子ノードの評価値 を表すデータは 記録されていません。そこで、ab_list_by_score の要素に (α 値, β 値, 子ノードの評価値, メッセージ) という tuple が代入されるように、Mbtree クラスの calc_score_by_ab を修正 することにします。なお、この tuple の 子ノードの評価値の要素 には、子ノードの評価値が 計算された直後のみ その 評価値を代入 し、それ以外の場合は None を代入する ことにします。

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

  • 12、25 行目子ノードの評価値が計算された直後 では、ablist_by_score に代入する tuple の要素に 子ノードの評価値を代入 するように修正する
  • 9、16、19、29、32、36 行目それ以外 の場合は、tuple の子ノードの評価値を表す要素に None を代入 するように修正する
 1  from tree import Mbtree
 2  from marubatsu import Marubatsu
 3
 4  def calc_score_by_ab(self, abroot, debug=False):           
 5      self.count = 0
 6     
 7      def calc_ab_score(node, alpha=float("-inf"), beta=float("inf")):
 8          self.nodelist_by_score.append(node)
 9          self.ablist_by_score.append((alpha, beta, None, "このノードの処理の開始"))
元と同じなので省略
10                      score = calc_ab_score(childnode, alpha, beta)
11                      self.nodelist_by_score.append(node)
12                      self.ablist_by_score.append((alpha, beta, score, f"child score = {score}"))
13                      if score >= beta:
14                          alpha = score
15                          self.nodelist_by_score.append(node)
16                          self.ablist_by_score.append((alpha, beta, None, f"child score = {score}{beta}(β値) によるβ狩り"))
17                          break
元と同じなので省略
18                      self.nodelist_by_score.append(node)
19                      self.ablist_by_score.append((alpha, beta, None, message))
20                  node.score = alpha
21              else:
22                  for childnode in node.children:
23                      score = calc_ab_score(childnode, alpha, beta)
24                      self.nodelist_by_score.append(node)
25                      self.ablist_by_score.append((alpha, beta, score, f"child score = {score}"))
26                      if score <= alpha:
27                          beta = score
28                          self.nodelist_by_score.append(node)
29                          self.ablist_by_score.append((alpha, beta, None, f"child score = {score}{alpha}(α値)によるα狩り"))
30                          break
元と同じなので省略
31                      self.nodelist_by_score.append(node)
32                      self.ablist_by_score.append((alpha, beta, None, message))
33                  node.score = beta
34
35          self.nodelist_by_score.append(node)
36          self.ablist_by_score.append((alpha, beta, None, f"このノードの評価値が {node.score} で確定"))
元と同じなので省略
37    
38  Mbtree.calc_score_by_ab = calc_score_by_ab
行番号のないプログラム
from tree import Mbtree
from marubatsu import Marubatsu

def calc_score_by_ab(self, abroot, debug=False):           
    self.count = 0
      
    def calc_ab_score(node, alpha=float("-inf"), beta=float("inf")):
        self.nodelist_by_score.append(node)
        self.ablist_by_score.append((alpha, beta, None, "このノードの処理の開始"))
        self.count += 1
        if node.mb.status != Marubatsu.PLAYING:
            self.calc_score_of_node(node)
        else:
            if node.mb.turn == Marubatsu.CIRCLE:
                for childnode in node.children:
                    score = calc_ab_score(childnode, alpha, beta)
                    self.nodelist_by_score.append(node)
                    self.ablist_by_score.append((alpha, beta, score, f"child score = {score}"))
                    if score >= beta:
                        alpha = score
                        self.nodelist_by_score.append(node)
                        self.ablist_by_score.append((alpha, beta, None, f"child score = {score}{beta}(β値) によるβ狩り"))
                        break
                    if score > alpha:
                        message = f"child score = {score} > {alpha}(α値)によるα値の更新"
                        alpha = score
                    else:
                        message = f"child score = {score}{alpha}(α値)なのでα値の更新は行わない"
                    self.nodelist_by_score.append(node)
                    self.ablist_by_score.append((alpha, beta, None, message))
                node.score = alpha
            else:
                for childnode in node.children:
                    score = calc_ab_score(childnode, alpha, beta)
                    self.nodelist_by_score.append(node)
                    self.ablist_by_score.append((alpha, beta, score, f"child score = {score}"))
                    if score <= alpha:
                        beta = score
                        self.nodelist_by_score.append(node)
                        self.ablist_by_score.append((alpha, beta, None, f"child score = {score}{alpha}(α値)によるα狩り"))
                        break
                    if score < beta:
                        message = f"child score = {score} < {beta}(β値) によるβ値の更新"
                        beta = score
                    else:
                        message = f"child score = {score}{beta}(β値) なのでβ値の更新は行わない"
                    self.nodelist_by_score.append(node)
                    self.ablist_by_score.append((alpha, beta, None, message))
                node.score = beta

        self.nodelist_by_score.append(node)
        self.ablist_by_score.append((alpha, beta, None, f"このノードの評価値が {node.score} で確定"))
        node.score_index = len(self.nodelist_by_score) - 1          
        return node.score

    from ai import dprint       
    for node in self.nodelist:
        node.score_index = float("inf")
    self.nodelist_by_score = []
    self.ablist_by_score = []
    calc_ab_score(abroot)
    dprint(debug, "count =", self.count) 
    
Mbtree.calc_score_by_ab = calc_score_by_ab
修正箇所
from tree import Mbtree
from marubatsu import Marubatsu

def calc_score_by_ab(self, abroot, debug=False):           
    self.count = 0
      
    def calc_ab_score(node, alpha=float("-inf"), beta=float("inf")):
        self.nodelist_by_score.append(node)
-       self.ablist_by_score.append((alpha, beta, "このノードの処理の開始"))
+       self.ablist_by_score.append((alpha, beta, None, "このノードの処理の開始"))
元と同じなので省略
                    score = calc_ab_score(childnode, alpha, beta)
                    self.nodelist_by_score.append(node)
-                   self.ablist_by_score.append((alpha, beta, f"child score = {score}"))
+                   self.ablist_by_score.append((alpha, beta, score, f"child score = {score}"))
                    if score >= beta:
                        alpha = score
                        self.nodelist_by_score.append(node)
-                       self.ablist_by_score.append((alpha, beta, f"child score = {score}{beta}(β値) によるβ狩り"))
+                       self.ablist_by_score.append((alpha, beta, None, f"child score = {score}{beta}(β値) によるβ狩り"))
                        break
元と同じなので省略
                    self.nodelist_by_score.append(node)
-                   self.ablist_by_score.append((alpha, beta, message))
+                   self.ablist_by_score.append((alpha, beta, None, message))
                node.score = alpha
            else:
                for childnode in node.children:
                    score = calc_ab_score(childnode, alpha, beta)
                    self.nodelist_by_score.append(node)
-                   self.ablist_by_score.append((alpha, beta, f"child score = {score}"))
+                   self.ablist_by_score.append((alpha, beta, score, f"child score = {score}"))
                    if score <= alpha:
                        beta = score
                        self.nodelist_by_score.append(node)
-                       self.ablist_by_score.append((alpha, beta, f"child score = {score}{alpha}(α値)によるα狩り"))
+                       self.ablist_by_score.append((alpha, beta, None, f"child score = {score}{alpha}(α値)によるα狩り"))
                        break
元と同じなので省略
                    self.nodelist_by_score.append(node)
-                   self.ablist_by_score.append((alpha, beta, message))
+                   self.ablist_by_score.append((alpha, beta, None, message))
                node.score = beta

        self.nodelist_by_score.append(node)
-       self.ablist_by_score.append((alpha, beta, f"このノードの評価値が {node.score} で確定"))
+       self.ablist_by_score.append((alpha, beta, None, f"このノードの評価値が {node.score} で確定"))
元と同じなので省略
    
Mbtree.calc_score_by_ab = calc_score_by_ab

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

次に、Mbtree_Anim クラスの create_widgets メソッドを、下記のプログラムのように αβ 法の評価値の計算手順を描画する場合のみFigure を作成する 処理を記述します。作成した Figure と Axes は abfigabax 属性に代入し、Figure を作成しなかった場合は abfig 属性に None を代入 することにします。

  • 9 ~ 12 行目:この部分は with plt.ioff() のブロックの後に記述していたが、この後の 13 ~ 19 行目の記述にあわせて、ここに記述するように修正した
  • 13 行目:αβ 法の評価値の計算手順を描画するかどうかを前回の記事と同様の方法で判定する
  • 14、15 行目:数直線を描画するための Figure と Axes を先程と同様の設定で作成する
  • 16 ~ 19 行目:9 ~ 12 行目と同様に、作成した Figure のツールバーなどが表示されないように設定する
  • 20、21 行目:数直線を描画するための Figure を作成しない場合は、abfig 属性に None を代入する
 1  from tree import Mbtree_Anim
 2  import ipywidgets as widgets
 3
 4  def create_widgets(self):
元と同じなので省略
 5      with plt.ioff():
 6          self.fig = plt.figure(figsize=[self.width * self.size,
 7                                         self.height * self.size])
 8          self.ax = self.fig.add_axes([0, 0, 1, 1])
 9          self.fig.canvas.toolbar_visible = False
10          self.fig.canvas.header_visible = False
11          self.fig.canvas.footer_visible = False
12          self.fig.canvas.resizable = False
13          if self.isscore and hasattr(self.mbtree, "ablist_by_score"):
14              self.abfig = plt.figure(figsize=(5, 1))
15              self.abax = self.abfig.add_axes([0, 0, 1, 1])
16              self.abfig.canvas.toolbar_visible = False
17              self.abfig.canvas.header_visible = False
18              self.abfig.canvas.footer_visible = False
19              self.abfig.canvas.resizable = False 
20          else:
21              self.abfig = None
22        
23  Mbtree_Anim.create_widgets = create_widgets
行番号のないプログラム
from tree import Mbtree_Anim
import ipywidgets as widgets

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

    with plt.ioff():
        self.fig = plt.figure(figsize=[self.width * self.size,
                                        self.height * self.size])
        self.ax = self.fig.add_axes([0, 0, 1, 1])
        self.fig.canvas.toolbar_visible = False
        self.fig.canvas.header_visible = False
        self.fig.canvas.footer_visible = False
        self.fig.canvas.resizable = False 
        if self.isscore and hasattr(self.mbtree, "ablist_by_score"):
            self.abfig = plt.figure(figsize=(5, 1))
            self.abax = self.abfig.add_axes([0, 0, 1, 1])
            self.abfig.canvas.toolbar_visible = False
            self.abfig.canvas.header_visible = False
            self.abfig.canvas.footer_visible = False
            self.abfig.canvas.resizable = False 
        else:
            self.abfig = None
        
Mbtree_Anim.create_widgets = create_widgets
修正箇所
from tree import Mbtree_Anim
import ipywidgets as widgets

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=(5, 1))
+           self.abax = self.abfig.add_axes([0, 0, 1, 1])
+           self.abfig.canvas.toolbar_visible = False
+           self.abfig.canvas.header_visible = False
+           self.abfig.canvas.footer_visible = False
+           self.abfig.canvas.resizable = False 
+       else:
+           self.abfig = None
-   self.fig.canvas.toolbar_visible = False
-   self.fig.canvas.header_visible = False
-   self.fig.canvas.footer_visible = False
-   self.fig.canvas.resizable = False         
        
Mbtree_Anim.create_widgets = create_widgets

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

次に、Mbtree_Anim クラスの display_widgets メソッドを、下記のプログラムの 5、6 行目のように abfig に Figure が代入 されている場合のみ、ゲーム木を描画する Figure の上に abfig の Figure を配置するように修正します。

1  def display_widgets(self):
2      hbox = widgets.HBox([self.play, self.prev_button, self.next_button, self.frame_slider, self.interval_slider])
3      if self.abfig is None:
4           display(widgets.VBox([hbox, self.fig.canvas])) 
5      else:
6           display(widgets.VBox([hbox, self.abfig.canvas, self.fig.canvas])) 
7
8  Mbtree_Anim.display_widgets = display_widgets
行番号のないプログラム
def display_widgets(self):
    hbox = widgets.HBox([self.play, self.prev_button, self.next_button, self.frame_slider, self.interval_slider])
    if self.abfig is None:
        display(widgets.VBox([hbox, self.fig.canvas])) 
    else:
        display(widgets.VBox([hbox, self.abfig.canvas, self.fig.canvas])) 

Mbtree_Anim.display_widgets = display_widgets
修正箇所
def display_widgets(self):
    hbox = widgets.HBox([self.play, self.prev_button, self.next_button, self.frame_slider, self.interval_slider])
-   display(widgets.VBox([hbox, self.fig.canvas])) 
+   if self.abfig is None:
+       display(widgets.VBox([hbox, self.fig.canvas])) 
+   else:
+       display(widgets.VBox([hbox, self.abfig.canvas, self.fig.canvas])) 

Mbtree_Anim.display_widgets = display_widgets

Mbtree_Anim クラスの update_ab メソッドの定義

これまでは、Mbtree_Anim クラスの update_gui メソッドの中で α 値などに関する描画を行っていましたが、プログラムが長くなる ので αβ 法の計算手順に関する描画を 行う update_ab という下記のようなメソッドを定義する事にします。

  • 4 ~ 10 行目update_gui の中で記述されていた、α 値などを描画する処理と同じ。ただし 5 行目のみ、先程行った ab_list_by_score に代入された list の要素を修正したのにあわせて、子ノードの評価値を score に代入するように修正した
  • 12 行目:数直線を描画する Axes の表示をクリアする
  • 13 ~ 41 行目:先程の数直線や α 値などを描画するプログラムと同じ。ただし、axself.abax に修正し、38 ~ 41 行目では scoreNone ではない場合のみ 子ノードの評価値を表すデータを描画するように修正した
 1  from marubatsu import Marubatsu
 2
 3  def update_ab(self):
 4      fontsize = 70 * self.size
 5      alpha, beta, score, message = self.mbtree.ablist_by_score[self.play.value]
 6      acolor = "red" if self.selectednode.mb.turn == Marubatsu.CIRCLE else "black"
 7      bcolor = "black" if self.selectednode.mb.turn == Marubatsu.CIRCLE else "red"
 8      self.ax.text(0, -1, f"α: {alpha}", fontsize=fontsize, c=acolor)        
 9      self.ax.text(9, -1, f"β: {beta}", fontsize=fontsize, c=bcolor)        
10      self.ax.text(19, -1, message, fontsize=fontsize)             
11                          
12      self.abax.clear()
13      self.abax.set_xlim(-4, 14)
14      self.abax.set_ylim(-1.5, 1.5)
15      self.abax.axis("off")
16
17      minus_inf = -3
18      plus_inf = 4
19      self.abax.plot(range(minus_inf, plus_inf + 1), [0] * (plus_inf + 1 - minus_inf) , "|-k")
20      for num in range(minus_inf, plus_inf + 1):
21          if num == minus_inf:
22            numtext = "-∞"
23          elif num == plus_inf:
24              numtext = ""
25          else:
26              numtext = num
27          self.abax.text(num, -1, numtext, ha="center")
28          
29      alphacoord = max(minus_inf, alpha)
30      betacoord = min(plus_inf, beta)
31      arrowprops = { "arrowstyle": "->"}
32      self.abax.plot(alphacoord, 0, "or")
33      self.abax.annotate(f"α = {alpha}", xy=(alphacoord, 0), xytext=(minus_inf, 1),
34                         arrowprops=arrowprops, ha="center")
35      self.abax.plot(betacoord, 0, "ob")
36      self.abax.annotate(f"β = {beta}", xy=(betacoord, 0), xytext=(plus_inf, 1),
37                         arrowprops=arrowprops, ha="center")
38      if score is not None:
39          self.abax.plot(score, 0, "og")
40          self.abax.annotate(f"score = {score}", xy=(score, 0), xytext=((minus_inf + plus_inf) / 2, 1),
41                             arrowprops=arrowprops, ha="center")
42                            
43  Mbtree_Anim.update_ab = update_ab
行番号のないプログラム
from marubatsu import Marubatsu

def update_ab(self):
    fontsize = 70 * self.size
    alpha, beta, score, message = self.mbtree.ablist_by_score[self.play.value]
    acolor = "red" if self.selectednode.mb.turn == Marubatsu.CIRCLE else "black"
    bcolor = "black" if self.selectednode.mb.turn == Marubatsu.CIRCLE else "red"
    self.ax.text(0, -1, f"α: {alpha}", fontsize=fontsize, c=acolor)        
    self.ax.text(9, -1, f"β: {beta}", fontsize=fontsize, c=bcolor)        
    self.ax.text(19, -1, message, fontsize=fontsize)             
                            
    self.abax.clear()
    self.abax.set_xlim(-4, 14)
    self.abax.set_ylim(-1.5, 1.5)
    self.abax.axis("off")

    minus_inf = -3
    plus_inf = 4
    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 = "-∞"
        elif num == plus_inf:
            numtext = ""
        else:
            numtext = num
        self.abax.text(num, -1, numtext, ha="center")
        
    alphacoord = max(minus_inf, alpha)
    betacoord = min(plus_inf, beta)
    arrowprops = { "arrowstyle": "->"}
    self.abax.plot(alphacoord, 0, "or")
    self.abax.annotate(f"α = {alpha}", xy=(alphacoord, 0), xytext=(minus_inf, 1),
                       arrowprops=arrowprops, ha="center")
    self.abax.plot(betacoord, 0, "ob")
    self.abax.annotate(f"β = {beta}", xy=(betacoord, 0), xytext=(plus_inf, 1),
                       arrowprops=arrowprops, ha="center")
    if score is not None:
        self.abax.plot(score, 0, "og")
        self.abax.annotate(f"score = {score}", xy=(score, 0), xytext=((minus_inf + plus_inf) / 2, 1),
                           arrowprops=arrowprops, ha="center")
                            
Mbtree_Anim.update_ab = update_ab
修正箇所
from marubatsu import Marubatsu

def update_ab(self):
    fontsize = 70 * self.size
-   alpha, beta, message = self.mbtree.ablist_by_score[self.play.value]
+   alpha, beta, score, message = self.mbtree.ablist_by_score[self.play.value]
    acolor = "red" if self.selectednode.mb.turn == Marubatsu.CIRCLE else "black"
    bcolor = "black" if self.selectednode.mb.turn == Marubatsu.CIRCLE else "red"
    self.ax.text(0, -1, f"α: {alpha}", fontsize=fontsize, c=acolor)        
    self.ax.text(9, -1, f"β: {beta}", fontsize=fontsize, c=bcolor)        
    self.ax.text(19, -1, message, fontsize=fontsize)             
                            
+   self.abax.clear()
-   ax.set_xlim(-4, 14)
+   self.abax.set_xlim(-4, 14)
-   ax.set_ylim(-1.5, 1.5)
+   self.abax.set_ylim(-1.5, 1.5)
-   ax.axis("off")
+   self.abax.axis("off")

    minus_inf = -3
    plus_inf = 4
-   ax.plot(range(minus_inf, plus_inf + 1), [0] * (plus_inf + 1 - minus_inf) , "|-k")
+   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 = "-∞"
        elif num == plus_inf:
            numtext = ""
        else:
            numtext = num
-       ax.text(num, -1, numtext, ha="center")
+       self.abax.text(num, -1, numtext, ha="center")
        
    alphacoord = max(minus_inf, alpha)
    betacoord = min(plus_inf, beta)
    arrowprops = { "arrowstyle": "->"}
-   ax.plot(alphacoord, 0, "or")
+   self.abax.plot(alphacoord, 0, "or")
-   ax.annotate(f"α = {alpha}", xy=(alphacoord, 0), xytext=(minus_inf, 1),
+   self.abax.annotate(f"α = {alpha}", xy=(alphacoord, 0), xytext=(minus_inf, 1),
                       arrowprops=arrowprops, ha="center")
-   ax.plot(betacoord, 0, "ob")
+   self.abax.plot(betacoord, 0, "ob")
-   ax.annotate(f"β = {beta}", xy=(betacoord, 0), xytext=(plus_inf, 1),
+   self.abax.annotate(f"β = {beta}", xy=(betacoord, 0), xytext=(plus_inf, 1),
                       arrowprops=arrowprops, ha="center")
-   ax.plot(score, 0, "og")
-   ax.annotate(f"score = {score}", xy=(score, 0), xytext=((minus_inf + plus_inf) / 2, 1),
-                 arrowprops=arrowprops, ha="center")
+   if score is not None:
+       self.abax.plot(score, 0, "og")
+       self.abax.annotate(f"score = {score}", xy=(score, 0), xytext=((minus_inf + plus_inf) / 2, 1),
+                          arrowprops=arrowprops, ha="center")
                            
Mbtree_Anim.update_ab = update_ab

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

最後に、Mbtree_Anim クラスの update_gui メソッドを下記のプログラムの 5、6 行目のように、元々あった α 値などを描画する処理を削除 し、self.abfigNone でない 場合のみ上記で定義した update_ab メソッドを呼び出す ように修正します。

1  def update_gui(self):
元と同じなので省略
2      self.mbtree.draw_subtree(centernode=self.centernode, selectednode=self.selectednode,
3                               anim_frame=self.play.value, isscore=self.isscore, 
4                               ax=self.ax, maxdepth=maxdepth, size=self.size)
5      if self.abfig is not None:
6          self.update_ab()
元と同じなので省略   
7    
8  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(-2, self.height - 2)   
    self.ax.invert_yaxis()
    self.ax.axis("off")   
    
    self.selectednode = self.nodelist[self.play.value]
    self.centernode = self.selectednode
    if self.mbtree.algo == "bf":
        if self.centernode.depth > 0:
            self.centernode = self.centernode.parent
    while self.centernode.depth > 6:
        self.centernode = self.centernode.parent
    if self.centernode.depth <= 4:
        maxdepth = self.centernode.depth + 1
    elif self.centernode.depth == 5:
        maxdepth = 7
    else:
        maxdepth = 9
    self.mbtree.draw_subtree(centernode=self.centernode, selectednode=self.selectednode,
                             anim_frame=self.play.value, isscore=self.isscore, 
                             ax=self.ax, maxdepth=maxdepth, size=self.size)
    if self.abfig is not None:
        self.update_ab()
                            
    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.mbtree.draw_subtree(centernode=self.centernode, selectednode=self.selectednode,
                             anim_frame=self.play.value, isscore=self.isscore, 
                             ax=self.ax, maxdepth=maxdepth, size=self.size)
-   if self.isscore and hasattr(self.mbtree, "ablist_by_score"):
-       fontsize = 70 * self.size
-       alpha, beta, message = self.mbtree.ablist_by_score[self.play.value]
-       acolor = "red" if self.selectednode.mb.turn == Marubatsu.CIRCLE else "black"
-       bcolor = "black" if self.selectednode.mb.turn == Marubatsu.CIRCLE else "red"
-       self.ax.text(0, -1, f"α: {alpha}", fontsize=fontsize, c=acolor)        
-       self.ax.text(9, -1, f"β: {beta}", fontsize=fontsize, c=bcolor)        
-       self.ax.text(19, -1, message, fontsize=fontsize)    
+   if self.abfig is not None:
+       self.update_ab()
元と同じなので省略   
    
Mbtree_Anim.update_gui = update_gui

プログラムの動作の確認

上記の修正後に下記のプログラムで αβ 法で評価値を計算した mbtree に対して Mbtree_Anim を実行すると、実行結果のように ゲーム木の上部数直線と α 値、β 値が描画 されるようになることが確認できます。

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

実行結果

下図は赤枠の最初の子ノードの評価値が計算された直後の 9 フレーム目の画像で、上部に計算された子ノードの評価値が緑色の円で描画 され、その上に評価値の注釈が描画されるようになったことが確認できます。

下図はその次の 10 フレーム目の画像で、上図の赤枠のノードの α 値が評価値の値で更新されている ことが確認できます。上図から下図に アニメーションをコマ送りにする ことで、α 値が具体的にどのように変化するかが 文字で α 値を描画するよりも 直観的にわかりやすく表現される ようになります。

下記の 2 つの図は α 狩り が行われる 前後のフレームの図 です。上下のフレームの図を見比べた際に、α 狩りによって β 値が更新 されたのか、α 狩りが行われずに 子ノードの評価値が β 値未満になったため β 値が更新 されたのかの 違いがあまり明確ではありません。そこで、次回の記事では その違いが直観的にわかりやすくなる ように改良することにします。どのように改良すればよいかについて余裕がある方は考えてみて下さい。

今回の記事のまとめ

今回の記事では、数直線 を使って α 値、β 値、子ノードの評価値を視覚化することで、αβ 法の評価値の計算手順 がより わかりやすくなるような工夫 を行ないました。

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

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

次回の記事

  1. 軸を表示すると x 軸と y 軸の両方が表示されてしまうようです。matplotlib で数直線を簡単に表示する方法をご存じの方がいればコメントで教えて頂ければ助かります

  2. オリジナルのマーカーを作成することもできます。興味がある方は こちら のリンク先などを参照して下さい.

  3. add_axes を利用する理由について忘れた方は、以前の記事を復習して下さい

  4. plot で折れ線の頂点の x 座標の一覧を表すデータ は、このように直接 range を使って指定 することができるので、わざわざ [num for num in range(-3, 5)] のように list の形式で記述する必要はありません

  5. ただし、αβ 法の性質からそれぞれの値は -∞ ≦ alpha ≦ 3、-2 ≦ beta ≦ ∞、-2 ≦ score ≦ 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?