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を一から作成する その122 評価値が計算された部分木の動的な作成と Mbtree_GUI クラスによる表示

Last updated at Posted at 2024-10-06

目次と前回の記事

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

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

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

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

評価値が計算された部分木の動的な作成と表示

今回の記事も前回に引き続き、draw_subtree メソッドで表示する 部分木を動的に作成する処理 の実装の続きを行います。

現状では、create_subtree で作成した部分木を draw_subtree で表示した際に、評価値が表示されない という問題があります。draw_subtree で評価値を表示するためには、部分木の それぞれのノードの score 属性に評価値を記録 しておく必要がありますが、そのデータは create_subtree で部分木を作成する際に利用する、局面と最善手の対応表を表す bestmoves_by_board.dat には保存されていません

そこで、局面と最善手の対応表のデータに、局面と評価値の対応表のデータを加えた ものを作製し、それを使って create_subtree評価値を計算した部分木を作成 するように修正することにします。そのようなデータを以降は「局面と最善手・評価値の対応表のデータ」と表記することにします。

局面と最善手・評価値の対応表のデータの作成

局面と評価値の対応表のデータは、util.py の中で定義した calc_and_save_bestmoves_by_board という関数で作成し、ファイルに保存していました。局面と最善手・評価値の対応表のデータを作成してファイルに保存する処理は、この関数と同様の処理 で行うことができます。

局面と最善手・評価値の対応表のデータ構造

まず、局面と最善手・評価値の対応表のデータを どのようなデータ構造で表現するか を決める必要があります。どのようなデータ構造にすれば良いかについて少し考えてみて下さい。

局面と最善手の対応表のデータは、下記のような dict で表現 しました。

  • キーを局面を表す文字列とする
  • キーの値をその局面の最善手の一覧を表す list で表現する

同様に、局面と最善手・評価値の対応表のデータは、下記のような dict で表現することができます。

  • キーを局面を表す文字列とする
  • キーの値をその局面の「最善手の一覧を表す list」と「評価値」を表すデータとする

「最善手の一覧表す list」と「評価値」をまとめて表現する方法としては、以下のような方法が考えられます。

  • 0 番の要素を最善手の一覧表す list、1 番の要素を評価値とする tuple(または list)で表現 する
  • bestmoves というキーの値に最善手の一覧を表す list を、score というキーの値に評価値を代入する dict で表現 する

どちらの方法を使っても構いませんが、前者はプログラムの記述が短くなる、後者はプログラムがわかりやすくなるという利点があります。本記事ではわかりやすさを重視して後者の方法を採用することにします。なお、上記以外の方法で表現しても構わないので、よりよい方法を思いついた方はその方法で実装してみて下さい。

calc_and_save_bestmoves_by_board のメソッドとしての定義

局面と最善手・評価値の対応表のデータを作成してファイルに保存する処理を実装する前に、局面と最善手の対応表のデータ を作成してファイルに保存する処理を行う、calc_and_save_bestmoves_by_board に関する 修正を行う ことにします。

以前の記事ではこの処理を 通常の関数として定義 しましたが、よく考えるとこの処理は Mbtree クラスのインスタンスに対して行う処理 なので、Mbtree クラスの メソッドとして定義したほうが自然 です。そこで、下記のプログラムのように、Mbtree クラスのメソッドとして calc_and_save_bestmoves_by_board を 定義する事にします。

  • 6 行目:仮引数 mbtreeself に修正する
  • 8 行目mbtreeself に修正する
 1  from tree import Mbtree
 2  import pickle
 3  import gzip
 4  from tqdm import tqdm
 5
 6  def calc_and_save_bestmoves_by_board(self, path):   
 7      bestmoves_by_board = {}
 8      for node in tqdm(self.nodelist):
 9          txt = node.mb.board_to_str()
10          if not txt in bestmoves_by_board.keys():
11              bestmoves_by_board[txt] = node.bestmoves
12
13      with gzip.open(path, "wb") as f:
14          pickle.dump(bestmoves_by_board, f)
15    
16      return bestmoves_by_board
17
18  Mbtree.calc_and_save_bestmoves_by_board = calc_and_save_bestmoves_by_board  
行番号のないプログラム
from tree import Mbtree
import pickle
import gzip
from tqdm import tqdm

def calc_and_save_bestmoves_by_board(self, path):
    
    bestmoves_by_board = {}
    for node in tqdm(self.nodelist):
        txt = node.mb.board_to_str()
        if not txt in bestmoves_by_board.keys():
            bestmoves_by_board[txt] = node.bestmoves

    with gzip.open(path, "wb") as f:
        pickle.dump(bestmoves_by_board, f)
    
    return bestmoves_by_board

Mbtree.calc_and_save_bestmoves_by_board = calc_and_save_bestmoves_by_board  
修正箇所
from tree import Mbtree
import pickle
import gzip
from tqdm import tqdm

-def calc_and_save_bestmoves_by_board(mbtree, path):
+def calc_and_save_bestmoves_by_board(self, path):   
    bestmoves_by_board = {}
-   for node in tqdm(mbtree.nodelist):
+   for node in tqdm(self.nodelist):
        txt = node.mb.board_to_str()
        if not txt in bestmoves_by_board.keys():
            bestmoves_by_board[txt] = node.bestmoves

    with gzip.open(path, "wb") as f:
        pickle.dump(bestmoves_by_board, f)
    
    return bestmoves_by_board

Mbtree.calc_and_save_bestmoves_by_board = calc_and_save_bestmoves_by_board  

なお、util.py に記述していた calc_and_save_bestmoves_by_board はもう必要がなくなったので削除することにします。

calc_and_save_bestmoves_and_score_by_board メソッドの定義

次に 局面と最善手・評価値の対応表のデータ を作成してファイルに保存するメソッドを定義します。メソッドの名前は calc_and_save_bestmoves_and_score_by_board とし、下記のプログラムのように定義します。

なお、メソッドの 名前が長すぎる と思った方は、簡略化した名前を付けてもかまいませんが、簡略化しすぎるとプログラムがわかりづらくなってしまう 点に注意して下さい。本記事ではわかりやすさを重視して長い名前を採用することにします。

  • 1 行目:メソッドを定義する。仮引数は calc_and_save_bestmoves_by_board と同じ
  • 2 行目:作成したデータを記録する変数名を bestmoves_and_score_by_board とする
  • 5、6、12 行目bestmoves_by_boardbestmoves_and_score_by_board に修正する
  • 6 ~ 9 行目:最善手の一覧と評価値のデータを先程説明した dict の形式で代入する
 1  def calc_and_save_bestmoves_and_score_by_board(self, path):
 2      bestmoves_and_score_by_board = {}
 3      for node in tqdm(self.nodelist):
 4          txt = node.mb.board_to_str()
 5          if not txt in bestmoves_and_score_by_board.keys():
 6              bestmoves_and_score_by_board[txt] = {
 7                  "bestmoves": node.bestmoves,
 8                  "score": node.score,
 9              }
10
11      with gzip.open(path, "wb") as f:
12          pickle.dump(bestmoves_and_score_by_board, f)
13      
14    return bestmoves_and_score_by_board
15
16  Mbtree.calc_and_save_bestmoves_and_score_by_board = calc_and_save_bestmoves_and_score_by_board  
行番号のないプログラム
def calc_and_save_bestmoves_and_score_by_board(self, path):
    bestmoves_and_score_by_board = {}
    for node in tqdm(self.nodelist):
        txt = node.mb.board_to_str()
        if not txt in bestmoves_and_score_by_board.keys():
            bestmoves_and_score_by_board[txt] = {
                "bestmoves": node.bestmoves,
                "score": node.score,
            }

    with gzip.open(path, "wb") as f:
        pickle.dump(bestmoves_and_score_by_board, f)
    
    return bestmoves_and_score_by_board

Mbtree.calc_and_save_bestmoves_and_score_by_board = calc_and_save_bestmoves_and_score_by_board  
修正箇所
-def calc_and_save_bestmoves_by_board(self, path):
+def calc_and_save_bestmoves_and_score_by_board(self, path):
-   bestmoves_by_board = {}
+   bestmoves_and_score_by_board = {}
    for node in tqdm(self.nodelist):
        txt = node.mb.board_to_str()
-       if not txt in bestmoves_by_board.keys():
+       if not txt in bestmoves_and_score_by_board.keys():
-           bestmoves_by_board[txt] = node.bestmoves
+           bestmoves_and_score_by_board[txt] = {
+               "bestmoves": node.bestmoves,
+               "score": node.score,
+           }

    with gzip.open(path, "wb") as f:
-       pickle.dump(bestmoves_by_board, f)
+       pickle.dump(bestmoves_and_score_by_board, f)
    
    return bestmoves_and_score_by_board

Mbtree.calc_and_save_bestmoves_and_score_by_board = calc_and_save_bestmoves_and_score_by_board  

上記を実行後に、下記のプログラムを実行して、下記の 2 種類のゲーム木のデータをファイルから読み込み、局面と最善手・評価値の対応表を作成してファイルに保存します。

  • 通常の方法で評価値を計算した aidata.mbtree。bestmoves_and_score_by_board.dat という名前のファイルに保存する
  • 最短の勝利を優先した評価値を計算した bftree_shortest_victory.mbtree。bestmoves_and_score_by_board_shortest_victory.dat という名前のファイルに保存する
mbtree = Mbtree.load("../data/aidata")
bestmoves_and_score_by_board = mbtree.calc_and_save_bestmoves_and_score_by_board(
       "../data/bestmoves_and_score_by_board.dat")
bftree_shortest_victory = Mbtree.load("../data/bftree_shortest_victory")
bestmoves_and_score_by_board_shortest_victory = \
    bftree_shortest_victory.calc_and_save_bestmoves_and_score_by_board(
       "../data/bestmoves_and_score_by_board_shortest_victory.dat")

実行結果

100%|██████████| 549946/549946 [00:00<00:00, 593251.04it/s]
100%|██████████| 549946/549946 [00:01<00:00, 456474.54it/s]

なお、作成されたファイルは、どちらも約 23 KB のように小さいので、ファイルから読み込む際に時間はほとんどかかりません。

ファイル名が長すぎると思った方は、もっと短い名前でデータ保存してもかまいませんが、メソッドの名前と同様に、簡略化しすぎると後からそのファイルを見た時にどのようなデータが記録されているかがわかりづらくなる点に注意して下さい。

Node クラスの __init__ メソッドの修正

下記のプログラムのように Node クラスの __init__ メソッドを修正することで、ノードの作成時評価値を計算して score 属性に代入 することができます。なお、この修正は前回の記事で ノードの作成時に最善手を計算して bestmoves 属性に代入するようにした際の修正とほぼ同じです。

  • 3 行目:仮引数 bestmoves_by_boardbestmoves_and_score_by_board に修正する
  • 11 行目bestmoves_by_boardbestmoves_and_score_by_board に修正する
  • 12 ~ 14 行目:ゲーム盤を文字列に変換した値を利用して最善手の一覧と評価値が代入されたキーの値を取り出し、それぞれの値を bestmoves 属性と score 属性に代入する
 1  from tree import Node
 2
 3  def __init__(self, mb, parent=None, depth=0, bestmoves_and_score_by_board=None):
 4      self.id = Node.count
 5      Node.count += 1
 6      self.mb = mb
 7      self.parent = parent
 8      self.depth = depth
 9      self.children = []
10      self.children_by_move = {}   
11      if bestmoves_and_score_by_board is not None:
12          bestmoves_and_score = bestmoves_and_score_by_board[self.mb.board_to_str()]
13          self.bestmoves = bestmoves_and_score["bestmoves"]
14          self.score = bestmoves_and_score["score"]
15        
16  Node.__init__ = __init__
行番号のないプログラム
from tree import Node

def __init__(self, mb, parent=None, depth=0, bestmoves_and_score_by_board=None):
    self.id = Node.count
    Node.count += 1
    self.mb = mb
    self.parent = parent
    self.depth = depth
    self.children = []
    self.children_by_move = {}   
    if bestmoves_and_score_by_board is not None:
        bestmoves_and_score = bestmoves_and_score_by_board[self.mb.board_to_str()]
        self.bestmoves = bestmoves_and_score["bestmoves"]
        self.score = bestmoves_and_score["score"]
        
Node.__init__ = __init__
修正箇所
from tree import Node

-def __init__(self, mb, parent=None, depth=0, bestmoves_by_board=None):
+def __init__(self, mb, parent=None, depth=0, bestmoves_and_score_by_board=None):
    self.id = Node.count
    Node.count += 1
    self.mb = mb
    self.parent = parent
    self.depth = depth
    self.children = []
    self.children_by_move = {}   
-   if bestmoves_by_board is not None:
+   if bestmoves_and_score_by_board is not None:
-       self.bestmoves = bestmoves_by_board[self.mb.board_to_str()]
+       bestmoves_and_score = bestmoves_and_score_by_board[self.mb.board_to_str()]
+       self.bestmoves = bestmoves_and_score["bestmoves"]
+       self.score = bestmoves_and_score["score"]
        
Node.__init__ = __init__

Node クラスの calcchildren メソッドの修正

前回の記事と同様に、Node クラスの __init__ メソッドの修正にあわせて Node クラスのインスタンスを作成する処理を修正 する必要があります。まず、子ノードの一覧を作成する Node クラスの calc_children メソッドを下記のプログラムのように修正します。

  • 3 行目:仮引数 bestmoves_by_boardbestmoves_and_score_by_board に修正する
  • 9 行目bestmoves_by_boardbestmoves_and_score_by_board に修正する
 1  from copy import deepcopy
 2
 3  def calc_children(self, bestmoves_and_score_by_board=None):
 4      self.children = []
 5      for x, y in self.mb.calc_legal_moves():
 6          childmb = deepcopy(self.mb)
 7          childmb.move(x, y)
 8          self.insert(Node(childmb, parent=self, depth=self.depth + 1,
 9                           bestmoves_and_score_by_board=bestmoves_and_score_by_board))
10
11  Node.calc_children = calc_children
行番号のないプログラム
from copy import deepcopy

def calc_children(self, bestmoves_and_score_by_board=None):
    self.children = []
    for x, y in self.mb.calc_legal_moves():
        childmb = deepcopy(self.mb)
        childmb.move(x, y)
        self.insert(Node(childmb, parent=self, depth=self.depth + 1,
                         bestmoves_and_score_by_board=bestmoves_and_score_by_board))
        
Node.calc_children = calc_children
修正箇所
from copy import deepcopy

-def calc_children(self, bestmoves_by_board=None):
+def calc_children(self, bestmoves_and_score_by_board=None):
    self.children = []
    for x, y in self.mb.calc_legal_moves():
        childmb = deepcopy(self.mb)
        childmb.move(x, y)
        self.insert(Node(childmb, parent=self, depth=self.depth + 1,
-                        bestmoves_by_board=bestmoves_by_board))
+                        bestmoves_and_score_by_board=bestmoves_and_score_by_board))
        
Node.calc_children = calc_children

Mbtree クラスの create_subtree の修正

次に、Mbtree クラスの create_subtree の中で ノードを作成する処理を修正 します。

局面と最善手・評価値の対応表 のデータは、create_subtree の仮引数 subtree に代入する dict の bestmoves_and_score_by_board というキーの値に代入することにし、その値を使って下記のプログラムのようにノードを作成するように修正します。

  • 4 行目bestmoves_and_score_by_board に局面と最善手・評価値の対応表のデータを代入するように修正する
  • 5、7、8、9、11 行目bestmoves_by_boardbestmoves_and_score_by_board に修正する
  • 9 行目:元の bestmoves_by_board[board_str] には最善手の一覧を表す list が代入されていたが、修正後の bestmoves_and_score_by_board[board_str] には最善手の一覧と評価値を表す dict が代入されているので、bestmoves_and_score_by_board[board_str]["bestmoves"] のように修正する必要がある点に注意すること
 1  from marubatsu import Marubatsu
 2
 3  def create_subtree(self):
 4      bestmoves_and_score_by_board = self.subtree["bestmoves_and_score_by_board"]
 5      self.root = Node(Marubatsu(), bestmoves_and_score_by_board=bestmoves_and_score_by_board)
元と同じなので省略
 6                  childnode = Node(childmb, parent=node, depth=depth+1, 
 7                                   bestmoves_and_score_by_board=bestmoves_and_score_by_board)   
元と同じなので省略
 8                  node.calc_children(bestmoves_and_score_by_board=bestmoves_and_score_by_board)
元と同じなので省略         
 9                      x, y = bestmoves_and_score_by_board[board_str]["bestmoves"][0]
元と同じなので省略
10                      childnode = Node(childmb, parent=node, depth=depth+1, 
11                                       bestmoves_and_score_by_board=bestmoves_and_score_by_board)   
元と同じなので省略
12        
13  Mbtree.create_subtree = create_subtree
行番号のないプログラム
from marubatsu import Marubatsu

def create_subtree(self):
    bestmoves_and_score_by_board = self.subtree["bestmoves_and_score_by_board"]
    self.root = Node(Marubatsu(), bestmoves_and_score_by_board=bestmoves_and_score_by_board)
    
    depth = 0
    nodelist = [self.root]
    centermb = self.subtree["centermb"]
    centerdepth = centermb.move_count
    records = centermb.records
    maxdepth = self.subtree["maxdepth"]
    while len(nodelist) > 0:
        childnodelist = []
        for node in nodelist:
            if depth < centerdepth - 1:
                childmb = deepcopy(node.mb)
                x, y = records[depth + 1]
                childmb.move(x, y)
                childnode = Node(childmb, parent=node, depth=depth+1, 
                                 bestmoves_and_score_by_board=bestmoves_and_score_by_board)   
                node.insert(childnode)
                childnodelist.append(childnode)
            elif depth < maxdepth:
                node.calc_children(bestmoves_and_score_by_board=bestmoves_and_score_by_board)                   
                if depth == centerdepth - 1:
                    for move, childnode in node.children_by_move.items():
                        if move == records[depth + 1]:
                            self.centernode = childnode
                            childnodelist.append(self.centernode)
                        else:
                            if childnode.mb.status == Marubatsu.PLAYING:
                                childnode.children.append(None)
                else:
                    childnodelist += node.children
            else:
                if node.mb.status == Marubatsu.PLAYING:
                    childmb = deepcopy(node.mb)
                    board_str = node.mb.board_to_str()               
                    x, y = bestmoves_and_score_by_board[board_str]["bestmoves"][0]
                    childmb.move(x, y)
                    childnode = Node(childmb, parent=node, depth=depth+1, 
                                     bestmoves_and_score_by_board=bestmoves_and_score_by_board)   
                    node.insert(childnode)
                    childnodelist.append(childnode)
        nodelist = childnodelist
        depth += 1

    selectedmb = self.subtree["selectedmb"]
    self.selectednode = self.root
    for move in selectedmb.records[1:]:
        self.selectednode = self.selectednode.children_by_move[move]
        
Mbtree.create_subtree = create_subtree
修正箇所
from marubatsu import Marubatsu

def create_subtree(self):
-   bestmoves_by_board = self.subtree["bestmoves_by_board"]
+   bestmoves_and_score_by_board = self.subtree["bestmoves_and_score_by_board"]
-   self.root = Node(Marubatsu(), bestmoves_by_board=bestmoves_by_board)
+   self.root = Node(Marubatsu(), bestmoves_and_score_by_board=bestmoves_and_score_by_board)
元と同じなので省略
                childnode = Node(childmb, parent=node, depth=depth+1, 
-                                bestmoves_by_board=bestmoves_by_board)   
+                                bestmoves_and_score_by_board=bestmoves_and_score_by_board)   
元と同じなので省略
-               node.calc_children(bestmoves_by_board=bestmoves_by_board)
+               node.calc_children(bestmoves_and_score_by_board=bestmoves_and_score_by_board)     
元と同じなので省略         
-                   x, y = bestmoves_by_board[board_str][0]
+                   x, y = bestmoves_and_score_by_board[board_str]["bestmoves"][0]
元と同じなので省略
                    childnode = Node(childmb, parent=node, depth=depth+1, 
-                                    bestmoves_by_board=bestmoves_by_board)   
+                                    bestmoves_and_score_by_board=bestmoves_and_score_by_board)   
元と同じなので省略
        
Mbtree.create_subtree = create_subtree

上記の修正後に、下記のプログラムを実行して前回の記事と同様の下記のような部分木を作成して表示すると、実行結果のように評価値が表示されるようになったことが確認できます。ただし、一番上の評価値の上部が灰色の背景の部分からはみ出して表示される という問題が生じます。この問題の原因と修正する方法について少し考えてみて下さい。

  • 中心となるノードは (0, 0) と (1, 0) に着手を行った局面のノード
  • 選択されたノードは、さらに (2, 0) に着手を行った局面のノード
  • 中心となるノードから深さ 3 までの全ての子孫ノードを持つ部分木を計算し、それ以降の深さでは最善手を着手し続けた局面のノードを計算する
mb = Marubatsu()
mb.move(0, 0)
mb.move(1, 0)
mb2 = Marubatsu()
mb2.move(0, 0)
mb2.move(1, 0)
mb2.move(2, 0)
maxdepth = 3
subtree = Mbtree(subtree={"centermb": mb, "selectedmb": mb2, "maxdepth": maxdepth, 
                          "bestmoves_and_score_by_board": bestmoves_and_score_by_board})
centernode = subtree.centernode
selectednode = subtree.selectednode
subtree.draw_subtree(centernode=centernode, selectednode=selectednode, maxdepth=maxdepth,
                     show_bestmove=True)
修正箇所
mb = Marubatsu()
mb.move(0, 0)
mb.move(1, 0)
mb2 = Marubatsu()
mb2.move(0, 0)
mb2.move(1, 0)
mb2.move(2, 0)
maxdepth = 3
subtree = Mbtree(subtree={"centermb": mb, "selectedmb": mb2, "maxdepth": maxdepth, 
-                         "bestmoves_by_board": bestmoves_by_board})
+                         "bestmoves_and_score_by_board": bestmoves_and_score_by_board})
centernode = subtree.centernode
selectednode = subtree.selectednode
subtree.draw_subtree(centernode=centernode, selectednode=selectednode, maxdepth=maxdepth,
                     show_bestmove=True)

実行結果

問題の検証と修正

一番上の評価値が灰色の背景からはみ出るというバグは、以前の記事Mbtree_GUI クラスを使って部分木を表示 した際にも発生しました。その際には Mbtree_GUI クラスで作成する、部分木を表示する Figure の大きさAxes の表示範囲を修正 することでバグの修正を行いました。一方、先程のプログラムは Mbtree_GUI クラスからではなく、直接 draw_subtree メソッドを呼び出して部分木を表示 しているので、以前の記事の Mbtree_GUI クラスの修正は反映されません

先程のプログラムのように、キーワード引数 ax を記述せずdraw_subtree で部分木を表示 した場合は、draw_subtree の中で Figure を作成し、Axes の表示範囲を設定しているので、その部分を一番上の評価値が灰色の背景からはみでないように修正する必要があります。下記は、そのように draw_subtree を修正したプログラムです。

  • 7 行目:一番上の評価値がはみでないようするために、Figrue の高さを 1 増やす
  • 9 行目:Axes の y 座標の表示範囲の最小値を 0 から -1 に修正する
 1  import matplotlib.pyplot as plt
 2  import matplotlib.patches as patches
 3
 4  def draw_subtree(self, centernode=None, selectednode=None, ax=None, anim_frame=None,
 5                   isscore=False, show_bestmove=False, show_score=True, size=0.25, lw=0.8, maxdepth=2):
元と同じなので省略
 6      if ax is None:
 7          fig, ax = plt.subplots(figsize=(width * size, (height + 1) * size))
 8          ax.set_xlim(0, width)
 9          ax.set_ylim(-1, height)   
10          ax.invert_yaxis()
11          ax.axis("off")        
元と同じなので省略 
12            
13  Mbtree.draw_subtree = draw_subtree
行番号のないプログラム
import matplotlib.pyplot as plt
import matplotlib.patches as patches

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
            elif node.mb.last_move in node.parent.bestmoves:
                return 0
            else:
                return 0.2
            
        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
    
    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 = 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 = 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 = 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 = 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
修正箇所
import matplotlib.pyplot as plt
import matplotlib.patches as patches

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):
元と同じなので省略
    if ax is None:
-       fig, ax = plt.subplots(figsize=(width * size, height * size))
+       fig, ax = plt.subplots(figsize=(width * size, (height + 1) * size))
        ax.set_xlim(0, width)
-       ax.set_ylim(0, height)   
+       ax.set_ylim(-1, height)   
        ax.invert_yaxis()
        ax.axis("off")        
元と同じなので省略 
            
Mbtree.draw_subtree = draw_subtree

上記の修正後に下記のプログラムを実行すると、実行結果のように一番上の評価値が灰色の背景からはみでないようになります。

subtree = Mbtree(subtree={"centermb": mb, "selectedmb": mb2, "maxdepth": maxdepth, 
                          "bestmoves_and_score_by_board": bestmoves_and_score_by_board})
centernode = subtree.centernode
selectednode = subtree.selectednode
subtree.draw_subtree(centernode=centernode, selectednode=selectednode, maxdepth=maxdepth,
                     show_bestmove=True)

実行結果

Mbtree_GUI クラスの修正

次に create_subtree によって 動的に作成した部分木を描画 するように Mbtree_GUI クラスを修正します。どのように修正すれば良いかについて少し考えてみて下さい。

__init__ メソッドの修正

下記は、現状の Mbtree_GUI クラスの __init__ メソッドの定義です。

1  def __init__(self, mbtree, show_score=True, size=0.15):
2      self.mbtree = mbtree
3      self.show_score = show_score
4      self.size = size
5      self.width = 50
6      self.height = 65
7      self.selectednode = self.mbtree.root
8      super(Mbtree_GUI, self).__init__()

Mbtree_GUI クラスは、これまではゲーム木全体を表す Mbtree クラスのインスタンスを使って部分木の描画を行っていましたが、そのデータは もう必要がない ので、そのデータを代入する 仮引数 mbtree__init__ メソッドから削除する 必要があります。また、その代わりに 部分木動的に作成するために必要 となる 局面と最善手・評価値の対応表 のデータを代入する 仮引数を __init__ メソッドに追加 する必要があります。

また、上記の 7 行目では 選択されたノード を表す selectednode 属性を、ゲーム木の ルートノードのデータで初期化 していますが、仮引数 mbtree を削除 してしまうと、この時点では ゲーム木のデータは存在しない ので 7 行目を実行すると エラーが発生 してしまいます。7 行目で selectednode 属性に何を代入すればよいかについては、現時点では判断できないので保留 し、後で 7 行目の処理を修正することにします。

下記は __init__ メソッドを修正したプログラムです。

  • 3 行目:仮引数 mbtree を削除し、代わりに部分木を動的な作成に必要となる、局面と最善手・評価値の対応表のデータを代入する仮引数 bestmoves_and_score_by_board を追加する
  • 4 行目mbtree 属性に同名の仮引数を代入する処理を削除し、代わりに bestmoves_and_score_by_board 属性に同名の仮引数を代入する処理を追加する
  • 9 行目:このままでは self.mbtree に値が代入されていないのでエラーが発生するが、この部分の修正は後回しにする
 1  from tree import Mbtree_GUI
 2
 3  def __init__(self, bestmoves_and_score_by_board, show_score=True, size=0.15):   
 4      self.bestmoves_and_score_by_board = bestmoves_and_score_by_board
 5      self.show_score = show_score
 6      self.size = size
 7      self.width = 50
 8      self.height = 65
 9      self.selectednode = self.mbtree.root
10      super(Mbtree_GUI, self).__init__()
11    
12  Mbtree_GUI.__init__ = __init__
行番号のないプログラム
from tree import Mbtree_GUI

def __init__(self, bestmoves_and_score_by_board, show_score=True, size=0.15):   
    self.bestmoves_and_score_by_board = bestmoves_and_score_by_board
    self.show_score = show_score
    self.size = size
    self.width = 50
    self.height = 65
    self.selectednode = self.mbtree.root
    super(Mbtree_GUI, self).__init__()
    
Mbtree_GUI.__init__ = __init__
修正箇所
from tree import Mbtree_GUI

-def __init__(self, mbtree, show_score=True, size=0.15):   
+def __init__(self, bestmoves_and_score_by_board, show_score=True, size=0.15):   
-   self.mbtree = mbtree
+   self.bestmoves_and_score_by_board = bestmoves_and_score_by_board
    self.show_score = show_score
    self.size = size
    self.width = 50
    self.height = 65
    self.selectednode = self.mbtree.root
    super(Mbtree_GUI, self).__init__()
    
Mbtree_GUI.__init__ = __init__

update_gui メソッドの修正

部分木の表示 の処理を行う update_gui メソッドは、これまでは下記のプログラムによって、下記の手順で部分木を表示していました。

  • 2 ~ 7 行目:選択されたノードを表す self.selectednode の深さ(depth 属性)から、maxdepth を計算 する
  • 8 ~ 10 行目self.selectednode から 中心となるノード を下記の手順で 計算する
    • 深さが 6 以下の場合は、選択されたノードを中心となるノードとする
    • 深さが 6 より大きい場合は、親ノード(parent 属性)をさかのぼって、深さが 6 の先祖ノードを中心となるノードとする
  • 11 ~ 13 行目:ゲーム木全体のデータが代入された self.mbtreedraw_subtree メソッドを呼び出して部分木を描画する
 1  def update_gui():

 2          if self.selectednode.depth <= 4:
 3              maxdepth = self.selectednode.depth + 1
 4          elif self.selectednode.depth == 5:
 5              maxdepth = 7
 6          else:
 7              maxdepth = 9
 8          centernode = self.selectednode
 9          while centernode.depth > 6:
10              centernode = centernode.parent
11          self.mbtree.draw_subtree(centernode=centernode, selectednode=self.selectednode,
12                                   show_bestmove=True, show_score=self.show_score,
13                                   ax=self.ax, maxdepth=maxdepth, size=self.size)

上記の処理を、部分木を動的に作成 し、その部分木を表示する ように修正する必要があります。部分木を動的に作成する create_subtree メソッドは、選択されたノードの局面 を表すデータ、中心となるノードの局面 を表すデータ、すべての子孫ノードを計算するノードの 最大の深さを表す maxdepth を必要とし、それらのデータは上記のプログラムの 2 ~ 10 行目の処理を行った結果、下記の変数に代入 されます。

データ 代入される変数
選択されたノードの局面を表すデータ self.selectednode.mb
中心となるノードの局面を表すデータ centernode.mb
最大の深さ maxdepth

従って、上記のデータを利用 する事で 部分木を動的に作成 して 描画 を行えます。

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

  • 2 ~ 10 行目:この部分のプログラムは修正する必要はない
  • 11、12 行目:2 ~ 10 行目で計算したデータを利用して部分木を作成し、Mbtree_GUI クラスが表示するゲーム木のデータを表す mbtree 属性に代入 する
  • 13 行目self.mbtree に代入した部分木は、新しく作成 したものなので、その中の 選択されたノードを表す self.mbtree.selectednode は、self.selectednode に代入されているノード とは別のノード である。そのため、self.selectednode に新しく作成された部分木の選択されたノードを代入して 更新する必要 がある
  • 14 行目:同様に、中心となるノードも centernode に代入されているノードとは別のノードなので centernode新しく作成した部分木の中心となるノードに更新 する
 1  def update_gui(self):
元と同じなので省略
 2      if self.selectednode.depth <= 4:
 3          maxdepth = self.selectednode.depth + 1
 4      elif self.selectednode.depth == 5:
 5          maxdepth = 7
 6      else:
 7          maxdepth = 9
 8      centernode = self.selectednode
 9      while centernode.depth > 6:
10          centernode = centernode.parent
11      self.mbtree = Mbtree(subtree={"centermb": centernode.mb, "selectedmb": self.selectednode.mb, "maxdepth": maxdepth, 
12                           "bestmoves_and_score_by_board": self.bestmoves_and_score_by_board})
13      self.selectednode = self.mbtree.selectednode
14      centernode = self.mbtree.centernode
15      self.mbtree.draw_subtree(centernode=centernode, selectednode=self.selectednode,
16                               show_bestmove=True, show_score=self.show_score,
17                               ax=self.ax, maxdepth=maxdepth, size=self.size)
元と同じなので省略
18    
19  Mbtree_GUI.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")   
    
    if self.selectednode.depth <= 4:
        maxdepth = self.selectednode.depth + 1
    elif self.selectednode.depth == 5:
        maxdepth = 7
    else:
        maxdepth = 9
    centernode = self.selectednode
    while centernode.depth > 6:
        centernode = centernode.parent
    self.mbtree = Mbtree(subtree={"centermb": centernode.mb, "selectedmb": self.selectednode.mb, "maxdepth": maxdepth, 
                         "bestmoves_and_score_by_board": self.bestmoves_and_score_by_board})
    self.selectednode = self.mbtree.selectednode
    centernode = self.mbtree.centernode
    self.mbtree.draw_subtree(centernode=centernode, selectednode=self.selectednode,
                             show_bestmove=True, show_score=self.show_score,
                             ax=self.ax, maxdepth=maxdepth, size=self.size)
    
    disabled = self.selectednode.parent is None
    self.set_button_status(self.left_button, disabled=disabled)
    disabled = self.selectednode.depth >= 6 or len(self.selectednode.children) == 0
    self.set_button_status(self.right_button, disabled=disabled)
    disabled = self.selectednode.parent is None or self.selectednode.parent.children.index(self.selectednode) == 0
    self.set_button_status(self.up_button, disabled=disabled)
    disabled = self.selectednode.parent is None or self.selectednode.parent.children[-1] is self.selectednode
    self.set_button_status(self.down_button, disabled=disabled)
    self.set_button_color(self.score_button, value=self.show_score)
    
Mbtree_GUI.update_gui = update_gui
修正箇所
def update_gui(self):
元と同じなので省略
    if self.selectednode.depth <= 4:
        maxdepth = self.selectednode.depth + 1
    elif self.selectednode.depth == 5:
        maxdepth = 7
    else:
        maxdepth = 9
    centernode = self.selectednode
    while centernode.depth > 6:
        centernode = centernode.parent
+   self.mbtree = Mbtree(subtree={"centermb": centernode.mb, "selectedmb": self.selectednode.mb, "maxdepth": maxdepth, 
+                        "bestmoves_and_score_by_board": self.bestmoves_and_score_by_board})
+   self.selectednode = self.mbtree.selectednode
+   centernode = self.mbtree.centernode
    self.mbtree.draw_subtree(centernode=centernode, selectednode=self.selectednode,
                           show_bestmove=True, show_score=self.show_score,
                           ax=self.ax, maxdepth=maxdepth, size=self.size)
元と同じなので省略
    
Mbtree_GUI.update_gui = update_gui

上記の 2 ~ 14 行目で行った修正によって、self.mbtreeself.selectednodecenternode新しく作成した部分木に対応するデータが代入 されるため、それらの値を使って 部分木の表示を行う 15 行目 のプログラムを 変更する必要はありません

また、2 ~ 14 行目では それら以外の変数や属性の値を変更する処理は行っていない ので、Mbtree_GUI クラスの 他のプログラムを修正する必要はありません。例えば、create_event_handler メソッドに記述されている処理を変更する必要はありません。

__init__ メソッド内で selectenode 属性に代入する値

上記で他のプログラムを修正する必要はないと説明しましたが、それは update_gui の 2 ~ 14 行目の処理が 行われた後 で実行される処理を修正する必要はないという意味です。

先程保留した __init__ メソッドで selectednode 属性の初期化 を行う処理は、update_gui2 ~ 14 行目の処理を行う前に実行される ため、update_gui の 2 ~ 14 行目の処理を 正しく行うことができるようなデータで初期化する 必要があります。

update_gui の 2 ~ 14 行目で、self.selectednode に対して行われる処理 は、selectednode の深さを表す depth 属性 と、親ノードを表す parent 属性 に対する処理だけです。従って、それらの属性に適切なデータが代入 されている ルートノードのデータself.selectednode初期化すればよい ことがわかります。

従って、__init__ メソッドの中で self.selectednode に代入する ルートノードを表すデータ は、下記の理由から Node(Marubatsu()) を代入すればよいことがわかります。

  • ゲーム開始時の局面のデータは Marubatsu() によって作成できる
  • ルートノードには親ノードは存在しないので、キーワード引数 parent を記述する必要はない
  • ルートノードの深さは 0 で、Node クラスの __init__ メソッドの深さを代入する仮引数 depth はデフォルト値が 0 として定義されているので、キーワード引数 depth を記述する必要はない
  • 最善手の一覧や評価値のデータを計算する必要はないので、キーワード引数 bestmoves_and_score_by_board を記述する必要はない
  • 子ノードのデータは必要がないので、ルートノードを作成した後で、子ノードのデータを計算する必要はない

下記はそのように __init__ メソッドを修正したプログラムです。

  • 8 行目ゲーム開始時の局面を表すノードを作成 して selectednode 属性に代入 する
1  def __init__(self, bestmoves_and_score_by_board, show_score=True, size=0.15):   
2      self.bestmoves_and_score_by_board = bestmoves_and_score_by_board
3      self.show_score = show_score
4      self.size = size
5      self.width = 50
6      self.height = 65
7      self.selectednode = Node(Marubatsu())
8      super(Mbtree_GUI, self).__init__()    
9  Mbtree_GUI.__init__ = __init__
行番号のないプログラム
def __init__(self, bestmoves_and_score_by_board, show_score=True, size=0.15):   
    self.bestmoves_and_score_by_board = bestmoves_and_score_by_board
    self.show_score = show_score
    self.size = size
    self.width = 50
    self.height = 65
    self.selectednode = Node(Marubatsu())
    super(Mbtree_GUI, self).__init__()
    
Mbtree_GUI.__init__ = __init__
修正箇所
def __init__(self, bestmoves_and_score_by_board, show_score=True, size=0.15):   
    self.bestmoves_and_score_by_board = bestmoves_and_score_by_board
    self.show_score = show_score
    self.size = size
    self.width = 50
    self.height = 65
-   self.selectednode = self.mbtree.root
+   self.selectednode = Node(Marubatsu())
    super(Mbtree_GUI, self).__init__()    
Mbtree_GUI.__init__ = __init__

上記の修正後に下記のプログラムを実行すると、実行結果のようにエラーが発生します。このエラーの原因について少し考えてみて下さい。

Mbtree_GUI(bestmoves_and_score_by_board)

実行結果

略
Cell In[11], line 20
     17 self.mbtree = Mbtree(subtree={"centermb": centernode.mb, "selectedmb": self.selectednode.mb, "maxdepth": maxdepth, 
     18                      "bestmoves_and_score_by_board": self.bestmoves_and_score_by_board})
     19 self.selectednode = self.mbtree.selectednode
---> 20 centernode = self.mbtree.centernode
     21 self.mbtree.draw_subtree(centernode=centernode, selectednode=self.selectednode,
     22                          show_bestmove=True, show_score=self.show_score,
     23                          ax=self.ax, maxdepth=maxdepth, size=self.size)
     25 disabled = self.selectednode.parent is None

AttributeError: 'Mbtree' object has no attribute 'centernode'

エラーの原因の検証と修正

エラーメッセージから centernode=self.mbtree.centernode を実行した際に、self.mbtreecenternode 属性が存在しない ことがわかります。そこで、create_subtree 内で centernode 属性を計算する処理を検証 ことにします。

下記は create_subtreecenternode に値を代入する処理に関連するプログラムです。

 1  def create_subtree(self):
 2      bestmoves_and_score_by_board = self.subtree["bestmoves_and_score_by_board"]
 3      self.root = Node(Marubatsu(), bestmoves_and_score_by_board=bestmoves_and_score_by_board)
 4   
 5      depth = 0
 6      nodelist = [self.root]

 7      while len(nodelist) > 0:

 8                  node.calc_children(bestmoves_and_score_by_board=bestmoves_and_score_by_board)                   
 9                  if depth == centerdepth - 1:
10                      for move, childnode in node.children_by_move.items():
11                          if move == records[depth + 1]:
12                              self.centernode = childnode
                            

上記から、centernode に値を代入する処理は、7 行目の while 文による繰り返しの処理の中で、以下の手順で行われることがわかります。

  • 8 行目で node の子ノードを作成する
  • 11 行目で node の子ノードが中心となるノードと判定された場合に、12 行目で centernode 属性にその子ノードを代入する

一方、部分木の ルートノードは 3 行目で作成 されますが、ルートノード は他のノードの 子ノードではありません。そのため、ルートノードが中心となるノードの場合 は、11 行目の処理が実行されることはないため、centernode 属性に値は代入されません

Mbtree_GUI クラスの __init__ メソッドでは、ルートノードを selectednode に代入 し、その場合は update_gui 内の処理で ルートノードが中心となるノードとなる ので centernode 属性は計算されず、先程のようなエラーが発生することになります。

従って、このエラーは下記のプログラムのように、中心となるノードがルートノードの場合centernode 属性にルートノードを代入する ことで修正することができます。

  • 9、10 行目:深さが 0 のノードはルートノードのみなので、中心となるノードの深さが 0 の場合に centernode 属性にルートノードを代入する
 1  def create_subtree(self):
元と同じなので省略
 2      bestmoves_and_score_by_board = self.subtree["bestmoves_and_score_by_board"]
 3      self.root = Node(Marubatsu(), bestmoves_and_score_by_board=bestmoves_and_score_by_board)
 4   
 5      depth = 0
 6      nodelist = [self.root]
 7      centermb = self.subtree["centermb"]
 8      centerdepth = centermb.move_count
 9      if centerdepth == 0:
10          self.centernode = self.root
元と同じなので省略
11        
12  Mbtree.create_subtree = create_subtree
行番号のないプログラム
def create_subtree(self):
    bestmoves_and_score_by_board = self.subtree["bestmoves_and_score_by_board"]
    self.root = Node(Marubatsu(), bestmoves_and_score_by_board=bestmoves_and_score_by_board)
    
    depth = 0
    nodelist = [self.root]
    centermb = self.subtree["centermb"]
    centerdepth = centermb.move_count
    if centerdepth == 0:
        self.centernode = self.root
    records = centermb.records
    maxdepth = self.subtree["maxdepth"]
    while len(nodelist) > 0:
        childnodelist = []
        for node in nodelist:
            if depth < centerdepth - 1:
                childmb = deepcopy(node.mb)
                x, y = records[depth + 1]
                childmb.move(x, y)
                childnode = Node(childmb, parent=node, depth=depth+1, 
                                 bestmoves_and_score_by_board=bestmoves_and_score_by_board)   
                node.insert(childnode)
                childnodelist.append(childnode)
            elif depth < maxdepth:
                node.calc_children(bestmoves_and_score_by_board=bestmoves_and_score_by_board)                   
                if depth == centerdepth - 1:
                    for move, childnode in node.children_by_move.items():
                        if move == records[depth + 1]:
                            self.centernode = childnode
                            childnodelist.append(self.centernode)
                        else:
                            if childnode.mb.status == Marubatsu.PLAYING:
                                childnode.children.append(None)
                else:
                    childnodelist += node.children
            else:
                if node.mb.status == Marubatsu.PLAYING:
                    childmb = deepcopy(node.mb)
                    board_str = node.mb.board_to_str()               
                    x, y = bestmoves_and_score_by_board[board_str]["bestmoves"][0]
                    childmb.move(x, y)
                    childnode = Node(childmb, parent=node, depth=depth+1, 
                                     bestmoves_and_score_by_board=bestmoves_and_score_by_board)   
                    node.insert(childnode)
                    childnodelist.append(childnode)
        nodelist = childnodelist
        depth += 1

    selectedmb = self.subtree["selectedmb"]
    self.selectednode = self.root
    for move in selectedmb.records[1:]:
        self.selectednode = self.selectednode.children_by_move[move]
        
Mbtree.create_subtree = create_subtree
修正箇所
def create_subtree(self):
元と同じなので省略
    bestmoves_and_score_by_board = self.subtree["bestmoves_and_score_by_board"]
    self.root = Node(Marubatsu(), bestmoves_and_score_by_board=bestmoves_and_score_by_board)
    
    depth = 0
    nodelist = [self.root]
    centermb = self.subtree["centermb"]
    centerdepth = centermb.move_count
    if centerdepth == 0:
        self.centernode = self.root
元と同じなので省略
        
Mbtree.create_subtree = create_subtree

実行結果は省略しますが、上記の修正後に下記のプログラムを実行するとルートノードが選択された部分木が表示され、上部のボタンをクリックしたり、ノードをクリックするとこれまでと同様に選択されたノードが正しく変更されます。実際に確認してみて下さい。

Mbtree_GUI(bestmoves_and_score_by_board)

また、下記のプログラムのように、最短の勝利を優先した評価値を計算したデータ で Mbtree_GUI クラスのインスタンスを作成すると、実行結果のように部分木にそのような評価値が表示されるようになることが確認できます。なお、下図では 2.0 という評価値が表示されるように、(1, 0) に着手を行った局面を選択状態にしました。

Mbtree_GUI(bestmoves_and_score_by_board_shortest_victory)

実行結果

本記事のまとめ

本記事では、局面と最善手・評価値の対応表のデータを作成してファイルに保存するプログラムを作成し、そのデータ利用して部分木を表示するように Mbtree_GUI を修正しました。また、それによって異なる方法で評価値を計算したゲーム木の部分木を表示することができることを示しました。

次回の記事では Mbtree_GUI クラスで表示するゲーム木の種類を後から変更できるようにし、gui_play で部分木を正しく表示できるように修正する予定です。

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

リンク 説明
marubatsu.ipynb 本記事で入力して実行した JupyterLab のファイル
tree_new.py 今回の記事で更新した tree.py
util_new.py 今回の記事で更新した util_new.py
bestmoves_and_score_by_board.dat 今回の記事で作成した、局面と最善手・評価値の対応表のデータを保存したファイル
bestmoves_and_score_by_board_shortest_victory.dat 今回の記事で作成した、最短の勝利を優先した評価値を計算した局面と最善手・評価値の対応表のデータを保存したファイル

次回の記事

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?