GoGametest / app.py
SmokeyBandit's picture
Update app.py
d805da2 verified
import gradio as gr
import numpy as np
import random
import cv2
from enum import Enum
from PIL import Image
class Difficulty(Enum):
EASY = "Easy"
MEDIUM = "Medium"
HARD = "Hard"
class GoGame:
def __init__(self, size=9):
self.size = size
self.board = np.zeros((size, size), dtype=int)
self.current_player = 1 # 1 for black, -1 for white
self.pass_count = 0
self.last_move = None
self.captured_black = 0
self.captured_white = 0
def get_neighbors(self, x, y):
neighbors = []
for dx, dy in [(0, 1), (1, 0), (0, -1), (-1, 0)]:
nx, ny = x + dx, y + dy
if 0 <= nx < self.size and 0 <= ny < self.size:
neighbors.append((nx, ny))
return neighbors
def get_group(self, x, y):
color = self.board[x, y]
if color == 0:
return set()
group = {(x, y)}
frontier = [(x, y)]
while frontier:
current = frontier.pop()
for nx, ny in self.get_neighbors(*current):
if self.board[nx, ny] == color and (nx, ny) not in group:
group.add((nx, ny))
frontier.append((nx, ny))
return group
def has_liberties(self, x, y):
group = self.get_group(x, y)
for stone_x, stone_y in group:
for nx, ny in self.get_neighbors(stone_x, stone_y):
if self.board[nx, ny] == 0:
return True
return False
def capture_stones(self):
captured = 0
for x in range(self.size):
for y in range(self.size):
if self.board[x, y] == -self.current_player and not self.has_liberties(x, y):
group = self.get_group(x, y)
captured += len(group)
for gx, gy in group:
self.board[gx, gy] = 0
return captured
def is_valid_move(self, x, y):
if self.board[x, y] != 0:
return False
# Make a temporary move.
self.board[x, y] = self.current_player
valid = self.has_liberties(x, y)
captures_enemy = self.capture_stones() > 0
# Undo temporary move.
self.board[x, y] = 0
return valid or captures_enemy
def make_move(self, x, y):
if not self.is_valid_move(x, y):
return False
self.board[x, y] = self.current_player
captured = self.capture_stones()
if self.current_player == 1:
self.captured_white += captured
else:
self.captured_black += captured
self.last_move = (x, y)
self.current_player *= -1
return True
def ai_move(self, difficulty):
empty_positions = list(zip(*np.where(self.board == 0)))
if not empty_positions:
return None
valid_moves = [pos for pos in empty_positions if self.is_valid_move(*pos)]
if not valid_moves:
return None
if difficulty == Difficulty.EASY:
move = random.choice(valid_moves)
elif difficulty == Difficulty.MEDIUM:
move = self.medium_ai(valid_moves)
else:
move = self.hard_ai(valid_moves)
self.make_move(*move)
return move
def medium_ai(self, valid_moves):
if self.last_move:
lx, ly = self.last_move
valid_moves.sort(key=lambda p: abs(p[0] - lx) + abs(p[1] - ly))
return valid_moves[0]
return self.center_based_move(valid_moves)
def center_based_move(self, valid_moves):
center = self.size // 2
valid_moves.sort(key=lambda p: abs(p[0] - center) + abs(p[1] - center))
return valid_moves[0]
def hard_ai(self, valid_moves):
for move in valid_moves:
self.board[move] = self.current_player
if self.capture_stones() > 0:
self.board[move] = 0
return move
self.board[move] = 0
opponent = -self.current_player
self.current_player = opponent
for move in valid_moves:
if self.is_valid_move(*move):
self.board[move] = opponent
if self.capture_stones() > 0:
self.board[move] = 0
self.current_player = -opponent
return move
self.board[move] = 0
self.current_player = -opponent
return self.medium_ai(valid_moves)
def create_board_image(board, last_move=None, hover_pos=None):
cell_size = 60
margin = 40
total_size = board.shape[0] * cell_size + 2 * margin
# Create wooden background.
image = np.full((total_size, total_size, 3), [219, 179, 119], dtype=np.uint8)
# Draw grid.
for i in range(board.shape[0]):
cv2.line(image, (margin + i * cell_size, margin), (margin + i * cell_size, total_size - margin), (0, 0, 0), 2)
cv2.line(image, (margin, margin + i * cell_size), (total_size - margin, margin + i * cell_size), (0, 0, 0), 2)
# Draw star points.
star_points = [(2, 2), (2, 6), (4, 4), (6, 2), (6, 6)]
for point in star_points:
cv2.circle(image, (margin + point[1] * cell_size, margin + point[0] * cell_size), 4, (0, 0, 0), -1, cv2.LINE_AA)
# Draw hover indicator, if any.
if hover_pos:
hover_row, hover_col = hover_pos
center = (margin + hover_col * cell_size, margin + hover_row * cell_size)
overlay = image.copy()
cv2.circle(overlay, center, 23, (0, 0, 0), -1, cv2.LINE_AA)
image = cv2.addWeighted(overlay, 0.5, image, 0.5, 0)
cv2.circle(image, center, 23, (255, 255, 255), 1, cv2.LINE_AA)
# Draw stones.
for i in range(board.shape[0]):
for j in range(board.shape[1]):
if board[i, j] != 0:
center = (margin + j * cell_size, margin + i * cell_size)
# Shadow effect.
cv2.circle(image, (center[0] + 2, center[1] + 2), 23, (0, 0, 0), -1, cv2.LINE_AA)
stone_color = (0, 0, 0) if board[i, j] == 1 else (255, 255, 255)
cv2.circle(image, center, 23, stone_color, -1, cv2.LINE_AA)
# 3D highlight.
if board[i, j] == -1:
cv2.circle(image, (center[0] - 5, center[1] - 5), 8, (240, 240, 240), -1, cv2.LINE_AA)
else:
cv2.circle(image, (center[0] - 5, center[1] - 5), 8, (40, 40, 40), -1, cv2.LINE_AA)
# Highlight last move.
if last_move:
row, col = last_move
center = (margin + col * cell_size, margin + row * cell_size)
cv2.circle(image, center, 5, (255, 0, 0), -1, cv2.LINE_AA)
return Image.fromarray(image)
class GradioGoGame:
def __init__(self):
self.game = GoGame()
self.difficulty = Difficulty.EASY
self.hover_pos = None
def process_click(self, evt: gr.SelectData):
cell_size = 60
margin = 40
# evt.index returns pixel coordinates as (x, y). Convert to board coordinates.
col = round((evt.index[0] - margin) / cell_size)
row = round((evt.index[1] - margin) / cell_size)
if not (0 <= row < self.game.size and 0 <= col < self.game.size):
return create_board_image(self.game.board, self.game.last_move), "Invalid click position"
if not self.game.make_move(row, col):
return create_board_image(self.game.board, self.game.last_move), "Invalid move (occupied or suicide)"
# AI Move.
ai_move = self.game.ai_move(self.difficulty)
if ai_move is None:
return create_board_image(self.game.board, self.game.last_move), "Game Over"
status = f"Black captures: {self.game.captured_white} | White captures: {self.game.captured_black}"
return create_board_image(self.game.board, self.game.last_move), f"AI moved to: {ai_move}\n{status}"
def update_hover(self, evt: gr.SelectData):
cell_size = 60
margin = 40
col = round((evt.index[0] - margin) / cell_size)
row = round((evt.index[1] - margin) / cell_size)
if 0 <= row < self.game.size and 0 <= col < self.game.size:
self.hover_pos = (row, col)
else:
self.hover_pos = None
return create_board_image(self.game.board, self.game.last_move, self.hover_pos)
def reset_game(self):
self.game = GoGame()
self.hover_pos = None
return create_board_image(self.game.board), "Game reset"
def create_interface():
game = GradioGoGame()
with gr.Blocks(theme=gr.themes.Soft()) as interface:
gr.Markdown("""
# Go Game vs AI
Play the ancient game of Go against an AI opponent. Black plays first.
**Rules:**
- Click on any intersection to place a stone.
- Capture enemy stones by surrounding them.
- Stones must have liberties (empty adjacent points) to survive.
""")
with gr.Row():
with gr.Column(scale=2):
board_output = gr.Image(
value=create_board_image(game.game.board),
label="Go Board",
type="pil",
height=600,
width=600,
interactive=True
)
msg_output = gr.Textbox(
label="Game Status",
value="Click on an intersection to place a stone",
lines=2
)
with gr.Column(scale=1):
difficulty = gr.Radio(
choices=[d.value for d in Difficulty],
value=Difficulty.EASY.value,
label="AI Difficulty"
)
reset_btn = gr.Button("Reset Game", variant="secondary")
# Update difficulty when the radio value changes.
def update_difficulty(value):
game.difficulty = Difficulty(value)
return value
difficulty.change(update_difficulty, inputs=[difficulty], outputs=[difficulty])
# Use .select() for click events on the interactive image.
board_output.select(
game.process_click,
inputs=[], # No additional inputs: the event data is passed automatically.
outputs=[board_output, msg_output]
)
# (Removed mousemove event since gr.Image doesn't support it in your version.)
reset_btn.click(
game.reset_game,
outputs=[board_output, msg_output]
)
return interface
if __name__ == "__main__":
interface = create_interface()
interface.launch()