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

ITスキル

こんにちは。ヒトツメです。
前回までは、Pythonを使って、オセロを作ってきました。

今回からは、前回までに作ったGUIで動かせるオセロを活用して、CPUを作っていきたいと思います。
最終的には簡単なAIを作って、遊べるようにしていきたいと思います。

スポンサーリンク

考え方

前回までに作成してきた、GUIで動かせるオセロは、tkinterをベースに、gameUIというクラスを作成し、動かせるようにしてきました。その際、nextgameおよびplacableという関数を用意し、便利に呼び出せるようにしました。

今回はこれをベースに作るのですが、その際、CPUというクラスを作成し、CPU側に手番が回るたびにCPUクラスにgameの状況を渡すこととします。CPUクラスはgameの状況を渡されたら、gameUIに対してどの位置に石を置くかを返却することとします。
このようにすることで、CPU側にどのようなロジックを基に次の手を選ぶかを設定することが出来るため、CPU側を改良していくだけで、徐々にCPUを強くすることが出来ます。

CPUの手番を設定する

このような考え方でCPUを実装することを前提に、まずはgameUIを設定します。
もともとのgameUIは、次のようになっていましたが、ここに、「CPUが設定されている場合、CPUの手番になったら、CPUクラスに手番をお渡す」というロジックを入れます。

class gameUI(tk.Frame):
    def __init__(self, game, master=None):
        tk.Frame.__init__(self, master)
        self.master.title('othello')
        self.c = tk.Canvas(self, width = 640, height = 480, highlightthickness = 0)
        self.c.bind('<Button-1>', self.on_click)
        self.player = 1
        self.c.pack()
        #盤の色を指定
        self.c.create_rectangle(0, 0, 480, 480, width = 0.0, fill = '#006400')
        #升目を表示
        for rowIter in range(8):
            self.c.create_line(0, rowIter * 60, 480, rowIter * 60, width = 1.0, fill = '#FFFFFF')
        for clmIter in range(8):
            self.c.create_line(clmIter * 60, 0, clmIter * 60, 480, width = 1.0, fill = '#FFFFFF')
        #石を配置
        self.game = game
        self.on_draw()
    def on_click(self, event):
        self.x = int(event.x/60)
        self.y = int(event.y/60)
        self.nextstatus = self.game.copy()
        nextgame(self.nextstatus, self.y, self.x, self.player)
        if self.nextstatus[self.y][self.x] != self.game[self.y][self.x]:
            self.game = self.nextstatus
            self.player = -1 * self.player
            able = placable(self.game, self.player)
            if numpy.sum(able) == 0.0: #どこにも置けない場合、手番を変更
                self.player = -1 * self.player
                able = placable(self.game, self.player)
            self.on_draw()
            if numpy.sum(able) == 0.0: #どこにも置けない場合、終局
                self.game_end() 

引数として、CPUをどちらに設定するかを入れ、on_drawするたびにCPUに手番があるかどうかを判定します。必要に応じて、CPUに手番を渡しますが、この時、クリックして石を置く場合も、CPUが石を置く場合も同じような処理になるため、setStoneという関数を新たに作り、石の設置も記載を簡略化します。

class gameUI(tk.Frame):
    def __init__(self, game, CPU_stone, master=None):
        tk.Frame.__init__(self, master)
        self.master.title('othello')
        self.c = tk.Canvas(self, width = 640, height = 480, highlightthickness = 0)
        self.c.bind('<Button-1>', self.on_click)
        self.player = 1
        self.c.pack()
        self.CPU_stone = CPU_stone
        #盤の色を指定
        self.c.create_rectangle(0, 0, 480, 480, width = 0.0, fill = '#006400')
        #升目を表示
        for rowIter in range(8):
            self.c.create_line(0, rowIter * 60, 480, rowIter * 60, width = 1.0, fill = '#FFFFFF')
        for clmIter in range(8):
            self.c.create_line(clmIter * 60, 0, clmIter * 60, 480, width = 1.0, fill = '#FFFFFF')
        #石を配置
        self.game = game
        self.on_draw()
    def on_click(self, event):
        self.x = int(event.x/60)
        self.y = int(event.y/60)
        if self.y < 8 and self.x < 8:
            self.setStone(self.y, self.x)
            self.on_draw()
    def setStone(self, row, clm):
        self.nextstatus = self.game.copy()
        nextgame(self.nextstatus, row, clm, self.player)
        if self.nextstatus[row][clm] != self.game[row][clm]:
            self.game = self.nextstatus
            self.player = -1 * self.player
            able = placable(self.game, self.player)
            if numpy.sum(able) == 0.0: #どこにも置けない場合、手番を変更
                self.player = -1 * self.player
                able = placable(self.game, self.player)
            if numpy.sum(able) == 0.0: #どこにも置けない場合、終局
                self.finish = True
                self.game_end()
    def on_draw(self):
        #CPUに手番がある場合、CPUの処理を行う
        if self.player == self.CPU_stone:
            self.CPU_player = CPU(self.game, self.player)
            row, clm = self.CPU_player.Action()
            self.setStone(row, clm)
            ...

ポイントは、次の二点です。
一点目は、CPU_stoneという引数を設定することで、gameUIを呼び出す際にCPUの手番を設定できるところです。その上で、CPUというクラスにActionという関数を設定し、CPUに手番が渡るたびにどの手を設定するかを渡せるようにします。
二点目は、手番が変更になるたびに、CPUの手番になれば、「row, clm = self.CPU_player.Action()」という部分で、CPUが選ぶ石の位置を返却させるという点です。

最初はランダムアクション

その上で、CPUクラスのActionに、どの手を選ぶかを設定させるわけですが、最初はランダムアクションを設定します。placable関数を活用し、ランダムにどの手を選ぶかを決めます。

class CPU():
    def __init__(self, game, CPU_stone):
        self.game = game
        self.CPU_stone = CPU_stone
    def Action(self):
        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

reshapeという関数で、8×8の二次元配列を一次元配列に変換します。それに対してランダムな同じ形の一次元配列を掛けることで、指せる手に重みを付けます。
一番値が大きくなった手を、再度8×8に変換して、返却するようにしています。

さいごに

これで、一人でオセロを楽しめるようになりました。
ただ、完全にランダムアクションなので、非常に弱いです。
次からは、より良い手に重みを付けて、より良い手を選べるようになる方法を考えていきたいと思います。

コメント

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