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?

More than 1 year has passed since last update.

Pythonで〇×ゲームのAIを一から作成する その73 重複する処理の統合

0
Last updated at Posted at 2024-04-18

目次と前回の記事

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

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

ルールベースの AI の一覧

ルールベースの AI の一覧については、下記の記事を参照して下さい。

処理の統合

前回の記事で、リセットボタン を 実装 しましたが、play、on_button_clicked、on_mouse_down の 中 で 似たような処理 が 記述 されているので、統合 することにします。ただし、3 つの関数 の 処理 は 似てはいます が、同じプログラム で 記述されていない ので、統合するため には 3 つの関数 で行われる 処理を検証 する 必要 が あります。

play メソッドと on_button_clicked の処理の統合

3 つの関数 の 統合 を 一度に行う のは 大変 なので、最初に play メソッドと on_button_clicked の 処理 の 統合 を行うことにします。

似たような処理 を 統合 するためには、具体的 な 共通点 を 調べる必要 があるので、play メソッドと on_button_clicked が 行う処理 を 整理 することにします。

play メソッドが行う処理

play メソッドが行う 処理 を 箇条書き にすると 以下 のようになります。

  1. 乱数の種 に 関する処理 を行う
  2. ゲーム を リセット する
  3. Figure に 関する処理 を行う
  4. リセットボタン に 関する処理 を行う
  5. 繰り返し処理 によって 決着がつくまで の間 以下の処理 を行う
    1. ゲーム盤の画像 を 描画 する
    2. 現在の手番 が AI の場合 は AI の着手 を 選択 する
    3. 現在の手番 が 人間 の場合 は、関数の処理 を 終了 して イベントループ を 再開 する
  6. 繰り返しの処理 の 終了後 に ゲーム盤の画像 を 描画 する

言葉の説明だけ では わかりづらい ので、フローチャート で 説明 します。

play メソッドのフローチャートの整理

下図 は、以前の記事で紹介した play メソッドの フローチャート です。

GUI で 〇×ゲーム を 遊ぶ場合 は、gui に True が 代入 されているので、その場合 の フローチャート は 下図 のようになります。なお、背景が 水色 の 繰り返し処理 の 中 の、「AI の手番?」の 条件分岐 は、その 右上 の「人間の手番?」が no(False)の 場合 に 実行される ので、必ず yes(True)になる点に 注意 して下さい。

上図 から、「gui が True」の 条件分岐 と、実行されない部分 を 削除 して 整理 すると、下図 のような フローチャート になります。なお、以前の記事 の フローチャート では、乱数の種 に関する 処理 が 省略 されていたので、下図 には その処理 を 追加 しました。また、以前の記事 でフローチャートを示した 時点 では、まだ リセットボタン に関する 処理 が 実装されていなかった ので、下図 には リセットボタン に関する 処理 を 追加 しました。

上図のフローチャートと、先程示した play メソッドが行う 処理 を 見比べて みて下さい。

  1. 乱数の種 に 関する処理 を行う
  2. ゲーム を リセット する
  3. Figure に 関する処理 を行う
  4. リセットボタン に 関する処理 を行う
  5. 繰り返し処理 によって 決着がつくまで の間 以下の処理 を行う
    1. ゲーム盤の画像 を 描画 する
    2. 現在の手番 が AI の場合 は AI の着手 を 選択 する
    3. 現在の手番 が 人間 の場合 は、関数の処理 を 終了 して イベントループ を 再開 する
  6. 繰り返しの処理 の 終了後 に ゲーム盤の画像 を 描画 する

on_mouse_clicked が行う処理

下記は、on_mouse_clicked の定義です。

        # リセットボタンのイベントハンドラを定義する
        def on_button_clicked(b):
            self.restart()       
            self.draw_board(ax)  
            
            while self.status == Marubatsu.PLAYING:
                # 現在の手番を表す ai のインデックスを計算する
                index = 0 if self.turn == Marubatsu.CIRCLE else 1
                # ai が着手を行うかどうかを判定する
                if ai[index] is not None:                
                    x, y = ai[index](self, **params[index])
                    self.move(x, y) 
                    self.draw_board(ax)                               
                else:
                    # 人間の手番の場合は、イベントハンドラを終了する
                    return

上記の on_mouse_clicked 行う 処理 を 箇条書き にすると 以下 のようになります。

  1. ゲーム を リセット する
  2. ゲーム盤の画像 を 描画 する
  3. 繰り返し処理 によって 決着がつくまで の間 以下の処理 を行う
    1. 現在の手番 が AI の場合 は AI の着手 を 選択 し、ゲーム盤の画像 を 描画 する
    2. 現在の手番 が 人間 の場合 は、関数の処理 を 終了 して イベントループ を 再開 する

前回の記事で示したように、上記 の処理の フローチャート は 下図 のようになります。

play メソッドと on_button_clicked の比較

下記は、play メソッドと on_button_clicked の 処理の手順 です。それぞれを 見比べて、どこ が 共通 し、どこが 異なるか について少し考えてみて下さい。

play メソッドの処理の手順

  1. 乱数の種 に 関する処理 を行う
  2. ゲーム を リセット する
  3. Figure に 関する処理 を行う
  4. リセットボタン に 関する処理 を行う
  5. 繰り返し処理 によって 決着がつくまで の間 以下の処理 を行う
    1. ゲーム盤の画像 を 描画 する
    2. 現在の手番 が AI の場合 は AI の着手 を 選択 する
    3. 現在の手番 が 人間 の場合 は、関数の処理 を 終了 して イベントループ を 再開 する
  6. 繰り返しの処理 の 終了後 に ゲーム盤の画像 を 描画 する

on_button_clicked の処理の手順

  1. ゲーム を リセット する
  2. ゲーム盤の画像 を 描画 する
  3. 繰り返し処理 によって 決着がつくまで の間 以下の処理 を行う
    1. 現在の手番 が AI の場合 は AI の着手 を 選択 し、ゲーム盤の画像 を 描画 する
    2. 現在の手番 が 人間 の場合 は、関数の処理 を 終了 して イベントループ を 再開 する

フローチャートの比較

わかりづらい と思った方は、下図の、play メソッドと on_button_clicked の フローチャート を横に並べて 比較 してみると良いでしょう。行われる処理 が 良く似ている ことが わかる のではないかと思います。なお、play メソッドの 処理の説明文 の 一部 は、on_button_clicked の 表記 に 合わせました。

処理の共通点と違い

上記から、それぞれの処理 には 以下 の 共通点 があることが わかります。

  • ゲーム盤 を 最初 に リセット する
  • 決着がつくまで、繰り返し処理 を行う
  • 繰り返し 処理の 中 で、AI の手番 の場合は AI が 着手を行う
  • 繰り返し 処理の 中 で、人間の手番 の場合は 関数の処理 を 終了 する

また、以下 の 違い があることが わかります。ただし、GUI で 〇×ゲーム を 遊ぶ場合 は、play メソッドの 返り値 は 利用しない ので、返り値の違い は 重要 では ありません。

  • play メソッドは、乱数の種 の 処理 と、Figure と リセットボタン に関する 処理を行う
  • 画像 を 描画 する タイミング が 異なる
  • play メソッドは 繰り返し処理 の 終了時 に 返り値 として 勝敗結果 を返す

上記の「画像 を 描画 する タイミング が 異なる」という 違い から、一見すると 両者は 異なる処理 を行っているように 見えるかもしれません が、画像の描画 に関しては、play メソッドと on_button_clicked は 全く同じ処理 を行います。そのことを示します。

画像の描画の処理の検証

〇×ゲーム で ゲーム盤の描画 を 行う必要 が 生じる のは、下記 の 場合 です。

  • ゲーム を 開始した時
  • 着手 を 行った時

また、GUI で 〇×ゲーム を 遊ぶ 際に、人間 が 着手 を行った場合は、on_mouse_down の イベントハンドラ内 で ゲーム盤の描画 を 行います。そのため、play メソッドと on_button_clicked では、AI の着手 に対してのみ 画像の描画 が 行われます。

play メソッドと on_button_clicked は、上記 の処理を、下記 の タイミング で 行います。なお、下記の「最後の着手」とは、ゲーム の 決着がつく着手 の事を 表します。

play on_button_clicked
ゲームを開始した時 最初 の 繰り返し 処理 の 直後 繰り返し 処理の 直前
最後以外の AI の着手 次 の 繰り返し 処理の 直後 着手 を行った 直後
最後の AI の着手 繰り返し 処理の 終了後 着手 を行った 直後

わかりづらいと思いますので、フローチャート で 説明 します。

ゲームを開始した時の画像の描画のタイミング

下図 は ゲーム を 開始した時 の 画像の描画 が行われる タイミング を表します。赤線 が 画像 が 描画されるまで に行われた 処理、黄色の処理 が 画像 の 描画処理 を表します。

ゲーム開始時 の時点では 決着 は ついていない ので、いずれの場合 も、必ずゲーム盤 の 描画 に関しては 同じ処理 が 行われる ことが 確認 できます。

AI が最初に着手を行った時の画像の描画のタイミング

下図は、上記の後 で AI が 着手した場合 に 画像の描画 が行われる タイミング を表します。水色の線 は、AI が 着手を選択 した 回 の 次 の 繰り返し 処理で 行われた処理 であることを表します。なお、AI の着手 は、最後の着手ではない ものとします。

図 からわかるように、play メソッドと on_button_clicked では、下記 のように 画像 を 描画 する タイミング が 異なります が、いずれ も 着手 を 行った後 で、次 の「AI の手番?」の 判定 を 行う前 に ゲーム盤の描画 が 行われる ことが わかります。

play on_button_clicked
次 の 繰り返し 処理の 直後 その回 の 繰り返し 処理で 着手 を行った 直後

AI が 2 手目以降に着手を行った時の画像の描画のタイミング

下図は、AI が 2 手目以降 に 着手した場合 に 画像の描画 が行われる タイミング を表します。緑の線 は、AI が 着手を選択 した 回 の 前 の 繰り返し 処理で 行われた処理 であることを表します。なお、AI の着手 は、最後の着手ではない ものとします。

play メソッドの 図 は、先程の図 と 全く同じ です。また、on_button_clicked も、AI が 着手 を 選択した直後 に ゲーム盤の画像 を 描画 している点では 先程と同じ です。従って、先程と同様に、play メソッドと on_button_clicked では、下記 のタイミングで 画像 を 描画 し、いずれ も 着手 を 行った後 で、次 の「AI の手番?」の 判定 を 行う前 に ゲーム盤の描画 が 行われる ことが わかります。

play on_button_clicked
次 の 繰り返し 処理の 直後 その回 の 繰り返し 処理で 着手 を行った 直後

人間の手番が回ってきた場合の処理

下図は、人間の手番 が 回ってきた場合 に行われる 処理 です。人間の手番 が 1 手目 の場合と 2 手目以降 の場合では、処理の流れ が 若干異なり ますが、本質的 には 変わらない ので、2 手目以降 に 着手した場合 に 画像の描画 が行われる タイミング を示します。

いずれの場合 も ゲーム盤の描画 を 行わず に 関数の処理 が 終了 することが わかります。

AI が最後の着手を行った時の画像の描画のタイミング

下図は、AI が 最後の着手 を 行った場合 に 画像の描画 が行われる タイミング を表します。今回 の図は、画像の描画 が 行われた後 で、関数 の 処理が終了するまで の 流れ を 示しました。線の色 は、緑、赤、青 の 順 で 処理が行われる ことを 表します。

図 から、play メソッドは、繰り返し の 処理 の 終了後 に ゲーム盤の画像 を 描画 しますが、on_button_clicked では、これまでと同様 に、着手 を 行った直後 に 描画 します。いずれの場合 も 関数の処理 が 終了するまで の間に ゲーム盤 を 描画 することが わかります。

play on_button_clicked
繰り返し 処理が 終了 した 直後 その回 の 繰り返し 処理で 着手 を行った 直後

まとめ

上記 から、いずれの場合 でも、play メソッドと on_button_clicked は ゲームの開始時 と、AI が 着手を行った後 に ゲーム盤 の 画像を描画する という、同じ処理 を 行う ことが 確認 できました。下記は、両者 が 同じ処理を行う ことを 言葉で説明 したものです。

  • play メソッドは、繰り返し 処理の 中 で、着手 を 行う前 に 画像を描画 する。そのため、ゲーム開始時 の 描画処理 を 繰り返し処理 の中で 行う ことが できる。一方、最後 の AI の着手 に対する 描画処理 は 繰り返し 処理の 中 で 行えない ので、繰り返し 処理の 終了後 に その処理 を 行う必要 がある
  • on_button_clicked は、繰り返し 処理の 中 で、着手 を行った 直後 に 画像を描画 する。そのため、着手 に対する 描画処理 を 繰り返し処理 の中で 行う ことが できる。一方、ゲーム開始時 の 描画処理 は 繰り返し 処理の 中 で 行えない ので、繰り返し 処理の 前 に その処理 を 行う必要 がある

play_common の定義

上記の考察 から、play メソッド と on_button_clicked が 行う処理 の 違い は、play メソッドが 繰り返し 処理の 前 に 行う「乱数の種 に 関する処理」と「Figure と リセットボタン に 関する処理」だけ である事が わかりました。

そこで、両者 で 共通する処理 を行う メソッドを定義 することにします。その メソッドの名前 は、共通 するという 意味 を表す common という 英単語 を使って、play_common という 名前にする ことにします。

どちらの処理を play_common に記述すべきか

次に、play_common に 記述 する プログラム を、play メソッドと on_button_clicked の どちら の プログラム から 抜き出し て 記述 するかを 考える必要 があります。上記 で示したように、どちらも 同じ処理を行う のだから、どちらでも良い のではないかと 思う人 が いるかもしれません が、そうではありません。その理由について少し考えてみて下さい。

play メソッドは、gui に False が 代入 されて 実行 される 場合 が あります。一方、on_button_clicked は、gui に True が 代入 された場合に イベントハンドラ として 登録 されるため、gui に True が 代入 されている場合 のみ実行 されます。そのため、play メソッドと on_button_clicked の 両方 から play_common メソッドを呼び出して 利用する場合 は、play メソッドの 内容を記述 しなければ、play メソッドで gui に False が 代入 されている場合の処理に 対応できない という 問題が発生 します。

そこで、play_common を、play メソッドから 「乱数の種 に 関する処理」と「Figure と リセットボタン に 関する処理」以外 の 処理 を 抜き出した、下記 のプログラムのように 定義 します。play_common の 仮引数 は、とりあえず play メソッドと 同じものにする ことにしました。その 理由 は この後 で 説明 します。

from marubatsu import Marubatsu
import matplotlib.pyplot as plt
import japanize_matplotlib
import ipywidgets as widgets
import math

def play_common(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
    # 〇×ゲームを再起動する
    self.restart()

    # ゲームの決着がついていない間繰り返す
    while self.status == Marubatsu.PLAYING:
        # 現在の手番を表す ai のインデックスを計算する
        index = 0 if self.turn == Marubatsu.CIRCLE else 1
        # ゲーム盤の表示
        if verbose:
            if gui:
                # AI どうしの対戦の場合は画面を描画しない
                if ai[0] is None or ai[1] is None:
                    self.draw_board(ax)
                # 手番を人間が担当する場合は、play メソッドを終了する
                if ai[index] is None:
                    return
            else:
                print(self)
                
        # ai が着手を行うかどうかを判定する
        if ai[index] is not None:
            x, y = ai[index](self, **params[index])
        else:
            # キーボードからの座標の入力
            coord = input("x,y の形式で座標を入力して下さい。exit を入力すると終了します")
            # "exit" が入力されていればメッセージを表示して関数を終了する
            if coord == "exit":
                print("ゲームを終了します")
                return       
            # x 座標と y 座標を要素として持つ list を計算する
            xylist = coord.split(",")
            # xylist の要素の数が 2 ではない場合
            if len(xylist) != 2:
                # エラーメッセージを表示する
                print("x, y の形式ではありません")
                # 残りの while 文のブロックを実行せずに、次の繰り返し処理を行う
                continue
            x, y = xylist
        # (x, y) に着手を行う
        try:
            self.move(int(x), int(y))
        except:
            print("整数の座標を入力して下さい")

    # 決着がついたので、ゲーム盤を表示する
    if verbose:
        if gui:
            self.draw_board(ax)
        else:
            print(self)
            
    return self.status
    
Marubatsu.play_common = play_common

play_common の不必要な仮引数の削除

上記 のプログラムを VSCode に 記述 すると、関数の定義 が、下図 のように 表示 されます。VSCode では、仮引数 が その関数 の ブロックの中 で一度も 利用されていない 場合は、薄い色の文字 で 表示 されます。下図 では、seed と size が 薄い色で表示 されます。

従って、play_common の 仮引数 seed と size は 必要がない ことが わかります。実際 に、VSCode で Ctrl + f を押して表示される 検索機能 を使って、seed と size が play_common の ブロックの中 で一度も 記述されていない ことを 確認 して下さい。

下記 のプログラムは、仮引数 seed と size を 削除 した play_common の 定義 です。

def play_common(self, ai, params=[{}, {}], verbose=True, gui=False):
元と同じなので省略
    
Marubatsu.play_common = play_common
プログラム全体
def play_common(self, ai, params=[{}, {}], verbose=True, gui=False):
    # 〇×ゲームを再起動する
    self.restart()

    # ゲームの決着がついていない間繰り返す
    while self.status == Marubatsu.PLAYING:
        # 現在の手番を表す ai のインデックスを計算する
        index = 0 if self.turn == Marubatsu.CIRCLE else 1
        # ゲーム盤の表示
        if verbose:
            if gui:
                # AI どうしの対戦の場合は画面を描画しない
                if ai[0] is None or ai[1] is None:
                    self.draw_board(ax)
                # 手番を人間が担当する場合は、play メソッドを終了する
                if ai[index] is None:
                    return
            else:
                print(self)
                
        # ai が着手を行うかどうかを判定する
        if ai[index] is not None:
            x, y = ai[index](self, **params[index])
        else:
            # キーボードからの座標の入力
            coord = input("x,y の形式で座標を入力して下さい。exit を入力すると終了します")
            # "exit" が入力されていればメッセージを表示して関数を終了する
            if coord == "exit":
                print("ゲームを終了します")
                return       
            # x 座標と y 座標を要素として持つ list を計算する
            xylist = coord.split(",")
            # xylist の要素の数が 2 ではない場合
            if len(xylist) != 2:
                # エラーメッセージを表示する
                print("x, y の形式ではありません")
                # 残りの while 文のブロックを実行せずに、次の繰り返し処理を行う
                continue
            x, y = xylist
        # (x, y) に着手を行う
        try:
            self.move(int(x), int(y))
        except:
            print("整数の座標を入力して下さい")

    # 決着がついたので、ゲーム盤を表示する
    if verbose:
        if gui:
            self.draw_board(ax)
        else:
            print(self)
            
    return self.status
    
Marubatsu.play_common = play_common
修正箇所
-def play_common(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
+def play_common(self, ai, params=[{}, {}], verbose=True, gui=False):
元と同じなので省略

Marubatsu.play_common = play_common

play メソッドと on_button_clicked の修正

次に、下記 のプログラムのように、play メソッドと on_button_clicked から、共通する処理 を 削除 し、代わり に play_common を 呼び出す ように 修正 します。

  • 6 行目:on_button_clicked に、play_common メソッドを 呼び出す処理のみ を 記述 する
  • 10 行目:ゲーム の 決着がついていない間 繰り返す 処理を削除 し、return 文 で、play_common メソッドの 返り値 を 返す処理 を 記述する

play メソッドの場合は、勝敗結果 を 返す 必要がある1ので、return 文 で play_common の 返り値 を 返す必要がある 点に 注意 して下さい。イベントハンドラ は 返り値 を 返す必要がない ので、on_button_clicked では、return 文 を 記述する必要 は ありません。

 1  def play(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
元と同じなので省略
 2      # リセットボタンのイベントハンドラを定義する
 3      def on_button_clicked(b):
 4          # この下に記述されていた処理を削除する
 5   
 6          self.play_common(ai=ai, params=params, verbose=verbose, gui=gui)           
元と同じなので省略         
 7
 8    # この下に記述されていた、ゲームの決着がついていない間繰り返す処理以降を削除する
 9  
10      return self.play_common(ai=ai, params=params, verbose=verbose, gui=gui)
11    
12  Marubatsu.play = play
行番号のないプログラム
def play(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
    # seed が None でない場合は、seed を乱数の種として設定する
    if seed is not None:
        random.seed(seed)
    
    # リセットボタンを配置する
    button = widgets.Button(description="リセット")
    display(button)
    
    # リセットボタンのイベントハンドラを定義する
    def on_button_clicked(b):
        self.play_common(ai=ai, params=params, verbose=verbose, gui=gui)           
         
    # イベントハンドラをリセットボタンに結びつける
    button.on_click(on_button_clicked)

    # gui が True の場合に、ゲーム盤を描画する画像を作成し、イベントハンドラに結びつける
    if gui:
        fig, ax = plt.subplots(figsize=[size, size])
        fig.canvas.toolbar_visible = False
        fig.canvas.header_visible = False
        fig.canvas.footer_visible = False
        fig.canvas.resizable = False          
        
        # ローカル関数としてイベントハンドラを定義する
        def on_mouse_down(event):
            # Axes の上でマウスを押していた場合のみ処理を行う
            if event.inaxes and self.status == Marubatsu.PLAYING:
                x = math.floor(event.xdata)
                y = math.floor(event.ydata)
                self.move(x, y)                
                self.draw_board(ax)

                # 現在の手番を表す ai のインデックスを計算する
                index = 0 if self.turn == Marubatsu.CIRCLE else 1
                # ゲームの決着がついていない場合に、ai が着手を行うかどうかを判定する
                if self.status == Marubatsu.PLAYING and ai[index] is not None:                
                    x, y = ai[index](self, **params[index])
                    self.move(x, y) 
                    self.draw_board(ax)
                   
        # fig の画像にマウスを押した際のイベントハンドラを結び付ける
        fig.canvas.mpl_connect("button_press_event", on_mouse_down)     

    return self.play_common(ai=ai, params=params, verbose=verbose, gui=gui)
    
Marubatsu.play = play
修正箇所
def play(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
元と同じなので省略
    # リセットボタンのイベントハンドラを定義する
    def on_button_clicked(b):
-       self.restart()       
-       self.draw_board(ax)  
        
        # 現在の手番を表す ai のインデックスを計算する
-       index = 0 if self.turn == Marubatsu.CIRCLE else 1
        # ゲームの決着がついていない場合に、ai が着手を行うかどうかを判定する
-       if self.status == Marubatsu.PLAYING and ai[index] is not None:                
-           x, y = ai[index](self, **params[index])
-           self.move(x, y) 
-           self.draw_board(ax)
+       self.play_common(ai=ai, params=params, verbose=verbose, gui=gui)           
元と同じなので省略         

    # ゲームの決着がついていない間繰り返す
-   while self.status == Marubatsu.PLAYING:
-   非常に長いのでこの間の部分は省略  
-   return self.status   
+   return self.play_common(ai=ai, params=params, verbose=verbose, gui=gui)
    
Marubatsu.play = play

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

%matplotlib widget
from ai import ai2
mb = Marubatsu()

mb.play(ai=[ai2, ai2], gui=True);

実行結果(下記のエラーメッセージの下に表示される画像は省略します)

略
Cell In[3], line 45
     42     # fig の画像にマウスを押した際のイベントハンドラを結び付ける
     43     fig.canvas.mpl_connect("button_press_event", on_mouse_down)     
---> 45 return self.play_common(ai=ai, params=params, verbose=verbose, gui=gui)

Cell In[2], line 49
     47 if verbose:
     48     if gui:
---> 49         self.draw_board(ax)
     50     else:
     51         print(self)

NameError: name 'ax' is not defined

エラーの検証と修正

エラーメッセージ から、ax が 定義されていない(not defined)ことが わかります。また、エラーが発生 している self.draw_board(ax) の 行 は、---> 45 return self.play_common(ai=ai, params=params, verbose=verbose, gui=gui) という メッセージ から、play_common の中 で 実行 されていることが わかります。

従って、このエラー は、play_common の中 で、ax が 定義されていない ことが 原因 です。

上記 の 名前が定義されていない という エラー は、以前の記事で説明したように、下図 のように、VSCode の play_common の ブロック内 のプログラムの ax の部分に オレンジ色 の 波線 が 表示 されることから 気づく ことも できます。

そこで、下記 のプログラムのように、play_common に 仮引数 ax を 追加 します。なお、通常の仮引数 は、デフォルト引数より前 に 記述 する 必要がある 点に 注意 して下さい。

def play_common(self, ai, ax, params=[{}, {}], verbose=True, gui=False):
元と同じなので省略
    
Marubatsu.play_common = play_common
プログラム全体
def play_common(self, ai, ax, params=[{}, {}], verbose=True, gui=False):
    # 〇×ゲームを再起動する
    self.restart()

    # ゲームの決着がついていない間繰り返す
    while self.status == Marubatsu.PLAYING:
        # 現在の手番を表す ai のインデックスを計算する
        index = 0 if self.turn == Marubatsu.CIRCLE else 1
        # ゲーム盤の表示
        if verbose:
            if gui:
                # AI どうしの対戦の場合は画面を描画しない
                if ai[0] is None or ai[1] is None:
                    self.draw_board(ax)
                # 手番を人間が担当する場合は、play メソッドを終了する
                if ai[index] is None:
                    return
            else:
                print(self)
                
        # ai が着手を行うかどうかを判定する
        if ai[index] is not None:
            x, y = ai[index](self, **params[index])
        else:
            # キーボードからの座標の入力
            coord = input("x,y の形式で座標を入力して下さい。exit を入力すると終了します")
            # "exit" が入力されていればメッセージを表示して関数を終了する
            if coord == "exit":
                print("ゲームを終了します")
                return       
            # x 座標と y 座標を要素として持つ list を計算する
            xylist = coord.split(",")
            # xylist の要素の数が 2 ではない場合
            if len(xylist) != 2:
                # エラーメッセージを表示する
                print("x, y の形式ではありません")
                # 残りの while 文のブロックを実行せずに、次の繰り返し処理を行う
                continue
            x, y = xylist
        # (x, y) に着手を行う
        try:
            self.move(int(x), int(y))
        except:
            print("整数の座標を入力して下さい")

    # 決着がついたので、ゲーム盤を表示する
    if verbose:
        if gui:
            self.draw_board(ax)
        else:
            print(self)
            
    return self.status
    
Marubatsu.play_common = play_common
修正箇所
-def play_common(self, ai, params=[{}, {}], verbose=True, gui=False):
+def play_common(self, ai, ax, params=[{}, {}], verbose=True, gui=False):
元と同じなので省略
    
Marubatsu.play_common = play_common

次に、下記 のプログラムの 4、7 行目 のように、play メソッドと on_button_clicked 内で play_common を 呼び出す際 に、実引数 に ax=ax を 記述 するように 修正 します。

1  def play(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
元と同じなので省略
2      # リセットボタンのイベントハンドラを定義する
3      def on_button_clicked(b):
4          self.play_common(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)           
5         
元と同じなので省略 
6
7      return self.play_common(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
8    
9  Marubatsu.play = play
行番号のないプログラム
def play(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
    # seed が None でない場合は、seed を乱数の種として設定する
    if seed is not None:
        random.seed(seed)
    
    # リセットボタンを配置する
    button = widgets.Button(description="リセット")
    display(button)
    
    # リセットボタンのイベントハンドラを定義する
    def on_button_clicked(b):
        self.play_common(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)           
         
    # イベントハンドラをリセットボタンに結びつける
    button.on_click(on_button_clicked)

    # gui が True の場合に、ゲーム盤を描画する画像を作成し、イベントハンドラに結びつける
    if gui:
        fig, ax = plt.subplots(figsize=[size, size])
        fig.canvas.toolbar_visible = False
        fig.canvas.header_visible = False
        fig.canvas.footer_visible = False
        fig.canvas.resizable = False          
        
        # ローカル関数としてイベントハンドラを定義する
        def on_mouse_down(event):
            # Axes の上でマウスを押していた場合のみ処理を行う
            if event.inaxes and self.status == Marubatsu.PLAYING:
                x = math.floor(event.xdata)
                y = math.floor(event.ydata)
                self.move(x, y)                
                self.draw_board(ax)

                # 現在の手番を表す ai のインデックスを計算する
                index = 0 if self.turn == Marubatsu.CIRCLE else 1
                # ゲームの決着がついていない場合に、ai が着手を行うかどうかを判定する
                if self.status == Marubatsu.PLAYING and ai[index] is not None:                
                    x, y = ai[index](self, **params[index])
                    self.move(x, y) 
                    self.draw_board(ax)
                   
        # fig の画像にマウスを押した際のイベントハンドラを結び付ける
        fig.canvas.mpl_connect("button_press_event", on_mouse_down)     

    return self.play_common(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
    
Marubatsu.play = play
修正箇所
def play(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
元と同じなので省略
    # リセットボタンのイベントハンドラを定義する
    def on_button_clicked(b):
-       self.play_common(ai=ai, params=params, verbose=verbose, gui=gui)           
+       self.play_common(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)           
         
元と同じなので省略 

-   return self.play_common(ai=ai, params=params, verbose=verbose, gui=gui)
+   return self.play_common(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
    
Marubatsu.play = play

上記 の 修正後 に、下記 のプログラムで play メソッドを 実行 し、正しく プログラムが 動作する ことを 確認 して下さい。また、人間 VS 人間、AI VS 人間、人間 VS AI でも 正しく プログラムが 動作する ことを 確認 して下さい。

mb.play(ai=[ai2, ai2], gui=True);

play メソッドと on_mouse_down の処理の統合

次に、play メソッドと on_mouse_down の 処理 を 統合 することにします。

play メソッドと on_mouse_down の処理の共通点

on_mouse_down が 行う処理 を 箇条書き にすると 以下 のようになります。

  1. マウス が 押されたマス に 着手 を行う
  2. 着手後の ゲーム盤 の 画像を描画 する
  3. ゲーム中 で 手番 が AI の場合 は、AI の着手 を行い、画像を描画 する

そのように思えないかもしれませんが、上記 の 手順 2、3 の 処理 は、下記 の play メソッドの 手順 5、6 で 置き換える ことが できます。その理由について少し考えてみて下さい。

  1. 乱数の種 に 関する処理 を行う
  2. ゲーム を リセット する
  3. Figure に 関する処理 を行う
  4. リセットボタン に 関する処理 を行う
  5. 繰り返し処理 によって 決着がつくまで の間 以下の処理 を行う
    1. ゲーム盤の画像 を 描画 する
    2. 現在の手番 が AI の場合 は AI の着手 を 選択 する
    3. 現在の手番 が 人間 の場合 は、関数の処理 を 終了 して イベントループ を 再開 する
  6. 繰り返しの処理 の 終了後 に ゲーム盤の画像 を 描画 する

下図左 は on_mouse_down の、下図右 は on_mouse_down の 手順 2、3 を play メソッドの 手順 5、6 で 置き換え た フローチャート です。緑 の部分が on_mouse_down の、水色 の部分が play メソッドの 手順 であることを表します。このフローチャート を使って、両者 が 同じ処理を行う ことを 示す ことにします。

on_mouse_down が 実行された ということは、〇 か × の いずれか を 人間が担当 しているということなので、AI VS AI 以外 の 組み合わせ の対戦で 行われる処理 を 検証 します。

人間 VS 人間 の場合

on_mouse_down の 手順 1 の 後 は、以下 の 2 種類の状況 が 考えられる ので、それぞれ の 処理の流れ が 同じ であることを 示します。

  • 手順 1 で行われた 着手 によって 決着 が つく
  • 手順 1 で行われた 着手 によって 決着 が つかない

下図 は、on_mouse_down の 手順 1 によって、ゲーム の 決着 が ついた場合 の 処理 を表す フローチャート です。図から、いずれの場合 も 手順 1 によって行われた 人間 の 着手後 の ゲーム盤の描画 を行い、処理が終了 します。

下図 は、on_mouse_down の 手順 1 によって、ゲーム の 決着 が ついていない場合 の 処理 を表す フローチャート です。下図左 で行われる 処理 は 上記 と 同じ です。下図右 で行われる 処理 は、処理の流れ は 変わっています が、人間 の 着手後 の ゲーム盤の描画 を行い、処理が終了する という点では両者は 同じ処理 を 行います。

上記 から、人間 VS 人間 の場合は、両者は 同じ処理を行う ことが 確認 できました。

人間 VS AI または、AI VS 人間 の場合

人間 VS AI と AI VS 人間 の いずれの場合 も、on_mouse_down の 手順 1 は 人間の着手の処理 である点に 変わりはありません。そのため、両者 の 処理の手順 を まとめて説明します。

on_mouse_down の 手順 1 の 後 は、以下 の 3 種類の状況 が 考えられる ので、それぞれ の 処理の流れ が 同じ であることを 示します。

  • 手順 1 で行われた 着手 によって 決着 が つく
  • 手順 1 で行われた 着手 によって 決着 が つかない
    • 次 の AI の着手 によって 決着 が つく
    • 次 の AI の着手 によって 決着 が つかない

手順 1 で行われた 着手 によって 決着 が つく 場合に 行われる処理 は、人間 VS 人間 の場合と 全く同じ なので説明は 省略 します。

下図 は、on_mouse_down の 手順 1 によって、ゲーム の 決着 が つかず、その 次 の AI の着手 によって 決着 が ついた場合 の 処理 を表す フローチャート です。線の色 は、赤、水色 の 順 で 処理が行われる ことを 表します。

図から、いずれの場合 も 下記 の処理が 行われる ことが わかります。

  • 人間 の 着手後 の ゲーム盤の描画 が 行われる
  • AI が 着手 を行い、ゲーム盤の描画 が 行われる

下図 は、on_mouse_down の 手順 1 によって、ゲーム の 決着 が つかず、その 次 の AI の着手 によって 決着 が つかなかった場合 の 処理 を表す フローチャート です。下図左 で行われる 処理 は 上記 と 同じ です。下図右 で行われる 処理 は、処理の流れ は 変わっています が、下記の処理 が 行われる という点では、両者は 同じ処理 を 行います。なお、下図右 の 黄色 の ゲーム盤を描画する 処理は、人間 の 着手後 と AI の 着手後 で 2 度実行 されます。

  • 人間 の 着手後 の ゲーム盤の描画 が 行われる
  • AI が 着手 を行い、ゲーム盤の描画 が 行われる

上記 から、人間 VS AI と AI VS 人間 の場合も、同じ処理を行う ことが 確認 できました。

play_loop の定義

上記から、on_mouse_down の 手順 2、3 と play メソッドの 手順 5、6 は 同じ処理 を 行う ことが 確認 できました。そこで、play_common の場合と 同様 に、共通する処理 である play メソッドの 手順 5、6 の処理を 抜き出したメソッド を 定義 する事にします。

play メソッドの 手順 5 の 処理 は、繰り返しの処理 なので、メソッド の 名前 を play_loop と 名付ける ことにし、下記 のプログラムのように 定義 します。

def play_loop(self, ai, ax, params=[{}, {}], verbose=True, gui=False):
    # ゲームの決着がついていない間繰り返す
    while self.status == Marubatsu.PLAYING:
        # 現在の手番を表す ai のインデックスを計算する
        index = 0 if self.turn == Marubatsu.CIRCLE else 1
        # ゲーム盤の表示
        if verbose:
            if gui:
                # AI どうしの対戦の場合は画面を描画しない
                if ai[0] is None or ai[1] is None:
                    self.draw_board(ax)
                # 手番を人間が担当する場合は、play メソッドを終了する
                if ai[index] is None:
                    return
            else:
                print(self)
                
        # ai が着手を行うかどうかを判定する
        if ai[index] is not None:
            x, y = ai[index](self, **params[index])
        else:
            # キーボードからの座標の入力
            coord = input("x,y の形式で座標を入力して下さい。exit を入力すると終了します")
            # "exit" が入力されていればメッセージを表示して関数を終了する
            if coord == "exit":
                print("ゲームを終了します")
                return       
            # x 座標と y 座標を要素として持つ list を計算する
            xylist = coord.split(",")
            # xylist の要素の数が 2 ではない場合
            if len(xylist) != 2:
                # エラーメッセージを表示する
                print("x, y の形式ではありません")
                # 残りの while 文のブロックを実行せずに、次の繰り返し処理を行う
                continue
            x, y = xylist
        # (x, y) に着手を行う
        try:
            self.move(int(x), int(y))
        except:
            print("整数の座標を入力して下さい")

    # 決着がついたので、ゲーム盤を表示する
    if verbose:
        if gui:
            self.draw_board(ax)
        else:
            print(self)
            
    return self.status   
    
Marubatsu.play_loop = play_loop
play_commonとの違い
-def play_common(self, ai, ax, params=[{}, {}], verbose=True, gui=False):
+def play_loop(self, ai, ax, params=[{}, {}], verbose=True, gui=False):
    # 〇×ゲームを再起動する
-   self.restart()
元と同じなので省略
    
Marubatsu.play_loop = play_loop

on_mouse_down の修正

次に、on_mouse_down の、マウス を 押したマス に 着手を行った後 の 処理 を、下記 のプログラムのように、play_loop を 呼び出す プログラムに 修正 します。

  • 12 行目:AI が 着手を行う処理 を 削除 し、play_loop を 呼び出す 処理を 記述 する
 1  def play(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
元と同じなので省略
 2          # ローカル関数としてイベントハンドラを定義する
 3          def on_mouse_down(event):
 4              # Axes の上でマウスを押していた場合のみ処理を行う
 5              if event.inaxes and self.status == Marubatsu.PLAYING:
 6                  x = math.floor(event.xdata)
 7                  y = math.floor(event.ydata)
 8                  self.move(x, y)                
 9                  self.draw_board(ax)
10
11                  # 次の手番の処理を行うメソッドを呼び出す
12                  self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
13                   
元と同じなので省略
14
15  Marubatsu.play = play
行番号のないプログラム
def play(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
    # seed が None でない場合は、seed を乱数の種として設定する
    if seed is not None:
        random.seed(seed)
    
    # リセットボタンを配置する
    button = widgets.Button(description="リセット")
    display(button)
    
    # リセットボタンのイベントハンドラを定義する
    def on_button_clicked(b):
        self.play_common(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)           
         
    # イベントハンドラをリセットボタンに結びつける
    button.on_click(on_button_clicked)

    # gui が True の場合に、ゲーム盤を描画する画像を作成し、イベントハンドラに結びつける
    if gui:
        fig, ax = plt.subplots(figsize=[size, size])
        fig.canvas.toolbar_visible = False
        fig.canvas.header_visible = False
        fig.canvas.footer_visible = False
        fig.canvas.resizable = False          
        
        # ローカル関数としてイベントハンドラを定義する
        def on_mouse_down(event):
            # Axes の上でマウスを押していた場合のみ処理を行う
            if event.inaxes and self.status == Marubatsu.PLAYING:
                x = math.floor(event.xdata)
                y = math.floor(event.ydata)
                self.move(x, y)                
                self.draw_board(ax)

                # 次の手番の処理を行うメソッドを呼び出す
                self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
                   
        # fig の画像にマウスを押した際のイベントハンドラを結び付ける
        fig.canvas.mpl_connect("button_press_event", on_mouse_down)     

    return self.play_common(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
    
Marubatsu.play = play
修正箇所
def play(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
元と同じなので省略
        # ローカル関数としてイベントハンドラを定義する
        def on_mouse_down(event):
            # Axes の上でマウスを押していた場合のみ処理を行う
            if event.inaxes and self.status == Marubatsu.PLAYING:
                x = math.floor(event.xdata)
                y = math.floor(event.ydata)
                self.move(x, y)                
                self.draw_board(ax)

                # 現在の手番を表す ai のインデックスを計算する
-               index = 0 if self.turn == Marubatsu.CIRCLE else 1
                # ゲームの決着がついていない場合に、ai が着手を行うかどうかを判定する
-               if self.status == Marubatsu.PLAYING and ai[index] is not None:                
-                   x, y = ai[index](self, **params[index])
-                   self.move(x, y) 
-                   self.draw_board(ax)                   
                # 次の手番の処理を行うメソッドを呼び出す
+               self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
                   
元と同じなので省略

Marubatsu.play = play

上記 の 修正後 に、下記 のプログラムで play メソッドを 実行 し、正しく プログラムが 動作する ことを 確認 して下さい。また、人間 VS 人間、AI VS 人間、人間 VS AI でも 正しく プログラムが 動作する ことを 確認 して下さい。

mb.play(ai=[ai2, ai2], gui=True);

play_common と play_loop の統合

ここまでで、play、on_button_clicked、on_mouse_down の 処理 の 統合 を 行うため に、play_loop と play_common の 2 種類 の メソッド を 定義 しましたが、この 2 つのメソッド で行われる 処理の違い は下記の点だけなので、統合 したほうが良いでしょう。

  • 最初 に self.restart() を 実行 して ゲーム を リセットするか どうか

従って、play_common の中で、play メソッドの 手順 5、6 の処理を 下記 のプログラムの 6 行目 のように play_loop を 呼び出す ように 修正 することが できます。

 1  def play_common(self, ai, ax, params=[{}, {}], verbose=True, gui=False):
 2      # 〇×ゲームを再起動する
 3      self.restart()
 4
 5      # 決着がつくまでの繰り返し処理を行うメソッドを呼び出す
 6      return self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
 7
 8  Marubatsu.play_common = play_common
行番号のないプログラム
def play_common(self, ai, ax, params=[{}, {}], verbose=True, gui=False):
    # 〇×ゲームを再起動する
    self.restart()

    # 決着がつくまでの繰り返し処理を行うメソッドを呼び出す
    return self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)

Marubatsu.play_common = play_common
修正箇所
def play_common(self, ai, ax, params=[{}, {}], verbose=True, gui=False):
    # 〇×ゲームを再起動する
    self.restart()

    # 決着がつくまでの繰り返し処理を行うメソッドを呼び出す
+   self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
-   # ゲームの決着がついていない間繰り返す
-   while self.status == Marubatsu.PLAYING:
-   以下非常に長いので省略します

    # 決着がついたので、ゲーム盤を表示する
    if verbose:
        if gui:
            self.draw_board(ax)
        else:
            print(self)
            
    return self.status
    
Marubatsu.play_common = play_common

上記 の 修正後 に、下記 のプログラムで play メソッドを 実行 し、正しく プログラムが 動作する ことを 確認 して下さい。また、人間 VS 人間、AI VS 人間、人間 VS AI でも 正しく プログラムが 動作する ことを 確認 して下さい。

mb.play(ai=[ai2, ai2], gui=True);

play_common の削除

play_common が 行う処理 は、self.restart と play_loop を 呼び出す という 処理だけ なので、わざわざ メソッドとして定義 する 必要はない と 思う人 が いるかもしれません。

そのように感じた人は、下記 のプログラムの 5 ~ 7、9 ~ 11 行目 のように、play メソッドと on_button_clicked 内で play_common を 呼び出す処理 を、self.restart と play_loop を呼び出す処理に 修正 すると良いでしょう。

 1  def play(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
元と同じなので省略
 2      # リセットボタンのイベントハンドラを定義する
 3      def on_button_clicked(b):
 4          # ゲームをリセットする
 5          self.restart()
 6          # 決着がつくまで繰り返す処理を呼び出す
 7          self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)           
元と同じなので省略
 8      # ゲームをリセットする
 9      self.restart()
10      # 決着がつくまで繰り返す処理を呼び出す
11      return self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
12    
13  Marubatsu.play = play
行番号のないプログラム
def play(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
    # seed が None でない場合は、seed を乱数の種として設定する
    if seed is not None:
        random.seed(seed)
    
    # リセットボタンを配置する
    button = widgets.Button(description="リセット")
    display(button)
    
    # リセットボタンのイベントハンドラを定義する
    def on_button_clicked(b):
        # ゲームをリセットする
        self.restart()
        # 決着がつくまで繰り返す処理を呼び出す
        self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)           
         
    # イベントハンドラをリセットボタンに結びつける
    button.on_click(on_button_clicked)

    # gui が True の場合に、ゲーム盤を描画する画像を作成し、イベントハンドラに結びつける
    if gui:
        fig, ax = plt.subplots(figsize=[size, size])
        fig.canvas.toolbar_visible = False
        fig.canvas.header_visible = False
        fig.canvas.footer_visible = False
        fig.canvas.resizable = False          
        
        # ローカル関数としてイベントハンドラを定義する
        def on_mouse_down(event):
            # Axes の上でマウスを押していた場合のみ処理を行う
            if event.inaxes and self.status == Marubatsu.PLAYING:
                x = math.floor(event.xdata)
                y = math.floor(event.ydata)
                self.move(x, y)                
                self.draw_board(ax)

                # 次の手番の処理を行うメソッドを呼び出す
                self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
                   
        # fig の画像にマウスを押した際のイベントハンドラを結び付ける
        fig.canvas.mpl_connect("button_press_event", on_mouse_down)     

    # ゲームをリセットする
    self.restart()
    # 決着がつくまで繰り返す処理を呼び出す
    return self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
    
Marubatsu.play = play
修正箇所
def play(self, ai, params=[{}, {}], verbose=True, seed=None, gui=False, size=3):
元と同じなので省略
    # リセットボタンのイベントハンドラを定義する
    def on_button_clicked(b):
        # ゲームをリセットする
+       self.restart()
        # 決着がつくまで繰り返す処理を呼び出す
-       self.play_common(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)           
+       self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)           
元と同じなので省略
    # ゲームをリセットする
+   self.restart()
    # 決着がつくまで繰り返す処理を呼び出す
-   return self.play_common(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
+   return self.play_loop(ai=ai, ax=ax, params=params, verbose=verbose, gui=gui)
    
Marubatsu.play = play

本記事 では 上記 のプログラムを 採用 しますが、修正前 と 修正後 のプログラムで、どちらが良いか については 一概には言えません。わかりやすい と 思ったほう を 採用して下さい。なお、play_common はもう 利用しない ので、marubatsu.py には 記述しません。

上記 の 修正後 に、下記 のプログラムで play メソッドを 実行 し、正しく プログラムが 動作する ことを 確認 して下さい。また、人間 VS 人間、AI VS 人間、人間 VS AI でも 正しく プログラムが 動作する ことを 確認 して下さい。

mb.play(ai=[ai2, ai2], gui=True);

今回の記事のまとめ

今回の記事では、play、on_button_clicked、on_mouse_down の 処理 の 統合 を行いました。このような 処理の統合 を 行わなくても、リセットボタン の 処理を記述 することが できます が、統合したほう が、プログラム を 簡潔に記述できる ようになります。統合前 と 統合後 のプログラムを 見比べて、そのことを 確認 してみて下さい。

また、同じ処理 をプログラムの 複数の個所 に 記述する ことは、避けたほうが良い ので、このような 統合ができる場合 は、しておいたほうが良い でしょう。

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

以下のリンクから、本記事で入力して実行した JupyterLab のファイルを見ることができます。

以下のリンクは、今回の記事で更新した marubatsu.py です。

次回の記事

  1. play メソッドの 返り値 は、ai_match を使って 通算成績 を 計算 する際などで 必要 になります ↩

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?