GoGametest / app.py
SmokeyBandit's picture
Update app.py
ecd7e7e verified
raw
history blame
11.8 kB
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 with thicker lines.
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 (hoshi).
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 position indicator.
if hover_pos:
hover_row, hover_col = hover_pos
center = (margin + hover_col * cell_size, margin + hover_row * cell_size)
overlay = image.copy()
# Draw a semi-transparent preview.
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 with enhanced 3D effect.
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.
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)
# Highlight for 3D effect.
if board[i, j] == -1: # White stone highlight.
cv2.circle(image,
(center[0] - 5, center[1] - 5),
8,
(240, 240, 240),
-1,
cv2.LINE_AA)
else: # Black stone highlight.
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, difficulty):
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"
self.difficulty = Difficulty(difficulty)
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
# Convert pixel position to board coordinates.
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")
# Use .select() for click events on the interactive image.
board_output.select(
game.process_click,
inputs=[board_output, difficulty],
outputs=[board_output, msg_output]
)
# Use .mousemove() for hover updates (if supported by your Gradio version).
board_output.mousemove(
game.update_hover,
inputs=board_output,
outputs=board_output
)
reset_btn.click(
game.reset_game,
outputs=[board_output, msg_output]
)
return interface
if __name__ == "__main__":
interface = create_interface()
interface.launch()