|
import gradio as gr |
|
from PIL import Image, ImageDraw |
|
import random |
|
|
|
def choose_move(board, player): |
|
""" |
|
Simple AI: chooses a random valid move from all legal flips. |
|
""" |
|
valid_moves = [] |
|
for r in range(8): |
|
for c in range(8): |
|
if get_flips(board, r, c, player): |
|
valid_moves.append((r, c)) |
|
if not valid_moves: |
|
return None |
|
return random.choice(valid_moves) |
|
|
|
def initialize_board(): |
|
board = [[0 for _ in range(8)] for _ in range(8)] |
|
board[3][3], board[4][4] = 1, 1 |
|
board[3][4], board[4][3] = -1, -1 |
|
return board |
|
|
|
DIRECTIONS = [(-1, -1), (-1, 0), (-1, 1), |
|
(0, -1), (0, 1), |
|
(1, -1), (1, 0), (1, 1)] |
|
|
|
def get_flips(board, row, col, player): |
|
if board[row][col] != 0: |
|
return [] |
|
flips = [] |
|
for dr, dc in DIRECTIONS: |
|
r, c = row + dr, col + dc |
|
buffer = [] |
|
while 0 <= r < 8 and 0 <= c < 8 and board[r][c] == -player: |
|
buffer.append((r, c)) |
|
r += dr |
|
c += dc |
|
if buffer and 0 <= r < 8 and 0 <= c < 8 and board[r][c] == player: |
|
flips.extend(buffer) |
|
return flips |
|
|
|
def apply_move(board, row, col, player): |
|
flips = get_flips(board, row, col, player) |
|
if not flips: |
|
return False |
|
board[row][col] = player |
|
for r, c in flips: |
|
board[r][c] = player |
|
return True |
|
|
|
def board_to_image(board, img_size=320): |
|
cell_size = img_size // 8 |
|
img = Image.new('RGB', (img_size, img_size), 'green') |
|
draw = ImageDraw.Draw(img) |
|
for r in range(8): |
|
for c in range(8): |
|
x0, y0 = c * cell_size, r * cell_size |
|
x1, y1 = x0 + cell_size, y0 + cell_size |
|
draw.rectangle([x0, y0, x1, y1], outline='black') |
|
if board[r][c] == 1: |
|
draw.ellipse([x0+4, y0+4, x1-4, y1-4], fill='white') |
|
elif board[r][c] == -1: |
|
draw.ellipse([x0+4, y0+4, x1-4, y1-4], fill='black') |
|
return img |
|
|
|
def count_score(board): |
|
black = sum(cell == -1 for row in board for cell in row) |
|
white = sum(cell == 1 for row in board for cell in row) |
|
return black, white |
|
|
|
def play_move(x, y, state): |
|
board, player = state |
|
img_size = 320 |
|
cell_size = img_size / 8 |
|
col, row = int(x // cell_size), int(y // cell_size) |
|
if not (0 <= row < 8 and 0 <= col < 8): |
|
return board_to_image(board), f"Click inside the board.", state |
|
if not apply_move(board, row, col, player): |
|
return board_to_image(board), f"Invalid move at ({row+1},{col+1})", state |
|
player = -player |
|
black_score, white_score = count_score(board) |
|
status = f"Black: {black_score} | White: {white_score} | {'Black' if player == -1 else 'White'} to move" |
|
return board_to_image(board), status, (board, player) |
|
|
|
def main(): |
|
with gr.Blocks() as demo: |
|
gr.HTML("<h2>Othello (Reversi) Game</h2>") |
|
state = gr.State((initialize_board(), -1)) |
|
image_box = gr.Image(value=board_to_image(initialize_board()), interactive=True, label="Click to place a stone") |
|
status = gr.Text(value="Black: 2 | White: 2 | Black to move", interactive=False) |
|
|
|
def click_handler(evt: gr.SelectData, state): |
|
|
|
board_img, status_text, state = play_move(evt.index[0], evt.index[1], state) |
|
board, player = state |
|
|
|
if player == 1: |
|
ai_move = choose_move(board, player) |
|
if ai_move: |
|
apply_move(board, ai_move[0], ai_move[1], player) |
|
player = -player |
|
black_score, white_score = count_score(board) |
|
status_text = f"Black: {black_score} | White: {white_score} | {'Black' if player == -1 else 'White'} to move" |
|
board_img = board_to_image(board) |
|
state = (board, player) |
|
return board_img, status_text, state |
|
|
|
image_box.select(click_handler, inputs=[state], outputs=[image_box, status, state]) |
|
demo.launch() |
|
|
|
if __name__ == "__main__": |
|
main() |