できるだけChatGPTだけで作るリバーシ(オセロ)ソフト その1

はじめに
オセロはツクダオリジナルの商標のため、以降はリバーシで統一します。
今回は検索用にタイトルに入れました。

以前にも書いた、ChatGPTだけでリバーシを作るという話は頓挫していたわけではなく、優先度が落ちていただけで最近になって少しずつ活動を再開しました。

今回はまずは動くものを作ることを目標に書いていきます。

選定言語は、標準ライブラリでもGUIができるPythonとしました。

まずは、ChatGPTに記載するプロンプトを考えます。

Pythonでマウス操作だけで遊べるリバーシのソフトを作ってください。 要件としては以下の通りです。 ・GUIはPython標準ライブラリだけで動くものとする ・プレイヤーが先手、後手を選んでからプレイできる ・スコアが表示される ・コンピュータ側は合法手からランダムで手を選んで打つ ・その他はリバーシのルールを踏襲する機能を持つ

GPT-4 2023/8/3バージョン

出てきたコードは次の通り

import tkinter as tk
import random

class Reversi:
    def __init__(self, root):
        self.root = root
        self.canvas = tk.Canvas(root, width=400, height=400)
        self.canvas.pack()
        self.board = [[0 for _ in range(8)] for _ in range(8)]
        self.board[3][3] = 1
        self.board[4][4] = 1
        self.board[3][4] = -1
        self.board[4][3] = -1
        self.player = 1
        self.score_label = tk.Label(root, text="Player 1: 2, Player 2: 2")
        self.score_label.pack()
        self.canvas.bind("<Button-1>", self.click)
        self.draw_board()

    def draw_board(self):
        for i in range(8):
            for j in range(8):
                x, y = i * 50, j * 50
                self.canvas.create_rectangle(x, y, x + 50, y + 50, fill="green")
                if self.board[i][j] == 1:
                    self.canvas.create_oval(x + 5, y + 5, x + 45, y + 45, fill="white")
                elif self.board[i][j] == -1:
                    self.canvas.create_oval(x + 5, y + 5, x + 45, y + 45, fill="black")
        self.update_score()

    def click(self, event):
        x, y = event.x // 50, event.y // 50
        if self.place_piece(x, y, self.player, True):
            self.player *= -1
            self.draw_board()
            self.computer_move()

    def computer_move(self):
        if self.player == -1:
            valid_moves = [(x, y) for x in range(8) for y in range(8) if self.place_piece(x, y, self.player, False)]
            if valid_moves:
                move = random.choice(valid_moves)
                self.place_piece(move[0], move[1], self.player, True)
                self.player *= -1
                self.draw_board()

    def place_piece(self, x, y, player, actual=False):
        if x < 0 or x >= 8 or y < 0 or y >= 8 or self.board[x][y] != 0:
            return False
        directions = [(0, 1), (1, 0), (0, -1), (-1, 0), (1, 1), (-1, -1), (1, -1), (-1, 1)]
        to_flip = []
        for dx, dy in directions:
            i, j, flips = x + dx, y + dy, []
            while 0 <= i < 8 and 0 <= j < 8 and self.board[i][j] == -player:
                flips.append((i, j))
                i += dx
                j += dy
            if 0 <= i < 8 and 0 <= j < 8 and self.board[i][j] == player and flips:
                to_flip.extend(flips)
        if not to_flip:
            return False
        if actual:
            self.board[x][y] = player
            for i, j in to_flip:
                self.board[i][j] = player
        return True

    def update_score(self):
        scores = [sum(row.count(player) for row in self.board) for player in [1, -1]]
        self.score_label.config(text=f"Player 1: {scores[0]}, Player 2: {scores[1]}")

root = tk.Tk()
reversi = Reversi(root)
root.mainloop()


見た目はちゃんとした感じのものが表示されました。

が、その後に出てきた説明文に不穏なことが書いてありまして・・・

  • プレイヤーは先手(白ピース)としてスタートします。

  • クリックすることでピースを配置できます。

  • コンピュータは合法手からランダムにピースを配置します。

  • スコアがラベルとして表示されます。

  • その他、リバーシの基本的なルールが実装されています。

いや、先手は黒なので。
終了判定などが入っていなかったので、いくつか質問を繰り返し、最終的に次のコードができあがりました。

import tkinter as tk
import random
from tkinter import messagebox

class Reversi:
    def __init__(self, root):
        self.root = root
        self.choose_color()
        self.canvas = tk.Canvas(root, width=400, height=400)
        self.canvas.pack()
        self.board = [[0 for _ in range(8)] for _ in range(8)]
        self.board[3][3] = -1
        self.board[4][4] = -1
        self.board[3][4] = 1
        self.board[4][3] = 1
        self.player = 1  # Player starts with black pieces
        self.score_label = tk.Label(root, text="Player 1: 2, Player 2: 2")
        self.score_label.pack()
        self.canvas.bind("<Button-1>", self.click)
        self.draw_board()

        self.move_count = 0
        
    def choose_color(self):
        self.color_choice = tk.Toplevel(self.root)
        tk.Label(self.color_choice, text="Choose your color:").pack()
        tk.Button(self.color_choice, text="Black", command=lambda: self.start_game(1)).pack()
        tk.Button(self.color_choice, text="White", command=lambda: self.start_game(-1)).pack()

    def start_game(self, color):
        self.color_choice.destroy()
        self.canvas = tk.Canvas(self.root, width=400, height=400)
        self.canvas.pack()
        self.initialize_board()
        self.player = color  # Player chooses the color
        self.score_label = tk.Label(self.root, text="Player 1: 2, Player 2: 2")
        self.score_label.pack()
        self.canvas.bind("<Button-1>", self.click)
        self.draw_board()
        if self.player == -1:
            self.computer_move()

    def initialize_board(self):
        self.board = [[0 for _ in range(8)] for _ in range(8)]
        self.board[3][3] = -1
        self.board[4][4] = -1
        self.board[3][4] = 1
        self.board[4][3] = 1
        self.move_count = 0
        
    def draw_board(self):
        for i in range(8):
            for j in range(8):
                x, y = i * 50, j * 50
                self.canvas.create_rectangle(x, y, x + 50, y + 50, fill="green")
                if self.board[i][j] == 1:
                    self.canvas.create_oval(x + 5, y + 5, x + 45, y + 45, fill="black")
                elif self.board[i][j] == -1:
                    self.canvas.create_oval(x + 5, y + 5, x + 45, y + 45, fill="white")
        self.update_score()

    def click(self, event):
        x, y = event.x // 50, event.y // 50
        if self.place_piece(x, y, self.player, True):
            self.move_count += 1
            self.player *= -1
            self.draw_board()
            self.check_game_end()
            
    def computer_move(self):
        if self.player == -1:
            valid_moves = [(x, y) for x in range(8) for y in range(8) if self.place_piece(x, y, self.player, False)]
            if valid_moves:
                move = random.choice(valid_moves)
                self.place_piece(move[0], move[1], self.player, True)
                self.move_count += 1
                self.player *= -1
                self.draw_board()
            else:
                self.player *= -1  # No valid moves, switch turn to the other player
            self.check_game_end()

    def place_piece(self, x, y, player, actual=False):
        if x < 0 or x >= 8 or y < 0 or y >= 8 or self.board[x][y] != 0:
            return False
        directions = [(0, 1), (1, 0), (0, -1), (-1, 0), (1, 1), (-1, -1), (1, -1), (-1, 1)]
        to_flip = []
        for dx, dy in directions:
            i, j, flips = x + dx, y + dy, []
            while 0 <= i < 8 and 0 <= j < 8 and self.board[i][j] == -player:
                flips.append((i, j))
                i += dx
                j += dy
            if 0 <= i < 8 and 0 <= j < 8 and self.board[i][j] == player and flips:
                to_flip.extend(flips)
        if not to_flip:
            return False
        if actual:
            self.board[x][y] = player
            for i, j in to_flip:
                self.board[i][j] = player
        return True

    def update_score(self):
        scores = [sum(row.count(player) for row in self.board) for player in [1, -1]]
        self.score_label.config(text=f"Player 1: {scores[0]}, Player 2: {scores[1]}")

    def check_game_end(self):
        player_moves = [(x, y) for x in range(8) for y in range(8) if self.place_piece(x, y, self.player, False)]
        opponent_moves = [(x, y) for x in range(8) for y in range(8) if self.place_piece(x, y, -self.player, False)]
        
        if not player_moves and not opponent_moves or self.move_count >= 60:
            self.show_game_end()
        elif not player_moves:  # No valid moves for the current player, switch turn
            self.player *= -1

    def show_game_end(self):
        scores = [sum(row.count(player) for row in self.board) for player in [1, -1]]
        winner = "Draw"
        if scores[0] > scores[1]:
            winner = "Player 1 wins"
        elif scores[1] > scores[0]:
            winner = "Player 2 wins"

        messagebox.showinfo("Game Over", f"{winner}! Final Score - Player 1: {scores[0]}, Player 2: {scores[1]}")
        self.canvas.destroy()
        self.score_label.destroy()
        self.choose_color()


root = tk.Tk()
reversi = Reversi(root)
root.mainloop()

初期配置は白黒逆だし、まだ先手後手の選択もできませんし、コンピュータの手番にならない(プレイヤーが相手側の石を置ける)など、まだまだ問題点は多いです。
これを見て作ってみようと思った方は、この辺りのことも最初の指示にしっかり書いた方がいいでしょう。いい感じにやってくれそうで、やってくれないです。

それでは、今日はここまで。また次回に。

次回は、バグ修正と、評価関数、探索関数の簡易的なものを作っていきます。

この記事が気に入ったらサポートをしてみませんか?