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を一から作成する その47 評価値を利用するアルゴリズム

Last updated at Posted at 2024-01-21

目次と前回の記事

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

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

これまでに作成した AI

これまでに作成した AI の アルゴリズム は以下の通りです。

関数名 アルゴリズム
ai1 左上から順空いているマス を探し、最初に見つかったマス着手 する
ai2 ランダム なマスに 着手 する
ai3 真ん中 のマスに 優先的着手 する
既に 埋まっていた場合ランダム なマスに 着手 する
ai4 真ん中 のマスの 優先的着手 する
既に 埋まっていた場合ランダム なマスに 着手 する
ai5 勝てる場合勝つ
そうでない場合は ランダム なマスに 着手 する
ai6 勝てる場合勝つ
そうでない場合は 相手の勝利阻止 する
そうでない場合は ランダム なマスに 着手 する
ai7 真ん中 のマスに 優先的着手 する
そうでない場合は 勝てる場合勝つ
そうでない場合は 相手の勝利阻止 する
そうでない場合は ランダム なマスに 着手 する

基準となる ai2 との 対戦結果(単位は %)は以下の通りです。太字ai2 VS ai2 よりも 成績が良い 数値を表します。欠陥 の列は、アルゴリズム欠陥 があるため、ai2 との 対戦成績良くても強い とは 限らない ことを表します。欠陥の詳細については、関数名のリンク先の説明を見て下さい。

関数名 o 勝 o 負 o 分 x 勝 x 負 x 分 欠陥
ai1 78.1 17.5 4.4 44.7 51.6 3.8 61.4 34.5 4.1 あり
ai2 58.7 28.8 12.6 29.1 58.6 12.3 43.9 43.7 12.5
ai3 69.3 19.2 11.5 38.9 47.6 13.5 54.1 33.4 12.5
ai4 83.0 9.5 7.4 57.2 33.0 9.7 70.1 21.3 8.6 あり
ai5 81.2 12.3 6.5 51.8 39.8 8.4 66.5 26.0 7.4
ai6 88.9 2.2 8.9 70.3 6.2 23.5 79.6 4.2 16.2
ai7 95.8 0.2 4.0 82.3 2.4 15.3 89.0 1.3 9.7

これまでのアルゴリズムの欠点

これまで作成 してきた AI は、ルール 7 のように、ルールに 複数の条件 が存在する場合は、「それぞれの条件順番に判定 し、最初 にみつかった 条件を満たす着手選択 する」という アルゴリズム実装 してきましたが、この アルゴリズム には 欠点 があります。

また、詳細は次回以降の記事で説明しますが、その 欠点 があるため、これまでに作成した最強の AI である ai7 より強い AI を、このアルゴリズム作成 することは 困難 です。

そこで、今回の記事では、このアルゴリズム欠点説明 し、その 欠点を解消 できる、評価値を利用する という アルゴリズム説明 します。また、これまでに実装 してきた AI を、評価値を利用 する アルゴリズム実装する方法 について 説明 します。

条件を判定する順番に関する問題点

これまでアルゴリズム では、ルール の中に 複数条件がある 場合は、条件優先順位 に従って、判定の順番正しく記述 する必要があります。例えば、前回の記事で説明したように、ルール 6 のアルゴリズムの 手順入れ替える と、間違った処理 が行われます。

この点に関しては、この後で説明する、評価値を利用 する アルゴリズム の場合でも、評価値 をルールに従って 正しく計算 する 必要がある ので、大きく変わるわけではありません。

問題は、何らかの理由条件優先順位変える場合 で、判定 を行うプログラムの 記述の順番 を正しく 入れ替える 必要があるため、プログラムの 修正が大変 です。

次回の記事で説明しますが、評価値を利用 する アルゴリズム では、条件の 優先順位変える 際に、評価値計算式修正 するという手法をとるため、処理の 記述の順番入れ替える 必要は ありません。そのため、プログラムの 修正比較的簡単 に行えます。

条件を満たす着手が複数ある場合の問題点

ルール 5 の、「勝てる場合勝つ」という条件は、下記の局面の 2 つ の青いマスのように、複数の合法手 がその 条件を満たす 場合があります。

上記の局面に対し、ai5 は、最初に見つかった 1 のマスを 必ず選択 してしまいます。もちろん、1 のマスを 選択 すれば 〇 が勝利 するので、強い AI を作成 するという 意味 では全く 問題ありません が、人間にとって は、1 と 2ランダムに選択 する AI のほうが 面白味が高い のではないでしょうか。しかし、最初に見つかった合法手選択 するという アルゴリズム では、そのような ランダム性 のある 着手 を行うことは できません

結果が TrueFalse 以外の条件を考慮したい場合の問題点

これまでルール条件 は、いずれも「真ん中のマスに着手したかどうか」のように、結果True か、Falseいずれか になるものばかりでした。

それに対し、例えば 参考書を買う 時に、「ページ数最も多い 本を 選ぶ」のような、数値の大きさ比較 するような 条件ルール は、すべての参考書調べなければ どれが最も小さいかを 判定 することは できない ので、これまでの、「最初に見つかったもの選択 する」という アルゴリズム利用できません

複数の条件を組み合わせて考慮したい場合の問題点

これまで の 〇×ゲームの ルール では、この 問題は起きない ので、別の例説明 します。

お店で ヨーグルトを買う 時に、「値段」、「容量」、「賞味期限」の 3 つ判断基準 にして どの商品を買うか選択 する場合の事を考えてみて下さい。

例えば、それぞれについて、下記 のような 判断基準設定 することにします。

  1. 値段200 円以下
  2. 容量200 g 以上
  3. 賞味期限3 日後以降

それぞれの条件順番に判定 し、最初 にみつかった 条件を満たす商品選択 する」場合は、上記の 判断基準優先順位設定 し、その 順番で判定 を行うことになります。

例えば、下記の表の 3 種類商品 に対して、上記の 123優先順位商品を選択 する場合、最初値段が 200 円以下 であることが 判明 した 商品 B選択 されます。

値段 容量 賞味期限 条件 1 条件 2 条件 3 条件を満たす数
商品 A 230 300 5 2
商品 B 180 150 1 1
商品 C 160 120 2 1

しかし、この方法 では、商品 B より後 の商品は チェックされない ので、商品 B より 安い商品 C選択されない という 問題 があります。

他のルール として、上記の 判断基準最も多く満たす 商品を 選択する というルールが考えられます。例えば、上記の場合、商品 C最も多く判断基準を満たす ので 商品 C選択 します。しかし、最も多く判断基準 を満たす商品を 見つけるため には、すべての商品調べる必要 があるので、「それぞれの条件順番に判定 し、最初 にみつかった 条件を満たす商品選択 する」方法で このルール作成 することは できません

評価値を利用するアルゴリズム

上記の問題解決 する方法に、下記の 評価値を利用 する アルゴリズム があります。手順 2 から、このアルゴリズムで、ランダム性 のある 選択を行う ことが できます

以下の手順で、複数のもの の中 から一つを選択 する。

  1. それぞれもの に対して 決められた計算 を行うことで、点数 をつける。その 点数 の事を 評価値呼ぶ
  2. 最も高い評価値 がつけられた ものを選択 する。最高点評価値 がつけられたものが 複数ある 場合は、その中から ランダムに 一つを 選択 する

ランダム性重要でない 場合は、手順 2 で、最も高い評価値 がつけられたものが 複数ある場合 は、最初にみつかったもの選択 するアルゴリズムもあります。

最も優秀 なものを 評価値を利用 する アルゴリズム選択 することは、現実の世界 でも、下記の表 のように 良く行われています

評価値
テスト 点数
マラソン ゴールするまでに要した時間
野球などのスポーツ 得点

先程の ページ数最も多い 参考書を選択する例では、参考書の ページ数評価値 です。

先程の ヨーグルト選択 する例では、条件満たす数評価値 で、下記の場合は 最も評価値が高い商品 A選択 されることになります。

値段 容量 賞味期限 条件 1 条件 2 条件 3 条件を満たす数
商品 A 230 300 5 2
商品 B 180 150 1 1
商品 C 160 120 2 1

このように、評価値を利用 することで、数値の大きさ条件 とする 選択 や、複数の条件組み合わせ考慮 する 選択 を行うことが できる ようになります。

また、評価値を利用 することで、先ほど説明した「条件を判定 する 順番 に関する 問題点」を 改善 することができます。具体例については次回の記事で説明します。

評価値を利用した 〇× ゲームの AI の記述

評価値を利用 した アルゴリズム による 〇× ゲーム の AI は、下記のプログラムのように、現在の局面 に対して それぞれの合法手着手 した 局面の評価値計算 し、その中で 最も評価値が高くなる合法手 から 選択 するという 処理を記述 します。なお、コメントの部分は、この後で具体的なプログラムを記述します。

現在の局面 に対して、それぞれの合法手着手 する 処理 は、ルール 5 の AI で行う 処理と同じ なので、2 ~ 5 行目 の処理の 記述 は、ai5 のプログラムと 同様 です。

 1  def ai(mb_orig):
 2      legal_moves = mb_orig.calc_legal_moves()
 3      for move in legal_moves:
 4          mb = deepcopy(mb_orig)
 5          x, y = move
 6          mb.move(x, y)
 7          # mb の局面の評価値を計算する
 8          # 最も高い評価値が得られる合法手の一覧を計算する
 9
10      # 最も高い評価値が得られる合法手の一覧の中からランダムに着手を選択する

評価値を利用した ルール 2 の AI の実装

これまでに作成 してきた AI は、いずれも評価値を利用 した アルゴリズム実装 することが できます。最初に、下記の ルール 2AI を、評価値を利用 した アルゴリズム実装 する 方法 を紹介します。なお、ルール 1 より先に ルール 2 を実装するのは、ルール 2 のほうが 簡単に実装できる からです。ルール 1 の実装方法については次回の記事で紹介します。

  • ランダム なマスに 着手 する

評価値を利用 する アルゴリズム で、ルール 2実装 する場合は、現在の局面 に対して 合法手着手 した すべて局面評価値同じ値 にします。その 理由 は、評価値を利用 する アルゴリズム では、先程説明したように、「最高点評価値 がつけられたものが 複数ある場合 は、その中 から ランダムに 一つを 選択 する」という処理を行うからです。

関数の名前について

評価値 の事を、英語score表記 するので、その 頭文字 をとって、評価値利用 して ルール x の処理を行う 関数の名前aixs のように 命名 することにします。例えば、これから実装する ルール 2 の処理を行う 関数 は、ai2s という 名前定義 します。

ai2s は、先程のプログラムの コメント順番に埋めていく という手順で 実装 します。

評価値の計算処理の記述

下記は、評価値利用 した アルゴリズムルール 2 の処理を行う 関数 ai2s定義 するプログラムです。下記のプログラムは、先程の 評価値利用 した プログラム の、7 行目 の 「mb の局面の評価値を計算する」 の コメントの処理 を 11 行目に 記述 しています。

すべての局面 に対して 同じ評価値計算 すれば良いので、その 評価値 には どのような数値設定 しても かまいません が、おそらく 0最も分かりやすい のではないかと思いますので、本記事では 0 を設定 することにしました。また、計算した 評価値代入 する 変数の名前score にしました。

なお、後で利用 するので 2 行目choiceインポート しています。

 1  from copy import deepcopy
 2  from random import choice
 3
 4  def ai2s(mb_orig):
 5      legal_moves = mb_orig.calc_legal_moves()
 6      for move in legal_moves:
 7          mb = deepcopy(mb_orig)
 8          x, y = move
 9          mb.move(x, y)
10          # mb の局面の評価値を計算する
11          score = 0
12          # 最も高い評価値が得られる合法手の一覧を計算する
13
14      # 最も高い評価値が得られる合法手の一覧の中からランダムに着手を選択する
行番号のないプログラム
from copy import deepcopy
from random import choice
 
def ai2s(mb_orig):
    legal_moves = mb_orig.calc_legal_moves()
    for move in legal_moves:
        mb = deepcopy(mb_orig)
        x, y = move
        mb.move(x, y)
        # mb の局面の評価値を計算する
        score = 0
        # 最も高い評価値が得られる合法手の一覧を計算する

    # 最も高い評価値が得られる合法手の一覧の中からランダムに着手を選択する
修正箇所
from copy import deepcopy
from random import choice

def ai2s(mb_orig):
    legal_moves = mb_orig.calc_legal_moves()
    for move in legal_moves:
        mb = deepcopy(mb_orig)
        x, y = move
        mb.move(x, y)
        # mb の局面の評価値を計算する
+       score = 0
        # 最も高い評価値が得られる合法手の一覧を計算する

    # 最も高い評価値が得られる合法手の一覧の中からランダムに着手を選択する

次に、上記のプログラムの 12 行目 のコメントの、「最も高い評価値 が得られる 合法手一覧を計算 する」という 処理記述 します。どのように記述すれば良いかについて少し考えてみて下さい。

なお、ルール 2 では、すべての局面評価値等しい ので、最も高い評価値計算 する 必要はない のではないかと思う人がいるかもしれませんが、今後、局面 によって 評価値異なるルール実装 する際に 必要になる ので、この機会に 実装する ことにします。

ai2s では、着手関係なく評価値常に 0 になるので、合法手着手処理 を行う、上記のプログラムの 7 ~ 9 行目記述 する 必要ありません。ただし、これは ルール 2例外 なだけで、局面状況に応じて評価値を計算 する ルール の場合は 記述 する 必要 があるので、本記事 でも 記述する ことにします。

最も高い評価値の記録

最も高い評価値 が得られる 合法手一覧を計算 する」という 処理 を行うためには、最も高い評価値何であるか記録 する 必要 があります。そこで、最も高い 評価値、すなわち 最高(best)の評価値を best_score という 名前変数記録 することにします。

次に、この best_score初期化処理記述 する 必要 があります。その処理を どこに記述 し、どのような値初期化 すればよいかについて少し考えてみて下さい。

記述 する 場所 は、合法手着手 した 局面評価値を計算 する より前 のどこかです。ただし、for 文ブロックの中 にその処理を 記述 すると、繰り返しのたびbest_score初期化されてしまう ので、for 文より前記述 する 必要 があります。具体的には、legal_moves = mb_orig.calc_legal_moves()前後どちらか の行に 記述すれば良い でしょう。本記事 では その後の行記述 することにします。

次に、best_scoreどのような値初期化 するかを 決めますbest_score は、それまでの局面評価値 の中の 最高値 を表しますが、初期化 した 時点 では、まだ一度も 評価値計算されていません。そこで、下記のプログラムの 3 行目 のように、とりあえずデータ存在しない ことを表す None初期化 することにします。

 1  def ai2s(mb_orig):
 2      legal_moves = mb_orig.calc_legal_moves()
 3      best_score = None
 4      for move in legal_moves:
 5          mb = deepcopy(mb_orig)
 6          x, y = move
 7          mb.move(x, y)
 8          # mb の局面の評価値を計算する
 9          score = 0
10          # 最も高い評価値が得られる合法手の一覧を計算する
11
12      # 最も高い評価値が得られる合法手の一覧の中からランダムに着手を選択する    
行番号のないプログラム
def ai2s(mb_orig):
    legal_moves = mb_orig.calc_legal_moves()
    best_score = None
    for move in legal_moves:
        mb = deepcopy(mb_orig)
        x, y = move
        mb.move(x, y)
        # mb の局面の評価値を計算する
        score = 0
        # 最も高い評価値が得られる合法手の一覧を計算する

    # 最も高い評価値が得られる合法手の一覧の中からランダムに着手を選択する  
修正箇所
def ai2s(mb_orig):
    legal_moves = mb_orig.calc_legal_moves()
+   best_score = None
    for move in legal_moves:
        mb = deepcopy(mb_orig)
        x, y = move
        mb.move(x, y)
        # mb の局面の評価値を計算する
        score = 0
        # 最も高い評価値が得られる合法手の一覧を計算する

    # 最も高い評価値が得られる合法手の一覧の中からランダムに着手を選択する  

最も高い評価値の判定方法

次に、10 行目の「最も高い評価値 が得られる 合法手一覧を計算 する」という 処理記述 します。どのような アルゴリズム で記述すれば良いかについて少し考えてみて下さい。

それまで局面 の中で 最も高い評価値 を表す best_score と、move着手 した 局面 での 評価値 を表す score関係整理 すると、下記の表 のようになります。

関係を表す式 関係が True の場合の意味
best_score is None 最初合法手着手 した 局面
best_score < score score がそれまでの 評価値 の中の 最大値 より 大きい
best_score == score score がそれまでの 評価値 の中の 最大値等しい
best_score > score score がそれまでの 評価値 の中の 最大値 より 小さい

上記の表の それぞれの場合 に行う 処理 は以下のようになります。

最初の合法手を着手した局面

評価値計算 する 最初の局面 なので、その 評価値最も高い評価値 になります。また、「最も高い評価値 が得られる 合法手一覧」は、その時に 着手 した move のみ です。従って、下記の処理 を行う必要があります。

  • best_scorescore更新 する
  • 最も高い評価値 が得られる 合法手一覧」を、その時に 着手 した move更新 する

score がそれまでの評価値の最大値より大きい

この場合も、その 評価値最も高い評価値 になります。また、それまでの「最も高い評価値 が得られる 合法手一覧」を 破棄 して、その時に 着手 した move のみ上書き する必要があります。それらの処理は、最初の合法手を着手した局面 で行う処理と 同じ です。

score がそれまでの評価値の最大値と等しい

この場合は、最も高い評価値 の値は 変化しない ので、下記の処理のみが必要です。

  • 最も高い評価値 が得られる 合法手一覧」に、その時に 着手 した move追加 する

score がそれまでの評価値の最大値より小さい

この場合は、特に 何もする必要はありません

プログラムの記述

上記 から、最も高い評価値判定 する 処理 は、下記のプログラムのように記述できます。

  • 10、11 行目:「最初合法手着手 した 局面」 または 「score がそれまでの 評価値最大値 より 大きい 場合」の処理を記述する
  • 14、15 行目score がそれまでの 評価値最大値等しい 場合の処理を記述する
  • 16 行目:Python では、ブロックの中プログラムを 1 つも記述しないエラー になるので、そのような場合は pass を記述 する 必要 がある

なお、score がそれまでの 評価値最大値 より 小さい 場合は、何もする必要はない ので、その場合のプログラムを 記述 する 必要ありません

 1  def ai2s(mb_orig):
 2      legal_moves = mb_orig.calc_legal_moves()
 3      best_score = None
 4      for move in legal_moves:
 5          mb = deepcopy(mb_orig)
 6          x, y = move
 7          mb.move(x, y)
 8          score = 0
 9          # 最初の合法手を着手した局面 または score がそれまでの評価値の中の最大値より大きい
10          if best_score is None or best_score < score:
11              best_score = score
12              # 「最も高い評価値が得られる合法手の一覧」を、その時に着手した move で更新する
13          # score がそれまでの評価値の中の最大値と等しい
14          elif best_score == score:
15              # 「最も高い評価値が得られる合法手の一覧」に、その時に着手した move を追加する
16              pass
17
18      # 最も高い評価値が得られる合法手の一覧の中からランダムに着手を選択する   
行番号のないプログラム
def ai2s(mb_orig):
    legal_moves = mb_orig.calc_legal_moves()
    best_score = None
    for move in legal_moves:
        mb = deepcopy(mb_orig)
        x, y = move
        mb.move(x, y)
        score = 0
        # 最初の合法手を着手した局面 または score がそれまでの評価値の中の「最大値より大きい」
        if best_score is None or best_score < score:
            best_score = score
            # 「最も高い評価値が得られる合法手の一覧」を、その時に着手した move で更新する
        # score がそれまでの評価値の中の「最大値と等しい」
        elif best_score == score:
            # 「最も高い評価値が得られる合法手の一覧」に、その時に着手した move を追加する
            pass

    # 最も高い評価値が得られる合法手の一覧の中からランダムに着手を選択する 
修正箇所
def ai2s(mb_orig):
    legal_moves = mb_orig.calc_legal_moves()
    best_score = None
    for move in legal_moves:
        mb = deepcopy(mb_orig)
        x, y = move
        mb.move(x, y)
        score = 0
+       # 最初の合法手を着手した局面 または score がそれまでの評価値の中の「最大値より大きい」
+       if best_score is None or best_score < score:
+           best_score = score
+           # 「最も高い評価値が得られる合法手の一覧」を、その時に着手した move で更新する
+       # score がそれまでの評価値の中の「最大値と等しい」
+       elif best_score == score:
+           # 「最も高い評価値が得られる合法手の一覧」に、その時に着手した move を追加する
+           pass

    # 最も高い評価値が得られる合法手の一覧の中からランダムに着手を選択する   

最も高い評価値が得られる合法手の一覧の処理

最後に、最も高い評価値 が得られる 合法手一覧 に関する 処理記述 します。強い AI が行う 処理 は、合法手 の中で 最も良い手、すなわち 最善手(best move)を 求める ことなので、そのデータ代入 する 変数名前best_moves と命名することにします。合法手一覧 なので、best_moves代入 する データ には list を利用することにします。下記は、best_moves に関する 処理ai2s記述 したプログラムです。

  • 4 行目for 文 による 繰り返し処理 で、best_moves空の list初期化 する

  • 13 行目:「最も高い評価値 が得られる 合法手一覧」を、その時に 着手 した move更新 する処理は、best_moves に、move のみ要素 とする list代入 する処理なので、best_moves[move]代入 する。move ではない 点に 注意 すること

  • 16 行目:「最も高い評価値 が得られる 合法手一覧」に、その時に 着手 した move追加 する処理を、append メソッドを使って行う

  • 18 行目choice を使って best_moves の中から ランダムな着手返り値 として 返す

 1  def ai2s(mb_orig):
 2      legal_moves = mb_orig.calc_legal_moves()
 3      best_score = None
 4      best_moves = []
 5      for move in legal_moves:
 6          mb = deepcopy(mb_orig)
 7          x, y = move
 8          mb.move(x, y)
 9          score = 0
10          # 最初の合法手を着手した局面 または score がそれまでの評価値の中の「最大値より大きい」
11          if best_score is None or best_score < score:
12              best_score = score
13              best_moves = [move]
14          # score がそれまでの評価値の中の「最大値と等しい」
15          elif best_score == score:
16              best_moves.append(move)
17
18      return choice(best_moves) 
行番号のないプログラム
def ai2s(mb_orig):
    legal_moves = mb_orig.calc_legal_moves()
    best_score = None
    best_moves = []
    for move in legal_moves:
        mb = deepcopy(mb_orig)
        x, y = move
        mb.move(x, y)
        score = 0
        # 最初の合法手を着手した局面 または score がそれまでの評価値の中の「最大値より大きい」
        if best_score is None or best_score < score:
            best_score = score
            best_moves = [move]
        # score がそれまでの評価値の中の「最大値と等しい」
        elif best_score == score:
            best_moves.append(move)

    return choice(best_moves)
修正箇所
def ai2s(mb_orig):
    legal_moves = mb_orig.calc_legal_moves()
    best_score = None
+   best_moves = []
    for move in legal_moves:
        mb = deepcopy(mb_orig)
        x, y = move
        mb.move(x, y)
        score = 0
        # 最初の合法手を着手した局面 または score がそれまでの評価値の中の「最大値より大きい」
        if best_score is None or best_score < score:
            best_score = score
+           best_moves = [move]
        # score がそれまでの評価値の中の「最大値と等しい」
        elif best_score == score:
-           pass
+           best_moves.append(move)

+    return choice(best_moves)  

動作の確認

ai2s正しく動作 するかどうかを 確認 する 方法 の一つに、同じルール着手を選択 する ai2対戦させる という方法があります。同じルール着手を選択 するのであれば、対戦した際の 通算成績勝率敗率ほぼ同じになる からです。

下記のプログラムの実行結果から、通算成績勝率敗率ほぼ同じ であることが 確認 できるので、ai2s正しく実装 できている 可能性が高い ことが 確認 できました。

この方法で 100% 正しく 実装できていることを 確認できる わけでは ありません が、精度は高い と思いますので、以後 は、この方法正しく実装できているか確認 します。

from ai import ai_match, ai1, ai2, ai3

ai_match(ai=[ai2s, ai2])

実行結果(実行結果はランダムなので下記とは異なる場合があります)

ai2s VS ai2
count     win    lose    draw
o        5819    2919    1262
x        2938    5814    1248
total    8757    8733    2510

ratio     win    lose    draw
o       58.2%   29.2%   12.6%
x       29.4%   58.1%   12.5%
total   43.8%   43.7%   12.6%

負の無限大を利用した、None を使わない記述方法

先程のプログラムでは、11 行目の条件式に、None であることを 判定 する best_score is None記述 していますが、これを 記述しなくても済む 方法があります。

Python では、正の無限大 を表す 浮動小数点数型 のデータを float("inf")1負の無限大 を表す 浮動小数点数型 のデータを float("-inf") のように 記述 することが できます

inf とだけ記述すれば良いのではないかと思う人がいるかもしれませんが、inf は、そのような名前変数みなされる ので うまくいきません。また、"inf" と記述すると、文字列型 のデータと みなされる ので うまくいきません

正の無限大 は、数値型 のデータの中で 最も大きな値負の無限大 は、数値型 のデータの中で 最も小さい値 であると みなされます。また、正の無限大どうし や、負の無限大どうし比較 すると 等しい とみなされます。下記のプログラムは、無限大の比較の例です。

print(float("inf") > 1000000000000000000000)   # 正の無限大とかなり大きな数字の比較
print(float("-inf") < -1000000000000000000000) # 正の無限大とかなり小さな数字の比較
print(float("inf") == float("inf"))            # 正の無限大どうしの比較
print(float("-inf") == float("-inf"))          # 負の無限大どうしの比較
print(float("-inf") < float("inf"))            # 負の無限大と正の無限大の比較

実行結果

True
True
True
True
True

この性質利用 することで、ai2s を下記のプログラムのように 記述 できます。

  • 3 行目best_score負の無限大初期化 する
  • 11 行目条件式 から best_score is None削除 する
 1  def ai2s(mb_orig):
 2      legal_moves = mb_orig.calc_legal_moves()
 3      best_score = float("-inf")
 4      best_moves = []
 5      for move in legal_moves:
 6          mb = deepcopy(mb_orig)
 7          x, y = move
 8          mb.move(x, y)
 9          score = 0
10          # 最初の合法手を着手した局面 または score がそれまでの評価値の中の「最大値より大きい」
11          if best_score < score:
12              best_score = score
13              best_moves = [move]
14          # score がそれまでの評価値の中の「最大値と等しい」
15          elif best_score == score:
16              best_moves.append(move)
17
18      return choice(best_moves) 
行番号のないプログラム
def ai2s(mb_orig):
    legal_moves = mb_orig.calc_legal_moves()
    best_score = float("-inf")
    best_moves = []
    for move in legal_moves:
        mb = deepcopy(mb_orig)
        x, y = move
        mb.move(x, y)
        score = 0
        # 最初の合法手を着手した局面 または score がそれまでの評価値の中の「最大値より大きい」
        if best_score < score:
            best_score = score
            best_moves = [move]
        # score がそれまでの評価値の中の「最大値と等しい」
        elif best_score == score:
            best_moves.append(move)

    return choice(best_moves)
修正箇所
def ai2s(mb_orig):
    legal_moves = mb_orig.calc_legal_moves()
-   best_score = None
+   best_score = float("-inf")
    best_moves = []
    for move in legal_moves:
        mb = deepcopy(mb_orig)
        x, y = move
        mb.move(x, y)
        score = 0
        # 最初の合法手を着手した局面 または score がそれまでの評価値の中の「最大値より大きい」
-       if best_score is None or best_score < score:
+       if best_score < score:
            best_score = score
            best_moves = [move]
        # score がそれまでの評価値の中の「最大値と等しい」
        elif best_score == score:
            best_moves.append(move)

    return choice(best_moves)

プログラムが正しく動作する理由

このプログラムが 正しく動作 する 理由 を説明します。

最初の局面 に対する 評価値計算行う前 の時点では、best_scorebest_moves は、34 行目の 処理 から 変化していない ので、下記の表 のようになります。

変数
best_score float("-inf")
best_moves []

ai2s では、評価値 の値が 必ず 0 になりますが、今後作成 する AI のことを 考慮 して、評価値 の値が どのような数値 になっても 正しい処理行われる ことを 示します

最初の局面で評価値の値が負の無限大以外になった場合

最初の局面 に対する 評価値 の値が、負の無限大以外 の値になった場合は、11 行目if best_score < score:条件式計算結果True になります。従って、1213 行目の 処理 が行われますが、これは、修正前 の、best_scoreNone代入 されていた場合と 全く同じ処理 が行われるということを 意味 します。

従って、最初の局面 に対する 評価値 の値が、負の無限大以外 の値になった場合は、修正前修正後 で、全く同じ処理 が行われ、best_score には、最初の局面評価値 である score が、best_moves には [move]代入 されます。

最初の局面で評価値の値が負の無限大になった場合

最初の局面 に対する 評価値 の値が、負の無限大 になった場合は、11 行目if best_score < score:条件式計算結果False になります。

その場合は、次の 15 行目elif best_score == score:実行 されますが、best_scorescore には どちらも負の無限大代入 されているので、その 条件式計算結果True になります。そのため、16 行目の best_moves.append(move) が実行されますが、best_moves には 空の list が代入されているので、best_moves の値は [move] になります。

best_score の値は 変化しません が、元々代入 されていた 負の無限大 は、最初の局面評価値等しい ので、best_score の値は score等しく なります。

従って、最初の局面 に対する 評価値 の値が、負の無限大 の場合も、修正前修正後 で、全く同じ処理 が行われます。以上の事から、最初の局面評価値どのような値 であっても、修正前修正後同じ処理 が行われることが 示されました

2 つ目以降の局面に値する処理

2 つ目以降局面 に対する 処理の記述 は、修正後修正前変わらない ので、2 つ目以降局面 に対しても、修正前修正後同じ処理 が行われることが分かります。以上の事から、修正前修正後 のプログラムが、常に 同じ処理 を行うことが 示されました

動作の確認

先程と 同様の方法 で、修正した ai2s正しく動作 するかどうかを 確認 します。実行結果から、ai2s正しく実装 できていることが 確認 できました。

ai_match(ai=[ai2s, ai2])

実行結果(実行結果はランダムなので下記とは異なる場合があります)

ai2s VS ai2
count     win    lose    draw
o        5874    2901    1225
x        2876    5890    1234
total    8750    8791    2459

ratio     win    lose    draw
o       58.7%   29.0%   12.2%
x       28.8%   58.9%   12.3%
total   43.8%   44.0%   12.3%

評価値を利用した ルール 3 の AI の実装

次に、評価値を利用 した アルゴリズムルール 3 の AI を 実装 する方法を 説明 します。なお、ルール 1 より先ルール 3実装 するのは、ルール 2 の時と同様に、ルール 3 のほう簡単実装できる からです。

下記は ルール 3再掲 したものです。

  • 真ん中 のマスに 優先的着手 する
  • 既に 埋まっていた場合ランダム なマスに 着手 する

評価値を利用 した アルゴリズム で上記の ルール 3実装 するために、評価値どのように計算 すれば良いかについて少し考えてみて下さい。

評価値を利用 した アルゴリズム では 以下 のように 評価値設定 します。

  • ルールの 条件 のうち、優先順位高い 条件を満たすものの 評価値 を、それよりも 優先順位低い 条件を満たすものの 評価値より高く設定 する
  • 優先順位同じ 条件を満たす、ランダム選択 したいものの 評価値 を、すべて 同じ値設定 する

ルール 3 の場合は、真ん中のマス優先的着手 し、真ん中のマス埋まっていた場合 は、ランダムなマス着手 するので、評価値下記 のように 設定 します。

  • 真ん中 のマスに 着手 した 局面評価値 に、最も高い評価値設定 する
  • 真ん中 のマス 以外着手 した 局面評価値 を、すべて 同じ値設定 する

上記の条件満たしていればどのような評価値設定 しても かまいません。本記事では、以下の表 のように 評価値設定 することにします。

局面 評価値
真ん中 のマスに 着手 した 局面 1
真ん中 のマス 以外着手 した 局面 0

他の値を設定したい人は、条件を満たす 好きな 値を設定 して下さい。

ai3s の定義

下記は、上記の表評価値を計算 するように、ai3s定義 したプログラムです。

  • 9 ~ 12行目着手 した 座標真ん中(1, 1) の場合は score1 を、そうでない場合0代入 する
 1  def ai3s(mb_orig):
 2      legal_moves = mb_orig.calc_legal_moves()
 3      best_score = float("-inf")
 4      best_moves = []
 5      for move in legal_moves:
 6          mb = deepcopy(mb_orig)
 7          x, y = move
 8          mb.move(x, y)
 9          if x == 1 and y == 1:
10              score = 1
11          else:
12              score = 0
13          # 最初の合法手を着手した局面 または score がそれまでの評価値の中の「最大値より大きい」
14          if best_score < score:
15              best_score = score
16              best_moves = [move]
17          # score がそれまでの評価値の中の「最大値と等しい」
18          elif best_score == score:
19              best_moves.append(move)
20
21      return choice(best_moves)
行番号のないプログラム
def ai3s(mb_orig):
    legal_moves = mb_orig.calc_legal_moves()
    best_score = float("-inf")
    best_moves = []
    for move in legal_moves:
        mb = deepcopy(mb_orig)
        x, y = move
        mb.move(x, y)
        if x == 1 and y == 1:
            score = 1
        else:
            score = 0
        # 最初の合法手を着手した局面 または score がそれまでの評価値の中の「最大値より大きい」
        if best_score < score:
            best_score = score
            best_moves = [move]
        # score がそれまでの評価値の中の「最大値と等しい」
        elif best_score == score:
            best_moves.append(move)

    return choice(best_moves)
修正箇所
-def ai2s(mb_orig):
+def ai3s(mb_orig):
    legal_moves = mb_orig.calc_legal_moves()
    best_score = float("-inf")
    best_moves = []
    for move in legal_moves:
        mb = deepcopy(mb_orig)
        x, y = move
        mb.move(x, y)
-       score = 0
+       if x == 1 and y == 1:
+           score = 1
+       else:
+           score = 0
        # 最初の合法手を着手した局面 または score がそれまでの評価値の中の「最大値より大きい」
        if best_score < score:
            best_score = score
            best_moves = [move]
        # score がそれまでの評価値の中の「最大値と等しい」
        elif best_score == score:
            best_moves.append(move)

    return choice(best_moves)

動作の確認

ai3s正しく動作 するかどうかを 確認 するために、先程と同様ai3対戦 を行います。実行結果 から、ai3s正しく実装 できていることが 確認 できました。

ai_match(ai=[ai3s, ai3])

実行結果(実行結果はランダムなので下記とは異なる場合があります)

ai3s VS ai3
count     win    lose    draw
o        6995    1839    1166
x        1911    6972    1117
total    8906    8811    2283

ratio     win    lose    draw
o       70.0%   18.4%   11.7%
x       19.1%   69.7%   11.2%
total   44.5%   44.1%   11.4%

tuple どうしの比較を利用した ai3s の記述

座標 を表す move に代入される データ は、x 座標y 座標表す2 つの要素 を持つ tuple です。先ほどのプログラムでは、move真ん中 のマスの 座標 を表す (1, 1) という tuple代入されているか どうかを、tuple各要素xy代入 してから x == 1 and y == 1 という 条件式 を記述することで 判定 していましたが、この 判定 を行う 条件式 を、より 簡潔に記述 することができます。

具体的には、2 つtuple が、同じ値要素持つか どうかを、下記のプログラムの 2、3 行目のように、== 演算子 を使って 判定 することができます。

move = (1, 1)
print(move == (1, 1))
print(move == (1, 2))

実行結果

True
False

従って、ai3s は、下記のプログラムのように記述することができます。

  • 9 行目move == (1, 1) によって、(1, 1) のマスに 着手したか どうかを 判定 する
 1  def ai3s(mb_orig):
 2      legal_moves = mb_orig.calc_legal_moves()
 3      best_score = float("-inf")
 4      best_moves = []
 5      for move in legal_moves:
 6          mb = deepcopy(mb_orig)
 7          x, y = move
 8          mb.move(x, y)
 9          if move == (1, 1):
10              score = 1
11          else:
12              score = 0
13          # 最初の合法手を着手した局面 または score がそれまでの評価値の中の「最大値より大きい」
14          if best_score < score:
15              best_score = score
16              best_moves = [move]
17          # score がそれまでの評価値の中の「最大値と等しい」
18          elif best_score == score:
19              best_moves.append(move)
20
21      return choice(best_moves)
行番号のないプログラム
def ai3s(mb_orig):
    legal_moves = mb_orig.calc_legal_moves()
    best_score = float("-inf")
    best_moves = []
    for move in legal_moves:
        mb = deepcopy(mb_orig)
        x, y = move
        mb.move(x, y)
        if move == (1, 1):
            score = 1
        else:
            score = 0
        # 最初の合法手を着手した局面 または score がそれまでの評価値の中の「最大値より大きい」
        if best_score < score:
            best_score = score
            best_moves = [move]
        # score がそれまでの評価値の中の「最大値と等しい」
        elif best_score == score:
            best_moves.append(move)

    return choice(best_moves)
修正箇所
def ai3s(mb_orig):
    legal_moves = mb_orig.calc_legal_moves()
    best_score = float("-inf")
    best_moves = []
    for move in legal_moves:
        mb = deepcopy(mb_orig)
        x, y = move
        mb.move(x, y)
        if x == 1 and y == 1:
            score = 1
        else:
            score = 0
        # 最初の合法手を着手した局面 または score がそれまでの評価値の中の「最大値より大きい」
        if best_score < score:
            best_score = score
            best_moves = [move]
        # score がそれまでの評価値の中の「最大値と等しい」
        elif best_score == score:
            best_moves.append(move)

    return choice(best_moves)

ai3s正しく動作 するかどうかを 確認 するために、ai3対戦 を行います。実行結果 から、ai3s正しく実装 できていることが 確認 できました。

ai_match(ai=[ai3s, ai3])

実行結果(実行結果はランダムなので下記とは異なる場合があります)

ai3s VS ai3
count     win    lose    draw
o        6945    1909    1146
x        1948    6878    1174
total    8893    8787    2320

ratio     win    lose    draw
o       69.5%   19.1%   11.5%
x       19.5%   68.8%   11.7%
total   44.5%   43.9%   11.6%

tuple どうしの比較の注意点

== 演算子を使って tuple どうしを 比較 する際に、外側()省略 すると、下記のプログラムのような、意図しない計算行われる 点に 注意 して下さい。

move = (1, 1)
print(move == 1, 1)  # print((1 == move, 1)) とみなされる
print(1, 1 == move)  # print((1, 1 == move)) とみなされる

実行結果

False 1
1 False

上記の計算が行われる 理由 は、2 行目move == 1, 1 という は、1 つ目要素move == 12 つ目要素1 である、(move == 1, 1) という tuple外側()省略された式 であると みなされる からです。move == 1 の計算結果は False なので、実行結果 には (False, 1) という tuple表す、「False 1」 が 表示 されます。3 行目も 同様 に、(1, 1 == move) という tupleみなされます

list どうしの比較と、注意点

list どうし比較 も、下記のプログラムの 2、3 行目 のように、tuple どうし の比較と 同様 に、== 演算子 を使って 行う ことが できます

ただし、4 行目 のように、要素の値が同じ であっても、listtuple== 演算子で 比較 すると False になる点に 注意 して下さい。

move = [1, 1]
print(move == [1, 1])
print(move == [1, 2])
print(move == (1, 1))

実行結果

True
False
False

評価値を利用した AI のひな形となる関数の定義

今回の記事で実装した ai2sai3s は、評価値計算 する 以外の処理完全に共通 します。下記のプログラムは、ai2sai3s共通する部分抜き出したもの です。なお、関数名は、仮の名前として ai としました。

共通しない のは、真ん中の「mb の局面の評価値を計算する処理」の 部分だけ です。

def ai(mb_orig):
    legal_moves = mb_orig.calc_legal_moves()
    best_score = float("-inf")
    best_moves = []
    for move in legal_moves:
        mb = deepcopy(mb_orig)
        x, y = move
        mb.move(x, y)

        # mb の局面の評価値を計算する処理
        
        if best_score < score:
            best_score = score
            best_moves = [move]
        elif best_score == score:
            best_moves.append(move)

    return choice(best_moves)

これは、ルール 23 以外 のルールを 評価値を利用 した アルゴリズム実装 する場合も 同様 です。その理由は、上記で抜き出したプログラムが行う処理が、評価値を利用 した アルゴリズム必ず行わなければならない共通の処理 だからです。

このことから、評価値を利用 した アルゴリズム では、異なるルールAI を実装する際に、局面評価値計算 する 処理のみ異なる ことがわかります。

評価値を利用 した アルゴリズムAI関数を記述 する際に、毎回 上記の コメント以外 のプログラムを 記述 するのは 大変 なので、上記の 共通する部分 の処理を行う、ひな形 となる 関数を定義 し、評価値の計算 を行う 処理のみ記述 することで、評価値を利用 した アルゴリズムAI簡潔に実装 できるようにします。

関数名の設定と、仮引数の追加

まず、ひな形 となる 関数の名前 を決める必要があります。評価値(score)を利用する AI なので、ai_by_score という 名前にする ことにします。

ひな形 となる 関数の中 で、「mb の局面の評価値を計算する処理」は、関数の 仮引数代入 した 関数呼び出して行う ことにします。これは、Marubatsu クラスの play メソッドや、ai_match実引数 に、AI の処理 を行う 関数記述 することで、play メソッドや ai_match の中で その関数 による 処理を行う ことができるようにした手法と 同じ です。

評価値計算 する 関数 のことを、評価関数evaluate function)と呼ぶので、その 関数を代入 する 仮引数の名前 は、それらの単語を略してつなげた eval_func と命名しました。

下記は、先程のプログラムの 関数名ai_by_score変更 し、仮引数eval_func追加 したプログラムです。

def ai_by_score(mb_orig, eval_func):
    legal_moves = mb_orig.calc_legal_moves()
    best_score = float("-inf")
    best_moves = []
    for move in legal_moves:
        mb = deepcopy(mb_orig)
        x, y = move
        mb.move(x, y)

        # eval_func を使って、mb の局面の評価値を計算する処理
        
        if best_score < score:
            best_score = score
            best_moves = [move]
        elif best_score == score:
            best_moves.append(move)

    return choice(best_moves)
修正箇所
-def ai(mb_orig):
+def ai_by_score(mb_orig, eval_func):
    legal_moves = mb_orig.calc_legal_moves()
    best_score = float("-inf")
    best_moves = []
    for move in legal_moves:
        mb = deepcopy(mb_orig)
        x, y = move
        mb.move(x, y)

-       # mb の局面の評価値を計算する処理
+       # eval_func を使って、mb の局面の評価値を計算する処理
        
        if best_score < score:
            best_score = score
            best_moves = [move]
        elif best_score == score:
            best_moves.append(move)

    return choice(best_moves)

評価関数の仕様

ai_by_score の中で、評価関数利用する処理記述 するためには、評価関数入力仮引数)と 出力返り値)の 仕様 を決める必要があります。

評価関数 は、局面評価値を計算 する 処理 を行うので、仮引数現在の局面 を表す Marubatsu クラスインスタンス代入 し、返り値 として 評価値を返す ことにします。

評価関数仕様決まった ので、ai_by_score の「eval_func を使って、mb の局面の評価値を計算する処理」の部分には、下記のプログラムの 10 行目 のように、eval_func実引数mb記述 して呼び出し、その 返り値score に代入 する処理を記述します。

 1  def ai_by_score(mb_orig, eval_func):
 2      legal_moves = mb_orig.calc_legal_moves()
 3      best_score = float("-inf")
 4      best_moves = []
 5      for move in legal_moves:
 6          mb = deepcopy(mb_orig)
 7          x, y = move
 8          mb.move(x, y)
 9
10          score = eval_func(mb)
11        
12          if best_score < score:
13              best_score = score
14              best_moves = [move]
15          elif best_score == score:
16              best_moves.append(move)
17
18      return choice(best_moves)
行番号のないプログラム
def ai_by_score(mb_orig, eval_func):
    legal_moves = mb_orig.calc_legal_moves()
    best_score = float("-inf")
    best_moves = []
    for move in legal_moves:
        mb = deepcopy(mb_orig)
        x, y = move
        mb.move(x, y)

        score = eval_func(mb)
        
        if best_score < score:
            best_score = score
            best_moves = [move]
        elif best_score == score:
            best_moves.append(move)

    return choice(best_moves)
修正箇所
def ai_by_score(mb_orig, eval_func):
    legal_moves = mb_orig.calc_legal_moves()
    best_score = float("-inf")
    best_moves = []
    for move in legal_moves:
        mb = deepcopy(mb_orig)
        x, y = move
        mb.move(x, y)

+       score = eval_func(mb)
        
        if best_score < score:
            best_score = score
            best_moves = [move]
        elif best_score == score:
            best_moves.append(move)

    return choice(best_moves)

ai_by_score を利用した、ルール 2 で着手を行う AI の定義

ai_by_score使ってルール 2着手 を行う AI定義 を行う 方法 を説明します。

まず、ルール 2評価関数定義 する必要があります。先ほど実装 した ai2s と同様に、 すべての局面評価値0 にすれば良いので、評価関数 は、下記のプログラムのように、常に 0 を返す関数 として 定義 します。

def eval_func(mb):
    return 0

ai2s は、ai_by_score と上記の eval_func を使って、下記のように 定義 できます。

def ai2s(mb):
    return ai_by_score(mb, eval_func)

このように、ひな形 となる ai_by_score定義 した事で、評価関数定義 と、return ai_by_score(mb, eval_func)記述するだけAI を作成できる ようになります。

動作の確認

修正した ai2s正しく動作 するかどうかを 確認 するために、ai2対戦 を行います。実行結果 から、ai2s正しく実装 できていることが 確認 できました。

ai_match(ai=[ai2s, ai2])

実行結果(実行結果はランダムなので下記とは異なる場合があります)

ai2s VS ai2
count     win    lose    draw
o        5774    2869    1357
x        2912    5830    1258
total    8686    8699    2615

ratio     win    lose    draw
o       57.7%   28.7%   13.6%
x       29.1%   58.3%   12.6%
total   43.4%   43.5%   13.1%

ai_by_score を利用した、ルール 3 で着手を行う AI の定義

次に、ai_by_score を使って、ルール 3 で着手を行う AI定義 することにします。

そのためには、ルール 3評価関数定義 する 必要 がありますが、ルール 2評価関数名前eval_func という 名前使った ので、その関数の名前には 別の名前付ける必要 があります。また、ai3s評価関数定義別々に記述 すると、その 2 つの関数関係わかりづらくなる という 欠点 があります。そこで、それらの 問題解決 する 方法 として、AI の関数評価関数 を、1 つにまとめる方法 を紹介します。

ローカル関数

その方法は、評価関数AIローカル関数 として 定義 するというものです。ローカル関数 は、関数の中定義した関数 の事で、以下ローカル変数同様の性質 を持ちます。

  • ローカル関数その関数ブロックの中 でしか 利用できない
  • 異なる関数 の中で、同一の名前ローカル関数異なる関数 として 定義できる

上記のような性質を持つ 理由 は、ローカル変数 も、ローカル関数 も、以前の記事で説明した、その 関数のローカル名前空間管理 されるからです。

従って、ai2s は、下記のプログラムのように定義できます。

  • 2 行目評価関数ai3sローカル関数 として eval_func という 名前定義 する
  • 3、4 行目直前の着手 は、mblast_move 属性代入 されるので、それが 真ん中 のマスを表す (1, 1)等しい 場合は 評価値 として 1返す
  • 5、6 行目:それ以外の場合は、評価値 として 0返す
  • 8 行目ai_by_score実引数eval_func を記述して呼び出した 返り値返す
1  def ai3s(mb):
2      def eval_func(mb):
3          if mb.last_move == (1, 1):
4              return 1
5          else:
6              return 0
7
8      return ai_by_score(mb, eval_func)
行番号のないプログラム
def ai3s(mb):
    def eval_func(mb):
        if mb.last_move == (1, 1):
            return 1
        else:
            return 0

    return ai_by_score(mb, eval_func)

2 行目評価関数名前 は、好きな名前 で定義しても 構いません が、その場合 は、8 行目実引数評価関数その名前記述 する 必要 があります。

動作の確認

修正した ai3s正しく動作 するかどうかを 確認 するために、ai3対戦 を行います。実行結果 から、ai3s正しく実装 できていることが 確認 できました。

ai_match(ai=[ai3s, ai3])

実行結果(実行結果はランダムなので下記とは異なる場合があります)

ai3s VS ai3
count     win    lose    draw
o        6915    1948    1137
x        1876    7001    1123
total    8791    8949    2260

ratio     win    lose    draw
o       69.2%   19.5%   11.4%
x       18.8%   70.0%   11.2%
total   44.0%   44.7%   11.3%

ローカル関数の利点

ローカル関数利用 することで、以下のような 利点得る ことができます。

  • AI の関数評価関数 を、一つ関数の定義まとめる ことが できる
  • どの AI実装 する 場合 でも、評価関数名前 として、常に eval_func という 共通の名前利用できる ので、AI ごと に、異なる名前考える必要なくなる

ローカル関数に関する補足

一般的関数 は、特定の処理 を、プログラムの さまざまな場所 から 利用できるようするこ とが 目的定義 しますが、その目的ローカル関数利用 することは できません。従って、ローカル関数 は、今回の記事で紹介したような、ローカル関数の利点有効に活用 できるような 場面以外 では あまり使われません

ローカル関数 は、クラスの定義ブロックの中定義するメソッドに似ている ように 見えるかもしれません が、クラスの メソッド は、クラスの定義の ブロックの外 で、そのクラスの インスタンス から呼び出して 利用 することが できます が、ローカル関数 は、ローカル関数を定義した関数の ブロックの外利用 することが できない 点が 異なります

関数ブロックの中 で、ローカル関数定義する前 で、そのローカル関数 を呼び出して 利用 することは できません。これは、以前の記事で説明した、グローバル関数定義する前 で、そのグローバル関数 を呼び出して 利用 することが できない のと 同じ です。以前の記事 で説明した、名前空間の仕組み 正しく理解していれば、そのことは 明らか でしょう。

従って、下記のプログラムのように、ai3s_buggy を定義して、ai_match(ai=[ai3s, ai3]) を呼び出すと、下記の実行結果のように エラーが発生 します。なお、関数名 の一部の buggy は、バグのある という 意味 の英語です。

def ai3s_buggy(mb):
    return ai_by_score(mb, eval_func)

    def eval_func(mb):
        if mb.last_move == (1, 1):
            return 1
        else:
            return 0

ai_match(ai=[ai3s_buggy, ai3])        

実行結果

略
Cell In[19], line 2
      1 def ai3s_buggy(mb):
----> 2     return ai_by_score(mb, eval_func)
      4     def eval_func(mb):
      5         if mb.last_move == (1, 1):

UnboundLocalError: cannot access local variable 'eval_func' where it is not associated with a value

上記のエラーメッセージは、以下のような意味を持ちます。

  • UnboundLocalError

ローカル(Local)変数(または関数)が名前空間に登録されていない(unbound)ことを表すエラー

  • cannot access local variable 'eval_func' where it is not associated with a value

値(vaule)が関連付けられていない(whiere it is not associated)eval_func というローカル(local)変数(または関数)(variable)をアクセス(利用)(access)できない(cannot)

ローカル関数を利用した、ルール 2 で着手を行う AI の定義

せっかくなので、下記のプログラムのように、ルール 2 についても、ローカル関数利用 した ai2s定義 を行います。

def ai2s(mb):
    def eval_func(mb):
        return 0
        
    return ai_by_score(mb, eval_func)

動作の確認

修正した ai2s正しく動作 するかどうかを 確認 するために、ai2対戦 を行います。実行結果 から、ai2s正しく実装 できていることが 確認 できました。

ai_match(ai=[ai2s, ai2])

実行結果(実行結果はランダムなので下記とは異なる場合があります)

ai2s VS ai2
count     win    lose    draw
o        5921    2857    1222
x        2829    5896    1275
total    8750    8753    2497

ratio     win    lose    draw
o       59.2%   28.6%   12.2%
x       28.3%   59.0%   12.8%
total   43.8%   43.8%   12.5%

今回の記事のまとめ

今回の記事では、評価値を利用 した アルゴリズム紹介 し、評価値を使った AIひな形 となる 処理 を行う ai_by_score定義 する事で、AI の処理 を行う 関数評価関数記述するだけ で、簡潔AI の関数記述 する 方法説明 しました。

また、実際ルール 2、3 を、評価値を利用 した アルゴリズム定義 しました。

今回の記事の内容からは、評価値を利用したアルゴリズムの 利点あまり感じられない かもしれません。次回の記事では引き続き、ルール 1、4 ~ 7 を評価値を利用したアルゴリズムで定義し、評価値を利用 した アルゴリズム利点と欠点 について 説明 します。

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

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

今回の記事では、marubatsu.py は修正していないので、marubatsu_new.py はありません。

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

次回の記事

  1. inf は、無限大を表す infinity の略です

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?