Pythonで〇×ゲームのAIを一から作成する　その125　現在の局面の状況と合法手を着手した際の局面の状況の表示

# 目次と前回の記事

https://qiita.com/ysgeso/items/2381dd4e3283cbed49a0

https://qiita.com/ysgeso/items/2c4dcf4a73f08750811f

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

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

| リンク | 説明 |
|:--|:--|
| [marubatsu.py](https://github.com/ysgeso/marubatsu/blob/master/125/marubatsu.py)  | Marubatsu、Marubatsu_GUI クラスの定義|
| [ai.py](https://github.com/ysgeso/marubatsu/blob/master/125/ai.py)  |  AI に関する関数 |
| [test.py](https://github.com/ysgeso/marubatsu/blob/master/125/test.py)  |  テストに関する関数 |
|  [util.py](https://github.com/ysgeso/marubatsu/blob/master/125/util.py) |  ユーティリティ関数の定義。現在は `gui_play` のみ定義されている |
| [tree.py](https://github.com/ysgeso/marubatsu/blob/master/125/tree.py)  | ゲーム木に関する Node、Mbtree クラスの定義  |
| [gui.py](https://github.com/ysgeso/marubatsu/blob/master/125/gui.py)  |  GUI に関する処理を行う基底クラスとなる GUI クラスの定義  |

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

https://qiita.com/ysgeso/items/10d1d01192c014173e4b

# Marubatsu_GUI クラスの改良の続き

[前回の記事](https://qiita.com/ysgeso/items/2c4dcf4a73f08750811f#marubatsu_gui-%E3%82%AF%E3%83%A9%E3%82%B9%E3%81%AE%E6%94%B9%E8%89%AF)で、Marubatsu_GUI クラスの改良を行いました。今回の記事ではその続きで、下記の改良のうちの最初の 2 つを実装します。

* 現在の局面の状況がわかるようにする
* ゲーム盤のマスに、そのマスに着手を行った場合の局面の状況を表示する
* ゲーム盤のマスに、そのマスに着手を行った際の AI の評価値を表示できるようにする

# 現在の局面の状況がわかるようにする改良

__現在の局面の状況__ は、下部の __GUI の部分木__ の赤枠の選択された __局面の色を見ればわかる__ ようになっていますが、__ゲーム盤にもその情報が表示されると便利__ です。

ただし、局面の状況を常に表示してしまうと、__真剣勝負の邪魔になる__ ので、__ボタンでその表示を切り替える__ ことができるようにします。

## 現在の局面の状況の表示方法の検討

まず、局面の状況をどのように表示するかを決める必要があります。本記事では、__局面の状況を表す下記表の 1 文字を表示__ するという、シンプルな表示を行うことにします。別の方法で表示を行いたい人は自由に変更して下さい。

| 局面の状況 | 表示する文字 |
|:-:|:-:|
| __〇 の必勝の局面__  | 〇  |
| __引き分けの局面__  | △  |
| __× の必勝の局面__  | ×  |

また、局面の状況を表す文字は、ゲーム盤の上の __手番の右に表示__ することにします。別の場所に表示したい人は自由に変更して下さい。

## Marubatsu_GUI クラスの `__init__` メソッドの修正

局面の状況の表示を行うようにするための __修正の流れ__ は、[前回の記事](https://qiita.com/ysgeso/items/2c4dcf4a73f08750811f#marubatsu_gui-%E3%82%AF%E3%83%A9%E3%82%B9%E3%81%AE%E6%94%B9%E8%89%AF)で Marubatsu_GUI クラスの改良を行った場合のものと __ほぼ同じ__ です。

まず、下記のプログラムのように、最初に局面の状況（status）を表示するかどうかを表す __仮引数 `show_status`__ を Marubatsu_GUI クラスの __`__init__` メソッドに追加__ します。

また、__局面の状況を計算して表示するため__ には、__局面と最善手・評価値の対応表のデータが必要__ なので、13 行目でそのデータをファイルから読み込んで `score_table` 属性に代入しています。

* __6 行目__：仮引数 `show_status` を追加する
* __8 行目__：`show_status` を同名の属性に代入する
* __12、14 行目__：局面と最善手・評価値の対応表のデータをファイルから読み込んで `score_table` 属性に代入する。なお、12 行目で `load_bestmoves` を [ローカルなインポート](https://qiita.com/ysgeso/items/6d1430a88e251279d77a#%E5%BE%AA%E7%92%B0%E5%8F%82%E7%85%A7%E3%81%AE%E5%95%8F%E9%A1%8C%E3%81%AE%E4%BF%AE%E6%AD%A3)を行っているのは、循環参照を防ぐためである

```python
 1  from marubatsu import Marubatsu_GUI
 2  from tkinter import Tk
 3  import os
 4
 5  def __init__(self, mb, params, names, ai_dict, scoretable_dict, show_subtree,
 6               show_status, seed, size):
元と同じなので省略
 7      self.show_subtree = show_subtree
 8      self.show_status = show_status
 9      self.seed = seed
10      self.size = size
11    
12      from util import load_bestmoves
13
14      self.score_table = load_bestmoves("../data/bestmoves_and_score_by_board.dat")
元と同じなので省略
15    
16  Marubatsu_GUI.__init__ = __init__
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
from marubatsu import Marubatsu_GUI
from tkinter import Tk
import os

def __init__(self, mb, params, names, ai_dict, scoretable_dict, show_subtree,
             show_status, seed, size):
    if params is None:
        params = [{}, {}]
    if ai_dict is None:
        ai_dict = {}
    if names is None:
        names = [None, None]
    for i in range(2):
        if names[i] is None:
            if mb.ai[i] is None:
                names[i] = "人間"
            else:
                names[i] = mb.ai[i].__name__
    
    # JupyterLab からファイルダイアログを開く際に必要な前処理
    root = Tk()
    root.withdraw()
    root.call('wm', 'attributes', '.', '-topmost', True)  

    # save フォルダが存在しない場合は作成する
    if not os.path.exists("save"):
        os.mkdir("save")        
    
    self.mb = mb
    self.ai_dict = ai_dict
    self.params = params
    self.names = names
    self.show_subtree = show_subtree
    self.show_status = show_status
    self.seed = seed
    self.size = size

    from util import load_bestmoves

    self.score_table = load_bestmoves("../data/bestmoves_and_score_by_board.dat")
    
    super(Marubatsu_GUI, self).__init__()
    
    from tree import Mbtree_GUI

    self.mbtree_gui = Mbtree_GUI(scoretable_dict, size=0.1)
    
Marubatsu_GUI.__init__ = __init__
```
</details>

<details><summary><strong>修正箇所</strong></summary>

```diff_python
from marubatsu import Marubatsu_GUI
from tkinter import Tk
import os

def __init__(self, mb, params, names, ai_dict, scoretable_dict, show_subtree,
-            seed, size):
+            show_status, seed, size):
元と同じなので省略
    self.show_subtree = show_subtree
+   self.show_status = show_status
    self.seed = seed
    self.size = size

+   from util import load_bestmoves
   
+   self.score_table = load_bestmoves("../data/bestmoves_and_score_by_board.dat")
元と同じなので省略
    
Marubatsu_GUI.__init__ = __init__
```
</details>

:::note
これまでの記事で、局面と最善手・評価値の対応表のデータは __通常の方法で評価値を計算したもの__ と、__[最短の勝利を優先して評価値を計算したもの](https://qiita.com/ysgeso/items/0f83bcdf76bac5b0175f#mbtree-%E3%82%AF%E3%83%A9%E3%82%B9%E3%81%AE%E4%BF%AE%E6%AD%A3)__ の 2 つを作成しましたが、__どちらも__ 〇 の必勝の局面の評価値を正の値、引き分けの局面の評価値を 0、× の必勝の曲年の評価値を負の値 __として計算している__ ので、局面の状況を調べて表示する場合は __どちらの対応表のデータを読み込んでもかまいません__。

また、局面と最善手・評価値の対応表のデータは Mbtree_GUI クラスでもファイルから読みんでいますが、ファイルサイズが小さく、__ほぼ一瞬で読み込むことができる__ ので __重複して読み込む__ ことにしました。
:::

## Marubatsu クラスの `play` メソッドの修正

次に、Marubatsu_GUI クラスのインスタンスを作成する Marubatsu クラスの `play` メソッドを以下のプログラムのように修正します。

* __4 行目__：デフォルト値を `False`[^1] とする仮引数 `show_status` を追加する
* __6 行目__：Marubatsu_GUI クラスのインスタンスを作成する際に、実引数 `show_status=show_status` を追加する

```python
1  from marubatsu import Marubatsu
2
3  def play(self, ai:list, ai_dict=None, params=None, names=None, scoretable_dict=None,
4           show_subtree=True, show_status=False, verbose=True, seed=None, gui=False, size=3):
元と同じなので省略
5      if gui:
6          mb_gui = Marubatsu_GUI(self, params=params, names=names, ai_dict=ai_dict, scoretable_dict=scoretable_dict, 
7                                 show_subtree=show_subtree, show_status=show_status, seed=seed, size=size)
元と同じなので省略                        
8
9  Marubatsu.play = play
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
from marubatsu import Marubatsu

def play(self, ai:list, ai_dict=None, params=None, names=None, scoretable_dict=None,
         show_subtree=True, show_status=False, verbose=True, seed=None, gui=False, size=3):
    # params が None の場合のデフォルト値を設定する
    if params is None:
        params = [{}, {}]
        
    # 一部の仮引数をインスタンスの属性に代入する
    self.ai = ai
    self.verbose = verbose
    self.gui = gui
    
    # seed が None でない場合は、seed を乱数の種として設定する
    if seed is not None:
        random.seed(seed)

    # gui が True の場合に、GUI の処理を行う Marubatsu_GUI のインスタンスを作成する
    if gui:
        mb_gui = Marubatsu_GUI(self, params=params, names=names, ai_dict=ai_dict, scoretable_dict=scoretable_dict, 
                              show_subtree=show_subtree, show_status=show_status, seed=seed, size=size)
    else:
        mb_gui = None
        
    self.restart()
    return self.play_loop(mb_gui, params=params)

Marubatsu.play = play
```
</details>

<details><summary><strong>修正箇所</strong></summary>

```diff_python
from marubatsu import Marubatsu

def play(self, ai:list, ai_dict=None, params=None, names=None, scoretable_dict=None,
-        show_subtree=True, verbose=True, seed=None, gui=False, size=3):
+        show_subtree=True, show_status=False, verbose=True, seed=None, gui=False, size=3):
元と同じなので省略
    if gui:
        mb_gui = Marubatsu_GUI(self, params=params, names=names, ai_dict=ai_dict, scoretable_dict=scoretable_dict, 
-                              show_subtree=show_subtree, seed=seed, size=size)
+                              show_subtree=show_subtree, show_status=show_status, seed=seed, size=size)
元と同じなので省略                        

Marubatsu.play = play
```
</details>

## Marubatsu_GUI クラスの `create_widgets` メソッドの修正

:::note
2024/11/16 修正
`create_widgets` の最後に余計な処理が記述されていたので修正しました。
:::

次に、局面の状況の __表示の有無を選択するボタンを作成__ する必要があります。本記事では、__ボタンに表示する文字__ を「__状況__」としました。下記は、そのように `create_widgets` を修正したプログラムです。

* __5 行目__：「状況」と表示するボタンを作成する

```python
1  import ipywidgets as widgets 
2
3  def create_widgets(self):
元と同じなので省略
4      # 状況ボタン、大きさを変更する FloatSlider を作成する        
5      self.show_status_button = self.create_button("状況", 50)
6      self.size_slider = widgets.FloatSlider(min=1.0, max=5.0, step=0.1,
7                                          description="size", value=self.size)
元と同じなので省略
8    
9  Marubatsu_GUI.create_widgets = create_widgets
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
import ipywidgets as widgets 

def create_widgets(self):
    # 乱数の種の Checkbox と IntText を作成する
    self.checkbox = widgets.Checkbox(value=self.seed is not None, description="乱数の種",
                                    indent=False, layout=widgets.Layout(width="100px"))
    self.inttext = widgets.IntText(value=0 if self.seed is None else self.seed,
                                layout=widgets.Layout(width="80px"))   

    # 読み書き、ヘルプのボタンを作成する
    self.load_button = self.create_button("開く", 50)
    self.save_button = self.create_button("保存", 50)
    self.show_tree_button = self.create_button("木", 34)
    self.reset_tree_button = self.create_button("リ", 34)
    self.help_button = self.create_button("？", 34)

    # 状況ボタン、大きさを変更する FloatSlider を作成する        
    self.show_status_button = self.create_button("状況", 50)
    self.size_slider = widgets.FloatSlider(min=1.0, max=5.0, step=0.1,
                                        description="size", value=self.size)
    
    # AI を選択する Dropdown を作成する
    self.create_dropdown()
    # 変更、リセット、待ったボタンを作成する
    self.change_button = self.create_button("変更", 50)
    self.reset_button = self.create_button("リセット", 80)
    self.undo_button = self.create_button("待った", 60)    
    
    # リプレイのボタンとスライダーを作成する
    self.first_button = self.create_button("<<", 50)
    self.prev_button = self.create_button("<", 50)
    self.next_button = self.create_button(">", 50)
    self.last_button = self.create_button(">>", 50)     
    self.slider = widgets.IntSlider(layout=widgets.Layout(width="200px"))
    # ゲーム盤の画像を表す figure を作成する
    self.create_figure()

    # print による文字列を表示する Output を作成する
    self.output = widgets.Output()       
    
    # ヘルプを表示する Output を作成し、表示の設定を行う
    self.help = widgets.Output()
    self.print_helpmessage()
    self.help.layout.display = "none"    

    self.output = widgets.Output()  
    self.print_helpmessage()
    self.output.layout.display = "none"
    
Marubatsu_GUI.create_widgets = create_widgets
```
</details>

<details><summary><strong>修正箇所</strong></summary>

```diff_python
import ipywidgets as widgets 

def create_widgets(self):
元と同じなので省略
    # 状況ボタン、大きさを変更する FloatSlider を作成する        
+   self.show_status_button = self.create_button("状況", 50)
    self.size_slider = widgets.FloatSlider(min=1.0, max=5.0, step=0.1,
                                        description="size", value=self.size)
元と同じなので省略
    
Marubatsu_GUI.create_widgets = create_widgets
```
</details>

## Marubatsu_GUI クラスの `display_widgets` メソッドの修正

「状況」ボタンは、__FloatSlider の左に配置__ することにします。下記はそのように __`display_widgets`__ を修正したプログラムです。

* __3 行目__：FloatSlider の左に「状況」ボタンのウィジェットを配置するようにする

```python
1  def display_widgets(self):
元と同じなので省略
2      # 状況ボタンとゲーム盤のサイズの変更の FloatSlider を配置した HBox を作成する
3      hbox2 = widgets.HBox([self.show_status_button, self.size_slider])
元と同じなので省略
4    
5  Marubatsu_GUI.display_widgets = display_widgets
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
def display_widgets(self):
    # 乱数の種のウィジェット、読み書き、ヘルプのボタンを横に配置した HBox を作成する
    hbox1 = widgets.HBox([self.checkbox, self.inttext, self.load_button, self.save_button, 
                        self.show_tree_button, self.reset_tree_button, self.help_button])
    # 状況ボタンとゲーム盤のサイズの変更の FloatSlider を配置した HBox を作成する
    hbox2 = widgets.HBox([self.show_status_button, self.size_slider])
    # 〇 と × の dropdown とボタンを横に配置した HBox を作成する
    hbox3 = widgets.HBox([self.dropdown_list[0], self.dropdown_list[1], self.change_button, self.reset_button, self.undo_button])
    # リプレイ機能のボタンを横に配置した HBox を作成する
    hbox4 = widgets.HBox([self.first_button, self.prev_button, self.next_button, self.last_button, self.slider]) 
    # hbox1 ~ hbox4、Figure、Output を縦に配置した VBox を作成し、表示する
    display(widgets.VBox([hbox1, hbox2, hbox3, hbox4, self.fig.canvas, self.output, self.help])) 
    
Marubatsu_GUI.display_widgets = display_widgets
```
</details>

<details><summary><strong>修正箇所</strong></summary>

```diff_python
def display_widgets(self):
元と同じなので省略
    # 状況ボタンとゲーム盤のサイズの変更の FloatSlider を配置した HBox を作成する
-   hbox2 = widgets.HBox([self.size_slider])
+   hbox2 = widgets.HBox([self.show_status_button, self.size_slider])
元と同じなので省略
    
Marubatsu_GUI.display_widgets = display_widgets
```
</details>

## Marubatsu_GUI クラスの `create_event_handler` メソッドの修正

次に、`create_event_handler` 以下のプログラムのように修正します。

* __4 ~ 6 行目__：「状況」ボタンをクリックした際に実行されるイベントハンドラを定義する。行う処理は、`show_status` 属性の値を反転し、画面の表示を更新する処理である
* __14 行目__：「状況」ボタンとイベントハンドラを結び付ける

```python
 1  import math
 2
 3  def create_event_handler(self):
元と同じなので省略
 4      def on_show_status_button_clicked(b=None):
 5          self.show_status = not self.show_status
 6          self.update_gui()
 7
 8      def on_size_slider_changed(changed):
 9          self.size = changed["new"]
10          self.fig.set_figwidth(self.size)
11          self.fig.set_figheight(self.size)
12          self.update_gui()
13
14      self.show_status_button.on_click(on_show_status_button_clicked)
15      self.size_slider.observe(on_size_slider_changed, names="value")
元と同じなので省略
    
Marubatsu_GUI.create_event_handler = create_event_handler
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
import math

def create_event_handler(self):
    # 乱数の種のチェックボックスのイベントハンドラを定義する
    def on_checkbox_changed(changed):
        self.update_widgets_status()
        
    self.checkbox.observe(on_checkbox_changed, names="value")

    # 開く、保存ボタンのイベントハンドラを定義する
    def on_load_button_clicked(b=None):
        path = filedialog.askopenfilename(filetypes=[("〇×ゲーム", "*.mbsav")],
                                        initialdir="save")
        if path != "":
            with open(path, "rb") as f:
                data = pickle.load(f)
                self.mb.records = data["records"]
                self.mb.ai = data["ai"]
                self.params = data["params"] if "params" in data else [ {}, {} ]
                if "names" in data:
                    names = data["names"]
                else:
                    names = [ "人間" if mb.ai[i] is None else mb.ai[i].__name__ for i in range(2)]                       
                options = self.dropdown_list[0].options.copy()
                for i in range(2):
                    value = (self.mb.ai[i], self.params[i]) 
                    if not value in options.values():
                        options[names[i]] = value
                for i in range(2):
                    self.dropdown_list[i].options = options
                    self.dropdown_list[i].value = (self.mb.ai[i], self.params[i])            
                change_step(data["move_count"])
                if data["seed"] is not None:                   
                    self.checkbox.value = True
                    self.inttext.value = data["seed"]
                else:
                    self.checkbox.value = False
                    
    def on_save_button_clicked(b=None):
        names = [ self.dropdown_list[i].label for i in range(2) ]     
        timestr = datetime.now().strftime("%Y年%m月%d日 %H時%M分%S秒")
        fname = f"{names[0]} VS {names[1]} {timestr}"
        path = filedialog.asksaveasfilename(filetypes=[("〇×ゲーム", "*.mbsav")],
                                            initialdir="save", initialfile=fname,
                                            defaultextension="mbsav")
        if path != "":
            with open(path, "wb") as f:
                data = {
                    "records": self.mb.records,
                    "move_count": self.mb.move_count,
                    "ai": self.mb.ai,
                    "params": self.params,
                    "names": names,
                    "seed": self.inttext.value if self.checkbox.value else None
                }
                pickle.dump(data, f)
                
    def on_show_tree_button_clicked(b=None):
        self.show_subtree = not self.show_subtree
        self.mbtree_gui.vbox.layout.display = None if self.show_subtree else "none"
        self.update_gui()
        
    def on_reset_tree_button_clicked(b=None):
        self.update_gui()
                
    def on_help_button_clicked(b=None):
        self.help.layout.display = "none" if self.help.layout.display is None else None

    self.load_button.on_click(on_load_button_clicked)
    self.save_button.on_click(on_save_button_clicked)
    self.show_tree_button.on_click(on_show_tree_button_clicked)
    self.reset_tree_button.on_click(on_reset_tree_button_clicked)
    self.help_button.on_click(on_help_button_clicked)
    
    def on_show_status_button_clicked(b=None):
        self.show_status = not self.show_status
        self.update_gui()

    def on_size_slider_changed(changed):
        self.size = changed["new"]
        self.fig.set_figwidth(self.size)
        self.fig.set_figheight(self.size)
        self.update_gui()

    self.show_status_button.on_click(on_show_status_button_clicked)
    self.size_slider.observe(on_size_slider_changed, names="value")

    # 変更ボタンのイベントハンドラを定義する
    def on_change_button_clicked(b):
        for i in range(2):
            self.mb.ai[i], self.params[i] = self.dropdown_list[i].value
        self.mb.play_loop(self, self.params)

    # リセットボタンのイベントハンドラを定義する
    def on_reset_button_clicked(b=None):
        # 乱数の種のチェックボックスが ON の場合に、乱数の種の処理を行う
        if self.checkbox.value:
            random.seed(self.inttext.value)
        self.mb.restart()
        self.output.clear_output()
        on_change_button_clicked(b)

    # 待ったボタンのイベントハンドラを定義する
    def on_undo_button_clicked(b=None):
        if self.mb.move_count >= 2 and self.mb.move_count == len(self.mb.records) - 1:
            self.mb.move_count -= 2
            self.mb.records = self.mb.records[0:self.mb.move_count+1]
            self.mb.change_step(self.mb.move_count)
            self.update_gui()
        
    # イベントハンドラをボタンに結びつける
    self.change_button.on_click(on_change_button_clicked)
    self.reset_button.on_click(on_reset_button_clicked)   
    self.undo_button.on_click(on_undo_button_clicked)   

    # step 手目の局面に移動する
    def change_step(step):
        self.mb.change_step(step)
        # 描画を更新する
        self.update_gui()        

    def on_first_button_clicked(b=None):
        change_step(0)

    def on_prev_button_clicked(b=None):
        change_step(self.mb.move_count - 1)

    def on_next_button_clicked(b=None):
        change_step(self.mb.move_count + 1)
        
    def on_last_button_clicked(b=None):
        change_step(len(self.mb.records) - 1)

    def on_slider_changed(changed):
        if self.mb.move_count != changed["new"]:
            change_step(changed["new"])
        
    self.first_button.on_click(on_first_button_clicked)
    self.prev_button.on_click(on_prev_button_clicked)
    self.next_button.on_click(on_next_button_clicked)
    self.last_button.on_click(on_last_button_clicked)
    self.slider.observe(on_slider_changed, names="value")

    # ゲーム盤の上でマウスを押した場合のイベントハンドラ
    def on_mouse_down(event):
        # Axes の上でマウスを押していた場合のみ処理を行う
        if event.inaxes and self.mb.status == Marubatsu.PLAYING:
            x = math.floor(event.xdata)
            y = math.floor(event.ydata)
            with self.output:
                self.mb.move(x, y)                
            # 次の手番の処理を行うメソッドを呼び出す
                self.mb.play_loop(self, self.params)

    # ゲーム盤を選択した状態でキーを押した場合のイベントハンドラ
    def on_key_press(event):
        keymap = {
            "up": on_first_button_clicked,
            "left": on_prev_button_clicked,
            "right": on_next_button_clicked,
            "down": on_last_button_clicked,
            "0": on_undo_button_clicked,
            "enter": on_reset_button_clicked,            
            "-": on_load_button_clicked,            
            "l": on_load_button_clicked,            
            "+": on_save_button_clicked,            
            "s": on_save_button_clicked,            
            "*": on_help_button_clicked,            
            "h": on_help_button_clicked,            
        }
        if event.key in keymap:
            keymap[event.key]()
        else:
            try:
                num = int(event.key) - 1
                event.inaxes = True
                event.xdata = num % 3
                event.ydata = 2 - (num // 3)
                on_mouse_down(event)
            except:
                pass
            
    # fig の画像イベントハンドラを結び付ける
    self.fig.canvas.mpl_connect("button_press_event", on_mouse_down)     
    self.fig.canvas.mpl_connect("key_press_event", on_key_press)    
    
Marubatsu_GUI.create_event_handler = create_event_handler
```
</details>

<details><summary><strong>修正箇所</strong></summary>

```diff_python
import math

def create_event_handler(self):
元と同じなので省略
+   def on_show_status_button_clicked(b=None):
+       self.show_status = not self.show_status
+       self.update_gui()

    def on_size_slider_changed(changed):
        self.size = changed["new"]
        self.fig.set_figwidth(self.size)
        self.fig.set_figheight(self.size)
        self.update_gui()

+   self.show_status_button.on_click(on_show_status_button_clicked)
    self.size_slider.observe(on_size_slider_changed, names="value")
元と同じなので省略
    
Marubatsu_GUI.create_event_handler = create_event_handler
```
</details>

## Marubastu_GUI クラスの `update_gui` メソッドの修正

次に、__局面の状況を表示__ する処理を __`update_gui`__ に記述します。決着がついている場合は o win のような表示が行われるので、局面の状況は __決着がついていない場合__ に行えばよいでしょう。下記は、そのように `update_gui` を修正したプログラムです。

* __3、5 行目__：ゲームの決着がついておらず、局面の状況を表示するかどうかを表す `self.show_status` が `True` であることを判定する
* __6 行目__：`score_table` 属性に代入された[局面と最善手・評価値の対応表のデータ](https://qiita.com/ysgeso/items/c4150416c994571b2803#calc_and_save_bestmoves_and_score_by_board-%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%AE%E5%AE%9A%E7%BE%A9)と、局面を表す文字列を利用して、現在の局面の評価値を計算する
* __7 行目__：手番を表す文字列と局面の状況を表す文字列の __間を空ける__ ために、__半角の空白を `text` の後に追加__ する
* __8 ~ 13 行目__：評価値の値に応じて、手番を表す `text` の後に局面の状況を表す文字列を計算して追加する

```python
 1  def update_gui(self):
元と同じなので省略
 2      # ゲームの決着がついていない場合は、手番を表示する
 3      if self.mb.status == Marubatsu.PLAYING:
 4          text = "Turn " + self.mb.turn
 5          if self.show_status:
 6              score = self.score_table[self.mb.board_to_str()]["score"]
 7              text += " "
 8              if score > 0:
 9                  text += "〇"
10              elif score == 0:
11                  text += "△"
12              else:
13                  text += "×"
元と同じなので省略
14       
15  Marubatsu_GUI.update_gui = update_gui
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
def update_gui(self):
    ax = self.ax
    ai = self.mb.ai

    # Axes の内容をクリアして、これまでの描画内容を削除する
    ax.clear()

    # y 軸を反転させる
    ax.invert_yaxis()

    # 枠と目盛りを表示しないようにする
    ax.axis("off")   

    # リプレイ中、ゲームの決着がついていた場合は背景色を変更する
    is_replay =  self.mb.move_count < len(self.mb.records) - 1 
    if self.mb.status == Marubatsu.PLAYING:
        facecolor = "lightcyan" if is_replay else "white"
    else:
        facecolor = "lightyellow"

    ax.figure.set_facecolor(facecolor)
        
    # 上部のメッセージを描画する
    # 対戦カードの文字列を計算する
    ax.text(1.5, 3.5, f"{self.dropdown_list[0].label}　VS　{self.dropdown_list[1].label}", 
            fontsize=7*self.size, ha="center")   

    # ゲームの決着がついていない場合は、手番を表示する
    if self.mb.status == Marubatsu.PLAYING:
        text = "Turn " + self.mb.turn
        if self.show_status:
            score = self.score_table[self.mb.board_to_str()]["score"]
            text += " "
            if score > 0:
                text += "〇"
            elif score == 0:
                text += "△"
            else:
                text += "×"
    # 引き分けの場合
    elif self.mb.status == Marubatsu.DRAW:
        text = "Draw game"
    # 決着がついていれば勝者を表示する
    else:
        text = "Winner " + self.mb.status
    # リプレイ中の場合は "Replay" を表示する
    if is_replay:
        text += " Replay"
    ax.text(0, -0.2, text, fontsize=7*self.size)

    self.draw_board(ax, self.mb, lw=0.7*self.size)

    self.update_widgets_status()

    if hasattr(self, "mbtree_gui"):
        from tree import Node

        self.mbtree_gui.selectednode = Node(self.mb, depth=self.mb.move_count)
        self.mbtree_gui.update_gui()
        
Marubatsu_GUI.update_gui = update_gui
```
</details>

<details><summary><strong>修正箇所</strong></summary>

```diff_python
def update_gui(self):
元と同じなので省略
    # ゲームの決着がついていない場合は、手番を表示する
    if self.mb.status == Marubatsu.PLAYING:
        text = "Turn " + self.mb.turn
+       if self.show_status:
+           score = self.score_table[self.mb.board_to_str()]["score"]
+           text += " "
+           if score > 0:
+               text += "〇"
+           elif score == 0:
+               text += "△"
+           else:
+               text += "×"
元と同じなので省略
        
Marubatsu_GUI.update_gui = update_gui
```
</details>

## Marubastu_GUI クラスの `update_widgets_status` メソッドの修正

最後に、__`show_status`__ 属性の値に応じて __「状況」ボタンの表示の色を変更する__ ために、下記のプログラムのように __`update_widgets_status`__ の 5 行目を追加します。

```python
1  def update_widgets_status(self):
2      self.inttext.disabled = not self.checkbox.value
3      self.set_button_color(self.show_tree_button, self.show_subtree)    
4      self.set_button_status(self.reset_tree_button, not self.show_subtree)    
5      self.set_button_color(self.show_status_button, self.show_status)    
元と同じなので省略
6    
7  Marubatsu_GUI.update_widgets_status = update_widgets_status
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
def update_widgets_status(self):
    self.inttext.disabled = not self.checkbox.value
    self.set_button_color(self.show_tree_button, self.show_subtree)    
    self.set_button_status(self.reset_tree_button, not self.show_subtree)    
    self.set_button_color(self.show_status_button, self.show_status)    
    self.set_button_status(self.undo_button, self.mb.move_count < 2 or self.mb.move_count != len(self.mb.records) - 1)
    self.set_button_status(self.first_button, self.mb.move_count <= 0)
    self.set_button_status(self.prev_button, self.mb.move_count <= 0)
    self.set_button_status(self.next_button, self.mb.move_count >= len(self.mb.records) - 1)
    self.set_button_status(self.last_button, self.mb.move_count >= len(self.mb.records) - 1)    
    # value 属性よりも先に max 属性に値を代入する必要がある点に注意！
    self.slider.max = len(self.mb.records) - 1
    self.slider.value = self.mb.move_count
    
Marubatsu_GUI.update_widgets_status = update_widgets_status
```
</details>

<details><summary><strong>修正箇所</strong></summary>

```diff_python
def update_widgets_status(self):
    self.inttext.disabled = not self.checkbox.value
    self.set_button_color(self.show_tree_button, self.show_subtree)    
    self.set_button_status(self.reset_tree_button, not self.show_subtree)    
+   self.set_button_color(self.show_status_button, self.show_status)    
元と同じなので省略
    
Marubatsu_GUI.update_widgets_status = update_widgets_status
```
</details>

上記の修正後に下記のプログラムを実行すると、初期設定では「状況」ボタンが OFF になっているので実行結果の左図のように局面の状況は表示されません。

次に「状況」ボタンをクリックすると、実行結果の真ん中の図のように手番を表す文字の右に __局面の状況を表す文字列が表示される__ ようになります。実行結果の右図は __〇 の必勝の局面の場合__ の図です。表示がわかりづらいと思った方は、自由に変更して下さい。

```python
from util import gui_play

gui_play()
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/f0e61722-3a7c-b783-1a1d-cd74afba6280.png" width="150">　<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/74bb8984-159a-735d-f01f-7d6e8c0e58ee.png" width="150">　<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/8a1e6240-b1d0-3b50-39d8-c1fe5fe7a6c2.png" width="150">　

# 着手を行った場合の局面の状況の表示

現状では、それぞれの合法手に __着手を行った局面の状況__ は、下部の GUI の部分木の選択された局面の __子ノードの局面の色を見ればわかる__ ようになっていますが、直観的に __わかりやすいとは言えない__ でしょう。そこで、__ゲーム盤のマス__ に着手を行った場合の __局面の状況を直接表示__ するという改良を行うことにします。

本記事では、その __表示の切り替え__ を、先程の __「状況」ボタンで行う__ ことにします。「状況」ボタンとは別のボタンを配置して表示の切り替えを別々に行いたい人は、そのように実装してみて下さい。

## 着手を行った場合の局面の状況の表示方法の検討

まず、ゲーム盤のマスに __着手を行った場合の局面の状況__ を __どのように表示するかを決める__ 必要があります。本記事ではゲーム盤の __マスの中の左上__ に、そのマスに __着手した場合の局面の状況__ を、先程と同様の 〇、△、× の一文字を __小さく表示する__ ことにします。左上に小さく表示するのは、マスに __配置されたマークと区別できるようにするため__ です。

また、別の工夫として __最善手であるかどうかを区別できる__ ように、__最善手の場合__ は __文字を赤く表示__ することにします。その際に、__最短の勝利を優先__ して計算された局面と最善手・評価値の対応表を利用すると、__一部の最善手が赤く表示されなくなる__ ので、局面と最善手・評価値の対応表は、先程のプログラムで __`score_table`__ 属性に代入した __bestmoves_and_score_by_board.dat__ のファイルのデータを利用することにします。

## 着手を行った場合の局面の状況を表示する処理の記述場所

新しいボタンを配置しないことにしたので、__`__init__`__ メソッドや __`create_widgets`__ メソッド __などの修正を行う必要はありません__。修正する必要があるのは __ゲーム盤の表示を行う処理__ で、その処理は __`update_gui`__ メソッドの下記のプログラムで行っています。

```python
def update_gui(self):
略
        self.draw_board(ax, self.mb, lw=0.7*self.size)
略
```

着手を行った場合の局面の状況を __表示する処理__ は、以下の 2 種類の方法で実装することができます。

* `draw_board` メソッドの中に記述する
* `update_gui` メソッドの中で、上記の `draw_board` メソッドを呼び出した後に記述する

どちらの方法で実装しても構いませんが、`draw_board` メソッドの中に記述すると、`draw_board` に仮引数を追加するなどの修正が少々面倒なので、本記事では後者の方法で実装することにします。

## Marubatsu_GUI クラスの `update_gui` メソッドの修正

着手を行った場合の局面の状況を表示する処理は、下記の手順行います。

`show_status` 属性が `True` の場合に __それぞれの合法手__ に対して下記の処理を行う。

1. 合法手を着手した場合の __局面の状況を計算__ する
2. __文字の色__ を合法手が最善手の場合は赤、そうでない場合は黒とする
3. 合法手のマスの左上に、手順 1 で計算した局面の状況を、手順 2 で計算した文字の色で表示する

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

* __6 行目__：`show_status` 属性が `True` の場合に処理を行うようにする
* __7 行目__：局面と最善手・評価値の対応表から、現在の局面の最善手の一覧を計算して `bestmoves` に代入する
* __8 行目__：現在の局面のそれぞれの合法手に対する繰り返しの処理を行う
* __9、10 行目__：現在の局面を表す `self.mb` に対する深いコピーを計算して `mb` に代入し、`mb` に対して合法手の着手を行う
* __11 行目__：局面と最善手・評価値の対応表から、合法手を着手した局面の評価値を計算する
* __12 行目__：合法手が最善手であるかどうかによって、表示する文字の色を計算する
* __13 ~ 18 行目__：評価値から局面の状況を表す文字列を計算する
* __19 行目__：合法手のマスの左上に、上記で計算した局面の状況を表す文字を表示する。表示する文字の __座標__ と、__文字の大きさ__ は __試行錯誤して決めた__ ものである。また、__文字の大きさ__ は[前回の記事](https://qiita.com/ysgeso/items/2c4dcf4a73f08750811f#%E6%96%87%E5%AD%97%E3%81%AE%E8%A1%A8%E7%A4%BA%E3%81%AE%E5%A4%A7%E3%81%8D%E3%81%95%E3%81%AE%E4%BF%AE%E6%AD%A3)で説明したように、ゲーム盤の大きさを表す __`self.size` に比例した値にする必要がある__ 点に注意する事

```python
 1  from copy import deepcopy
 2
 3  def update_gui(self):
元と同じなので省略
 4      self.draw_board(ax, self.mb, lw=0.7*self.size)
 5   
 6      if self.show_status:
 7          bestmoves = self.score_table[self.mb.board_to_str()]["bestmoves"]
 8          for x, y in self.mb.calc_legal_moves():
 9              mb = deepcopy(self.mb)
10              mb.move(x, y)
11              score = self.score_table[mb.board_to_str()]["score"]
12              color = "red" if (x, y) in bestmoves else "black"
13              if score > 0:
14                  text = "〇"
15              elif score == 0:
16                  text = "△"
17              else:
18                  text = "×"            
19              ax.text(x + 0.1, y + 0.35, text, fontsize=5*self.size, c=color)
元と同じなので省略
20        
21  Marubatsu_GUI.update_gui = update_gui
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
from copy import deepcopy

def update_gui(self):
    ax = self.ax
    ai = self.mb.ai

    # Axes の内容をクリアして、これまでの描画内容を削除する
    ax.clear()

    # y 軸を反転させる
    ax.invert_yaxis()

    # 枠と目盛りを表示しないようにする
    ax.axis("off")   

    # リプレイ中、ゲームの決着がついていた場合は背景色を変更する
    is_replay =  self.mb.move_count < len(self.mb.records) - 1 
    if self.mb.status == Marubatsu.PLAYING:
        facecolor = "lightcyan" if is_replay else "white"
    else:
        facecolor = "lightyellow"

    ax.figure.set_facecolor(facecolor)
        
    # 上部のメッセージを描画する
    # 対戦カードの文字列を計算する
    ax.text(1.5, 3.5, f"{self.dropdown_list[0].label}　VS　{self.dropdown_list[1].label}", 
            fontsize=7*self.size, ha="center")   

    # ゲームの決着がついていない場合は、手番を表示する
    if self.mb.status == Marubatsu.PLAYING:
        text = "Turn " + self.mb.turn
        score = self.score_table[self.mb.board_to_str()]["score"]
        if self.show_status:
            text += " "
            if score > 0:
                text += "〇"
            elif score == 0:
                text += "△"
            else:
                text += "×"
    # 引き分けの場合
    elif self.mb.status == Marubatsu.DRAW:
        text = "Draw game"
    # 決着がついていれば勝者を表示する
    else:
        text = "Winner " + self.mb.status
    # リプレイ中の場合は "Replay" を表示する
    if is_replay:
        text += " Replay"
    ax.text(0, -0.2, text, fontsize=7*self.size)

    self.draw_board(ax, self.mb, lw=0.7*self.size)
    
    if self.show_status:
        bestmoves = self.score_table[self.mb.board_to_str()]["bestmoves"]
        for x, y in self.mb.calc_legal_moves():
            mb = deepcopy(self.mb)
            mb.move(x, y)
            score = self.score_table[mb.board_to_str()]["score"]
            color = "red" if (x, y) in bestmoves else "black"
            if score > 0:
                text = "〇"
            elif score == 0:
                text = "△"
            else:
                text = "×"            
            ax.text(x + 0.1, y + 0.35, text, fontsize=5*self.size, c=color)

    self.update_widgets_status()

    if hasattr(self, "mbtree_gui"):
        from tree import Node

        self.mbtree_gui.selectednode = Node(self.mb, depth=self.mb.move_count)
        self.mbtree_gui.update_gui()
        
Marubatsu_GUI.update_gui = update_gui
```
</details>

<details><summary><strong>修正箇所</strong></summary>

```diff_python
from copy import deepcopy

def update_gui(self):
元と同じなので省略
    self.draw_board(ax, self.mb, lw=0.7*self.size)
    
+   if self.show_status:
+       bestmoves = self.score_table[self.mb.board_to_str()]["bestmoves"]
+       for x, y in self.mb.calc_legal_moves():
+           mb = deepcopy(self.mb)
+           mb.move(x, y)
+           score = self.score_table[mb.board_to_str()]["score"]
+           color = "red" if (x, y) in bestmoves else "black"
+           if score > 0:
+               text = "〇"
+           elif score == 0:
+               text = "△"
+           else:
+               text = "×"            
+           ax.text(x + 0.1, y + 0.35, text, fontsize=5*self.size, c=color)
元と同じなので省略
        
Marubatsu_GUI.update_gui = update_gui
```
</details>

上記の修正後に下記のプログラムを実行し、「状況」ボタンをクリックすると、実行結果のように __空いているマス__ に __局面の状況を表す文字が表示される__ ようになります。

ゲーム開始時の局面はすべての合法手が最善手で、どのマスに着手を行っても引き分けの局面になるので、左図のようにすべてのマスに赤い △ が表示されます。

右図の (1, 1) に着手した局面は、四隅の合法手が最善手で引き分けの局面になるので赤い △ が、辺の合法手に着手すると 〇 の必勝の局面になるので黒い 〇 が表示されます。

他にも様々な着手を行い、下部の GUI の部分木の表示と比較して正しい表示が行われることを確認してみて下さい。

```python
gui_play()
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/2e33d670-074a-4c76-c0a6-a666c050fc1b.png" width="150">　<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/f49f9c7d-a31e-b2d9-70e1-6dbdb0681359.png" width="150">

## `update_gui` メソッドの修正

上記のプログラムでは、`update_gui` の中で、評価値を表す `score` から局面の状況を表す文字列を計算するという、__ほとんど同じ内容の処理__ を __2 箇所で記述している__ という問題点があるので、修正することにします。

具体的には、__下記のような関数__ を __`update_gui` メソッドの中にローカル関数として定義__ し、その関数を呼び出すようにします。なお、`calc_status_txt` をローカル関数として定義したのは、この関数を `update_gui` 以外で利用することがなさそうだったからです。他の場所から利用する可能性がある場合はメソッドとして定義したほうが良いでしょう。

__名前__：局面の状況を表す文字列を計算するので `calc_status_txt` とする
__処理__：評価値から局面の状況を表す文字列を計算する
__入力__：仮引数 `score` に評価値を代入する
__出力__：局面の状況を表す文字列を返り値として返す

下記は、そのように `update_gui` を修正したプログラムです。なお、10 行目の下に記述されていた `ai = self.mb.ai` は、ローカル変数 `ai` が `update_gui` の中で使われていないことに気づいたので削除しました。

* __2 ~ 8 行目__：上記の `calc_status_txt` を `update_gui` のローカル関数として定義する
* __16、24 行目__：`calc_status_txt` を利用して局面の状況を表す文字列を計算するように修正する

```python
 1  def update_gui(self):
 2      def calc_status_txt(score):
 3          if score > 0:
 4              return "〇"
 5          elif score == 0:
 6              return "△"
 7          else:
 8              return "×"
 9   
10      ax = self.ax
元と同じなので省略
11      # ゲームの決着がついていない場合は、手番を表示する
12      if self.mb.status == Marubatsu.PLAYING:
13          text = "Turn " + self.mb.turn
14          score = self.score_table[self.mb.board_to_str()]["score"]
15          if self.show_status:
16              text += " " + calc_status_txt(score)
元と同じなので省略            
17      if self.show_status:
18          bestmoves = self.score_table[self.mb.board_to_str()]["bestmoves"]
19          for x, y in self.mb.calc_legal_moves():
20              mb = deepcopy(self.mb)
21              mb.move(x, y)
22              score = self.score_table[mb.board_to_str()]["score"]
23              color = "red" if (x, y) in bestmoves else "black"
24              text = calc_status_txt(score)
25              ax.text(x + 0.1, y + 0.35, text, fontsize=5*self.size, c=color)
元と同じなので省略
        
Marubatsu_GUI.update_gui = update_gui
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
def update_gui(self):
    def calc_status_txt(score):
        if score > 0:
            return "〇"
        elif score == 0:
            return "△"
        else:
            return "×"
    
    ax = self.ax

    # Axes の内容をクリアして、これまでの描画内容を削除する
    ax.clear()

    # y 軸を反転させる
    ax.invert_yaxis()

    # 枠と目盛りを表示しないようにする
    ax.axis("off")   

    # リプレイ中、ゲームの決着がついていた場合は背景色を変更する
    is_replay =  self.mb.move_count < len(self.mb.records) - 1 
    if self.mb.status == Marubatsu.PLAYING:
        facecolor = "lightcyan" if is_replay else "white"
    else:
        facecolor = "lightyellow"

    ax.figure.set_facecolor(facecolor)
        
    # 上部のメッセージを描画する
    # 対戦カードの文字列を計算する
    ax.text(1.5, 3.5, f"{self.dropdown_list[0].label}　VS　{self.dropdown_list[1].label}", 
            fontsize=7*self.size, ha="center")   

    # ゲームの決着がついていない場合は、手番を表示する
    if self.mb.status == Marubatsu.PLAYING:
        text = "Turn " + self.mb.turn
        score = self.score_table[self.mb.board_to_str()]["score"]
        if self.show_status:
            text += " " + calc_status_txt(score)
    # 引き分けの場合
    elif self.mb.status == Marubatsu.DRAW:
        text = "Draw game"
    # 決着がついていれば勝者を表示する
    else:
        text = "Winner " + self.mb.status
    # リプレイ中の場合は "Replay" を表示する
    if is_replay:
        text += " Replay"
    ax.text(0, -0.2, text, fontsize=7*self.size)

    self.draw_board(ax, self.mb, lw=0.7*self.size)
    
    if self.show_status:
        bestmoves = self.score_table[self.mb.board_to_str()]["bestmoves"]
        for x, y in self.mb.calc_legal_moves():
            mb = deepcopy(self.mb)
            mb.move(x, y)
            score = self.score_table[mb.board_to_str()]["score"]
            color = "red" if (x, y) in bestmoves else "black"
            text = calc_status_txt(score)
            ax.text(x + 0.1, y + 0.35, text, fontsize=5*self.size, c=color)

    self.update_widgets_status()

    if hasattr(self, "mbtree_gui"):
        from tree import Node

        self.mbtree_gui.selectednode = Node(self.mb, depth=self.mb.move_count)
        self.mbtree_gui.update_gui()
        
Marubatsu_GUI.update_gui = update_gui
```
</details>

<details><summary><strong>修正箇所</strong></summary>

```diff_python
def update_gui(self):
+   def calc_status_txt(score):
+       if score > 0:
+           return "〇"
+       elif score == 0:
+           return "△"
+       else:
+           return "×"
    
    ax = self.ax
-   ai = self.mb.ai    
元と同じなので省略
    # ゲームの決着がついていない場合は、手番を表示する
    if self.mb.status == Marubatsu.PLAYING:
        text = "Turn " + self.mb.turn
        score = self.score_table[self.mb.board_to_str()]["score"]
        if self.show_status:
-           text += " "
-           if score > 0:
-               text += "〇"
-           elif score == 0:
-               text += "△"
-           else:
-               text += "×"
+           text += " " + calc_status_txt(score)
元と同じなので省略            
    if self.show_status:
        bestmoves = self.score_table[self.mb.board_to_str()]["bestmoves"]
        for x, y in self.mb.calc_legal_moves():
            mb = deepcopy(self.mb)
            mb.move(x, y)
            score = self.score_table[mb.board_to_str()]["score"]
            color = "red" if (x, y) in bestmoves else "black"
-           if score > 0:
-               text = "〇"
-           elif score == 0:
-               text = "△"
-           else:
-               text = "×" 
+           text = calc_status_txt(score)
            ax.text(x + 0.1, y + 0.35, text, fontsize=5*self.size, c=color)
元と同じなので省略
        
Marubatsu_GUI.update_gui = update_gui
```
</details>

実行結果は先ほどと同じなので省略しますが、下記のプログラムを実行して様々な操作を行った際に、正しい表示が行われることを確認して下さい。

```python
gui_play()
```

# 今回の記事のまとめ

今回の記事では、「状況」ボタンを作成し、現在の局面の状況と、それぞれの合法手を着手した際の局面の状況を表示できるように修正しました。次回の記事では、現在の局面に対するそれぞれの AI が計算した最善手と評価値を表示する処理を実装します。

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

| リンク | 説明 |
|:--|:--|
| [marubatsu.ipynb](https://github.com/ysgeso/marubatsu/blob/master/125/marubatsu.ipynb)  | 本記事で入力して実行した JupyterLab のファイル |
| [marubatsu_new.py](https://github.com/ysgeso/marubatsu/blob/master/125/marubatsu_new.py)  | 今回の記事で更新した marubatsu.py |

# 次回の記事

https://qiita.com/ysgeso/items/2c76474ff139a824f4fd

| 更新日時 | 更新内容 |
|:--|:--|
| 2024/11/16 | `create_widgets` の最後に余計な処理が記述されていたので削除しました |

[^1]:本記事ではデフォルト値を `False` としましたが、`True` にしたい人は自由に変更して下さい。