【はじめてのPython】オセロのCPUを作る④

ITスキル

こんにちは。ヒトツメです。
またも少し間が空いてしまいましたが、今回は前回学んだ再帰関数を使って、前々回に取り上げたアルファベータ法を実装していきたいと思います。

スポンサーリンク

考え方の整理とActionの修正

そこで、実際に再帰関数を用いて、3手先の局面の石の個数を数えて手の評価をしていきたいと思うわけですが、その下準備として、どのように再帰関数を用いるかということを整理して考える必要があります。
というのも、再帰関数を用いて3手先のすべての局面を洗い出し、もっとも理想的な局面がどのような状態かを検討したとしても、その局面に持っていくための手がどれなのか等、実際にアクションをするときの動きなどが定まっていないと、ただただ局面を洗い出しているにすぎないからです。

そこで、次のような考え方を用いて、実装をしていきたいと思います。

  1. その局面におけるすべての手を洗い出す
  2. その一つ一つの手を指した状態における「理想的な局面」を導く
  3. 一つ一つの手に関して、「理想的な局面」における石の数で評価を行う
  4. もっとも評価が高かった手を選択する

ちなみに、ここでいう「理想的な局面」とは、お互いに最善となる手を指していながら、自分にとって最も望ましい局面を言います。要は、相手が自分の都合の良いように動いているわけではなく、きちんと最善手(と思われる手)を指している状態を言います。

これを、CPUが黒の手番の時に関して実装すると、次のようになります。

    def Action(self):
        if self.CPU_stone == 1:
            actionArr = placable(self.game, self.CPU_stone).reshape(64) * numpy.random.rand(64) * self.CPU_stone
            action = actionArr.argmax()
            row = int(action / 8)
            clm = action % 8
            return row, clm
        elif self.CPU_stone == -1:
            act = placableAct(self.game, self.CPU_stone)
            actionList = []
            zero_count = numpy.count_nonzero(self.game == 0.0)
            for action in act:
                [row, clm] = action
                self.copygame = self.game.copy()
                nextgame(self.copygame, row, clm, self.CPU_stone)
                self.stone = -1 * self.CPU_stone
                score = self.minimax(self.copygame, self.stone, zero_count)
                actionList.append([score, action])
            actionList.sort(reverse=True)
            row, clm = actionList[0][1]
            return row, clm

CPU_stoneが「-1」、つまり黒の時に、全ての手を洗い出し、その一つ一つを実際に刺した局面を作り出します。また、その局面について、self.minimax()という関数でscoreを評価しています。それをactionListに入れていき、scoreが降順になるように並び替え、先頭の手を選択する、という感じです。

minimax関数を実装

このように準備ができたところで、minimax関数を実装していきます。
ちなみに、上のminimax関数では、zero_countという引数をとっています。これは、局面における「0」、つまり何も置かれていない場所の数を表します。これによって、今の局面が何手先の局面かを表しています。

    def minimax(self, game, stone, zero_count):
        if numpy.count_nonzero(game == 0.0) == 0 or numpy.count_nonzero(game == 0.0) <= zero_count - 3:
            stone = -1 * stone
            return numpy.count_nonzero(game == self.CPU_stone)
        else:
            best_score = (-float('inf'))
            act = placableAct(game, stone)
            for action in act:
                [row, clm] = action
                copy_game = game.copy()
                nextgame(copy_game, row, clm, stone)
                stone = -1 * stone
                score = -1 * self.minimax(copy_game, stone, zero_count)
                stone = -1 * stone
                if score > best_score:
                    best_score = score
            return best_score

実装は意外とシンプルで、if文がTrueの場合の処理は、何も置けなくなるか3手先の局面に至った状態で、自分と同じ色の石の数を返却するというものです。Falseの場合、相手方の手番における次の手を一つ一つ洗い出し、再帰関数を用いて、if文がTrueになるまで何度も処理をしていきます。
ポイントの一つ目は、gameをcopyしていることです。これをしなければ、分岐を探索するときに、きちんと分岐前の局面に戻ることが出来ません。
また、ポイントの二つ目は、scoreを「-1」で掛けて、より大きい場合にscoreを更新している点です。このようにすることで、相手にとっても自分の石を多くしようと最善手を取り、自分にとってのみ都合の良い局面に陥ることを防いでいます。
ちなみに、float(‘inf’)は、無限を表しています。

以上の実装を実際に動かしてみると、次のように、きちんと作動することが確認できます。

さいごに

とはいえ、これだと前々回に整理した枝刈りは行われていません。したがって、これだとアルファベータ法ではなく、単なるミニマックス法になります。
次回以降で、枝刈りの方法を整理し、処理を軽くして、アルファベータ法を実装していきたいと思います。

コメント

  1. kk より:

    プログラミング初心者でヒトツメさんのこのHPでオセロの勉強させていただいてます。
    なんとかここまで(【はじめてのPython】オセロのCPUを作る)までこれたのですが、ここのページ(【はじめてのPython】オセロのCPUを作る④)で躓いています。「too many values to unpack (expected 2)」や「name ‘placableAct’ is not defined」がエラーで出てしまい、動かせません。私の理解度が足りてないせいだと思うのですが、自分だけの力では修正できませんでした。もし可能なら、オセロのCPUを作る④の実装したコードを全文知りたいのですが教えていただけますでしょうか?(もし可能ならオセロのCPUを作る⑤も)

    • ヒトツメ より:

      コメントありがとうございます。
      こちらのコードのplacableActの関数定義が突然出現して困惑させてしまったようです。失礼しました。
      https://hitotsu-eye.com/python_07/
      こちらの記事に記載の、def placable の関数を用いて、次の一手をすべて洗い出し、3手先に進んだ場合の最も評価値の高い手を選び出すという部分で用いていますので、その前提で一度コードを見返していただくと理解が進むかと思います。
      よろしくお願いいたします。

      • kk より:

        返信ありがとうございます。
        「【はじめてのPython】オセロを作る⑥~ついに完成!~」で用いられたplacable関数をdef placableActに書き換えているということなのでしょうか?それともdef placable の関数はそのまま、新たにdef placableAct関数を作成するということのでしょうか?自分なりに修正しても動かすことが出来ても、他のエラー(「too many values to unpack (expected 2)」など)が出てきてしまい何が正解なのか分からない状態です。部分的なコードではなく【はじめてのPython】オセロを作る⑥~ついに完成!~」の最後に掲載して頂いたような感じで全コードが分かれば自分なりに答え合わせなど解釈できると思うのですが‥‥(placableAct、placable関数以外のコードも修正されていると思うのでその修正も、どうしていいのか分からないので)
        何度も申し訳ございません。
        私自身初歩的なことも理解してなくてすみません。

        • ヒトツメ より:

          大変申し訳ないのですが、コーディングは正解があるものではなく、色々とトライアンドエラーを繰り返すことによって導かれるものだと思います。
          placableAct is not difinedのようなエラーは、単純に関数定義がされていない(or読み込めていない)ということなので、その際には関数定義の部分を修正する必要があると言うことになります。
          エラーに対するデバッグも含めて、一度全体を見直していただくことを推奨致します。

          ※ブログという性質上、管理者が記事に全コードを載せていない場合は、外部への開示は難しいというものだとお考えいただきたいです。

          • kk より:

            度重なる質問への返信ありがとうございました。しかも、その質問内容も大変愚かなことばかりでごめんなさい。ご迷惑おかけして申し訳ありませんでした。

          • ヒトツメ より:

            とんでもないです。
            ご期待に応えられず申し訳ありません。引き続きお楽しみいただければ幸いです。

タイトルとURLをコピーしました