Update app.py
Browse files
app.py
CHANGED
@@ -2,8 +2,6 @@ import gradio as gr
|
|
2 |
from PIL import Image, ImageDraw
|
3 |
import numpy as np
|
4 |
import math
|
5 |
-
from io import BytesIO
|
6 |
-
import base64
|
7 |
|
8 |
# Constants
|
9 |
BOARD_SIZE = 9
|
@@ -13,7 +11,7 @@ EMPTY = 0
|
|
13 |
WHITE_SOLDIER = 1
|
14 |
BLACK_SOLDIER = 2
|
15 |
KING = 3
|
16 |
-
CASTLE = (4, 4)
|
17 |
CAMPS = [
|
18 |
(0,3), (0,4), (0,5), (1,4), # Top camp
|
19 |
(8,3), (8,4), (8,5), (7,4), # Bottom camp
|
@@ -22,14 +20,14 @@ CAMPS = [
|
|
22 |
]
|
23 |
ESCAPES = [(i,j) for i in [0,8] for j in range(BOARD_SIZE)] + [(i,j) for j in [0,8] for i in range(BOARD_SIZE) if (i,j) not in CAMPS]
|
24 |
COLORS = {
|
25 |
-
'empty':
|
26 |
-
'castle':
|
27 |
-
'camp':
|
28 |
-
'escape':
|
29 |
-
'white':
|
30 |
-
'black':
|
31 |
-
'king':
|
32 |
-
'highlight':
|
33 |
}
|
34 |
|
35 |
# Game state class
|
@@ -37,9 +35,9 @@ class TablutState:
|
|
37 |
def __init__(self):
|
38 |
self.board = np.zeros((BOARD_SIZE, BOARD_SIZE), dtype=int)
|
39 |
self.turn = 'WHITE'
|
40 |
-
self.black_in_camps = set(CAMPS)
|
41 |
self.setup_initial_position()
|
42 |
-
self.move_history = []
|
43 |
|
44 |
def setup_initial_position(self):
|
45 |
self.board[4, 4] = KING
|
@@ -59,16 +57,9 @@ class TablutState:
|
|
59 |
|
60 |
# Utility functions
|
61 |
def pos_to_coord(pos):
|
62 |
-
"""Convert (row, col) to board coordinate (e.g., (4,4) -> 'E5')."""
|
63 |
row, col = pos
|
64 |
return f"{chr(ord('A') + col)}{row + 1}"
|
65 |
|
66 |
-
def coord_to_pos(coord):
|
67 |
-
"""Convert board coordinate (e.g., 'E5') to (row, col)."""
|
68 |
-
col = ord(coord[0].upper()) - ord('A')
|
69 |
-
row = int(coord[1]) - 1
|
70 |
-
return (row, col)
|
71 |
-
|
72 |
def is_adjacent_to_castle(pos):
|
73 |
x, y = pos
|
74 |
cx, cy = CASTLE
|
@@ -95,7 +86,6 @@ def is_valid_move(state, from_pos, to_pos):
|
|
95 |
to_row, to_col = to_pos
|
96 |
if from_row != to_row and from_col != to_col:
|
97 |
return False
|
98 |
-
# Path must be clear
|
99 |
if from_row == to_row:
|
100 |
step = 1 if to_col > from_col else -1
|
101 |
for col in range(from_col + step, to_col, step):
|
@@ -106,10 +96,8 @@ def is_valid_move(state, from_pos, to_pos):
|
|
106 |
for row in range(from_row + step, to_row, step):
|
107 |
if state.board[row, from_col] != EMPTY:
|
108 |
return False
|
109 |
-
# Castle is only for the king
|
110 |
if to_pos == CASTLE and piece != KING:
|
111 |
return False
|
112 |
-
# Camp restrictions
|
113 |
if to_pos in CAMPS:
|
114 |
if state.turn == 'WHITE' or (state.turn == 'BLACK' and from_pos not in state.black_in_camps):
|
115 |
return False
|
@@ -137,14 +125,12 @@ def get_legal_moves(state, from_pos):
|
|
137 |
def is_soldier_captured(state, pos, friendly):
|
138 |
x, y = pos
|
139 |
friendly_pieces = get_friendly_pieces(friendly)
|
140 |
-
# Standard capture
|
141 |
if y > 0 and y < BOARD_SIZE - 1:
|
142 |
if state.board[x, y-1] in friendly_pieces and state.board[x, y+1] in friendly_pieces:
|
143 |
return True
|
144 |
if x > 0 and x < BOARD_SIZE - 1:
|
145 |
if state.board[x-1, y] in friendly_pieces and state.board[x+1, y] in friendly_pieces:
|
146 |
return True
|
147 |
-
# Capture against castle or camp
|
148 |
if is_adjacent_to_castle(pos):
|
149 |
cx, cy = CASTLE
|
150 |
if x == cx:
|
@@ -158,16 +144,17 @@ def is_soldier_captured(state, pos, friendly):
|
|
158 |
elif x > cx and x < BOARD_SIZE - 1 and state.board[x+1, y] in friendly_pieces:
|
159 |
return True
|
160 |
if pos in CAMPS:
|
161 |
-
return False
|
162 |
for camp in CAMPS:
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
|
|
171 |
return False
|
172 |
|
173 |
def is_king_captured(state, pos):
|
@@ -192,7 +179,6 @@ def apply_move(state, from_pos, to_pos):
|
|
192 |
new_state.board[from_pos] = EMPTY
|
193 |
if new_state.turn == 'BLACK' and from_pos in new_state.black_in_camps and to_pos not in CAMPS:
|
194 |
new_state.black_in_camps.discard(from_pos)
|
195 |
-
# Apply captures
|
196 |
captures = []
|
197 |
opponent = 'BLACK' if new_state.turn == 'WHITE' else 'WHITE'
|
198 |
for x in range(BOARD_SIZE):
|
@@ -207,7 +193,6 @@ def apply_move(state, from_pos, to_pos):
|
|
207 |
for pos in captures:
|
208 |
new_state.board[pos] = EMPTY
|
209 |
new_state.turn = 'BLACK' if new_state.turn == 'WHITE' else 'WHITE'
|
210 |
-
# Update move history for draw detection
|
211 |
board_tuple = tuple(new_state.board.flatten())
|
212 |
new_state.move_history.append(board_tuple)
|
213 |
return new_state
|
@@ -225,23 +210,13 @@ def check_game_status(state):
|
|
225 |
return "BLACK WINS"
|
226 |
if king_pos in ESCAPES:
|
227 |
return "WHITE WINS"
|
228 |
-
|
229 |
-
|
230 |
-
|
231 |
-
|
232 |
-
if (state.turn == 'WHITE' and state.board[x, y] in [WHITE_SOLDIER, KING]) or \
|
233 |
-
(state.turn == 'BLACK' and state.board[x, y] == BLACK_SOLDIER):
|
234 |
-
pieces.append((x, y))
|
235 |
-
has_moves = False
|
236 |
-
for pos in pieces:
|
237 |
-
if get_legal_moves(state, pos):
|
238 |
-
has_moves = True
|
239 |
-
break
|
240 |
if not has_moves:
|
241 |
return "BLACK WINS" if state.turn == 'WHITE' else "WHITE WINS"
|
242 |
-
|
243 |
-
board_tuple = tuple(state.board.flatten())
|
244 |
-
if state.move_history.count(board_tuple) >= 2:
|
245 |
return "DRAW"
|
246 |
return "CONTINUE"
|
247 |
|
@@ -257,11 +232,9 @@ def evaluate_state(state):
|
|
257 |
king_pos = find_king_position(state)
|
258 |
if not king_pos:
|
259 |
return -1000
|
260 |
-
# Heuristic: distance from king to nearest escape
|
261 |
min_escape_dist = min(manhattan_distance(king_pos, e) for e in ESCAPES)
|
262 |
white_pieces = sum(1 for x in range(BOARD_SIZE) for y in range(BOARD_SIZE) if state.board[x, y] == WHITE_SOLDIER)
|
263 |
black_pieces = sum(1 for x in range(BOARD_SIZE) for y in range(BOARD_SIZE) if state.board[x, y] == BLACK_SOLDIER)
|
264 |
-
# Encourage Black to surround king, White to escape
|
265 |
if state.turn == 'WHITE':
|
266 |
return -min_escape_dist * 10 + white_pieces * 5 - black_pieces * 3
|
267 |
else:
|
@@ -303,21 +276,22 @@ def minimax(state, depth, alpha, beta, maximizing_player):
|
|
303 |
|
304 |
def ai_move(state):
|
305 |
if state.turn != 'BLACK':
|
306 |
-
return state, "Not AI's turn"
|
307 |
-
depth = 3
|
308 |
_, move = minimax(state, depth, -math.inf, math.inf, True)
|
309 |
if move:
|
310 |
from_pos, to_pos = move
|
311 |
new_state = apply_move(state, from_pos, to_pos)
|
312 |
-
return new_state, f"AI moved from {pos_to_coord(from_pos)} to {pos_to_coord(to_pos)}"
|
313 |
-
return state, "AI has no moves"
|
314 |
|
315 |
-
#
|
316 |
-
def
|
317 |
-
|
318 |
-
|
319 |
-
|
320 |
-
|
|
|
321 |
for x in range(BOARD_SIZE):
|
322 |
for y in range(BOARD_SIZE):
|
323 |
pos = (x, y)
|
@@ -330,70 +304,58 @@ def generate_board_svg(state, selected_pos=None):
|
|
330 |
fill = COLORS['escape']
|
331 |
if pos == selected_pos:
|
332 |
fill = COLORS['highlight']
|
333 |
-
|
334 |
-
# Draw pieces
|
335 |
for x in range(BOARD_SIZE):
|
336 |
for y in range(BOARD_SIZE):
|
337 |
piece = state.board[x, y]
|
338 |
if piece != EMPTY:
|
339 |
-
|
340 |
-
cy = x * CELL_SIZE + CELL_SIZE // 2
|
341 |
color = COLORS['white'] if piece == WHITE_SOLDIER else COLORS['black'] if piece == BLACK_SOLDIER else COLORS['king']
|
342 |
-
|
343 |
-
|
344 |
for i in range(BOARD_SIZE):
|
345 |
-
|
346 |
-
|
347 |
-
|
348 |
-
return ''.join(svg)
|
349 |
-
|
350 |
-
def svg_to_image(svg_content):
|
351 |
-
img = Image.new('RGB', (BOARD_SIZE * CELL_SIZE, BOARD_SIZE * CELL_SIZE), color='white')
|
352 |
-
# For Gradio, we encode SVG as base64 to display in HTML
|
353 |
-
svg_bytes = svg_content.encode('utf-8')
|
354 |
-
svg_base64 = base64.b64encode(svg_bytes).decode('utf-8')
|
355 |
-
return svg_base64
|
356 |
|
357 |
# Gradio interface functions
|
358 |
def click_board(state, selected_pos, evt: gr.SelectData):
|
359 |
if state.turn != 'WHITE':
|
360 |
-
return state, "It's the AI's turn",
|
361 |
-
x = evt.index[1] // CELL_SIZE
|
362 |
y = evt.index[0] // CELL_SIZE
|
363 |
pos = (x, y)
|
364 |
if selected_pos is None:
|
365 |
-
# Select a piece
|
366 |
if state.board[pos] in [WHITE_SOLDIER, KING]:
|
367 |
-
return state, f"Selected {pos_to_coord(pos)}",
|
368 |
else:
|
369 |
-
return state, "Select a White piece or King",
|
370 |
else:
|
371 |
-
# Try to move
|
372 |
if is_valid_move(state, selected_pos, pos):
|
373 |
new_state = apply_move(state, selected_pos, pos)
|
374 |
status = check_game_status(new_state)
|
375 |
if status != "CONTINUE":
|
376 |
-
return new_state, status,
|
377 |
-
|
378 |
-
ai_state, ai_message, _ = ai_move(new_state)
|
379 |
final_status = check_game_status(ai_state)
|
380 |
-
|
|
|
381 |
else:
|
382 |
-
return state, "Invalid move",
|
383 |
|
384 |
def new_game():
|
385 |
state = TablutState()
|
386 |
-
return state, "New game started. Your turn (White).",
|
387 |
|
388 |
# Gradio interface
|
389 |
with gr.Blocks(title="Tablut Game") as demo:
|
390 |
state = gr.State()
|
391 |
selected_pos = gr.State(value=None)
|
392 |
-
|
393 |
message_label = gr.Label(label="Message")
|
394 |
new_game_button = gr.Button("New Game")
|
395 |
-
|
396 |
-
new_game_button.click(fn=new_game, outputs=[state, message_label,
|
397 |
-
demo.load(fn=new_game, outputs=[state, message_label,
|
398 |
|
399 |
# Note: demo.launch() is not needed for HF Spaces
|
|
|
2 |
from PIL import Image, ImageDraw
|
3 |
import numpy as np
|
4 |
import math
|
|
|
|
|
5 |
|
6 |
# Constants
|
7 |
BOARD_SIZE = 9
|
|
|
11 |
WHITE_SOLDIER = 1
|
12 |
BLACK_SOLDIER = 2
|
13 |
KING = 3
|
14 |
+
CASTLE = (4, 4)
|
15 |
CAMPS = [
|
16 |
(0,3), (0,4), (0,5), (1,4), # Top camp
|
17 |
(8,3), (8,4), (8,5), (7,4), # Bottom camp
|
|
|
20 |
]
|
21 |
ESCAPES = [(i,j) for i in [0,8] for j in range(BOARD_SIZE)] + [(i,j) for j in [0,8] for i in range(BOARD_SIZE) if (i,j) not in CAMPS]
|
22 |
COLORS = {
|
23 |
+
'empty': (255, 255, 255), # White
|
24 |
+
'castle': (128, 128, 128), # Gray
|
25 |
+
'camp': (139, 69, 19), # Brown
|
26 |
+
'escape': (0, 255, 0), # Green
|
27 |
+
'white': (255, 255, 255), # White
|
28 |
+
'black': (0, 0, 0), # Black
|
29 |
+
'king': (255, 215, 0), # Gold
|
30 |
+
'highlight': (255, 255, 0) # Yellow
|
31 |
}
|
32 |
|
33 |
# Game state class
|
|
|
35 |
def __init__(self):
|
36 |
self.board = np.zeros((BOARD_SIZE, BOARD_SIZE), dtype=int)
|
37 |
self.turn = 'WHITE'
|
38 |
+
self.black_in_camps = set(CAMPS)
|
39 |
self.setup_initial_position()
|
40 |
+
self.move_history = []
|
41 |
|
42 |
def setup_initial_position(self):
|
43 |
self.board[4, 4] = KING
|
|
|
57 |
|
58 |
# Utility functions
|
59 |
def pos_to_coord(pos):
|
|
|
60 |
row, col = pos
|
61 |
return f"{chr(ord('A') + col)}{row + 1}"
|
62 |
|
|
|
|
|
|
|
|
|
|
|
|
|
63 |
def is_adjacent_to_castle(pos):
|
64 |
x, y = pos
|
65 |
cx, cy = CASTLE
|
|
|
86 |
to_row, to_col = to_pos
|
87 |
if from_row != to_row and from_col != to_col:
|
88 |
return False
|
|
|
89 |
if from_row == to_row:
|
90 |
step = 1 if to_col > from_col else -1
|
91 |
for col in range(from_col + step, to_col, step):
|
|
|
96 |
for row in range(from_row + step, to_row, step):
|
97 |
if state.board[row, from_col] != EMPTY:
|
98 |
return False
|
|
|
99 |
if to_pos == CASTLE and piece != KING:
|
100 |
return False
|
|
|
101 |
if to_pos in CAMPS:
|
102 |
if state.turn == 'WHITE' or (state.turn == 'BLACK' and from_pos not in state.black_in_camps):
|
103 |
return False
|
|
|
125 |
def is_soldier_captured(state, pos, friendly):
|
126 |
x, y = pos
|
127 |
friendly_pieces = get_friendly_pieces(friendly)
|
|
|
128 |
if y > 0 and y < BOARD_SIZE - 1:
|
129 |
if state.board[x, y-1] in friendly_pieces and state.board[x, y+1] in friendly_pieces:
|
130 |
return True
|
131 |
if x > 0 and x < BOARD_SIZE - 1:
|
132 |
if state.board[x-1, y] in friendly_pieces and state.board[x+1, y] in friendly_pieces:
|
133 |
return True
|
|
|
134 |
if is_adjacent_to_castle(pos):
|
135 |
cx, cy = CASTLE
|
136 |
if x == cx:
|
|
|
144 |
elif x > cx and x < BOARD_SIZE - 1 and state.board[x+1, y] in friendly_pieces:
|
145 |
return True
|
146 |
if pos in CAMPS:
|
147 |
+
return False
|
148 |
for camp in CAMPS:
|
149 |
+
cx, cy = camp
|
150 |
+
if (x, y) == (cx + 1, cy) and 0 <= cx < BOARD_SIZE and state.board[cx, cy] in friendly_pieces + [EMPTY]:
|
151 |
+
return True
|
152 |
+
elif (x, y) == (cx - 1, cy) and 0 <= cx < BOARD_SIZE and state.board[cx, cy] in friendly_pieces + [EMPTY]:
|
153 |
+
return True
|
154 |
+
elif (x, y) == (cx, cy + 1) and 0 <= cy < BOARD_SIZE and state.board[cx, cy] in friendly_pieces + [EMPTY]:
|
155 |
+
return True
|
156 |
+
elif (x, y) == (cx, cy - 1) and 0 <= cy < BOARD_SIZE and state.board[cx, cy] in friendly_pieces + [EMPTY]:
|
157 |
+
return True
|
158 |
return False
|
159 |
|
160 |
def is_king_captured(state, pos):
|
|
|
179 |
new_state.board[from_pos] = EMPTY
|
180 |
if new_state.turn == 'BLACK' and from_pos in new_state.black_in_camps and to_pos not in CAMPS:
|
181 |
new_state.black_in_camps.discard(from_pos)
|
|
|
182 |
captures = []
|
183 |
opponent = 'BLACK' if new_state.turn == 'WHITE' else 'WHITE'
|
184 |
for x in range(BOARD_SIZE):
|
|
|
193 |
for pos in captures:
|
194 |
new_state.board[pos] = EMPTY
|
195 |
new_state.turn = 'BLACK' if new_state.turn == 'WHITE' else 'WHITE'
|
|
|
196 |
board_tuple = tuple(new_state.board.flatten())
|
197 |
new_state.move_history.append(board_tuple)
|
198 |
return new_state
|
|
|
210 |
return "BLACK WINS"
|
211 |
if king_pos in ESCAPES:
|
212 |
return "WHITE WINS"
|
213 |
+
pieces = [(x, y) for x in range(BOARD_SIZE) for y in range(BOARD_SIZE) if
|
214 |
+
(state.turn == 'WHITE' and state.board[x, y] in [WHITE_SOLDIER, KING]) or
|
215 |
+
(state.turn == 'BLACK' and state.board[x, y] == BLACK_SOLDIER)]
|
216 |
+
has_moves = any(get_legal_moves(state, pos) for pos in pieces)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
217 |
if not has_moves:
|
218 |
return "BLACK WINS" if state.turn == 'WHITE' else "WHITE WINS"
|
219 |
+
if state.move_history.count(tuple(state.board.flatten())) >= 2:
|
|
|
|
|
220 |
return "DRAW"
|
221 |
return "CONTINUE"
|
222 |
|
|
|
232 |
king_pos = find_king_position(state)
|
233 |
if not king_pos:
|
234 |
return -1000
|
|
|
235 |
min_escape_dist = min(manhattan_distance(king_pos, e) for e in ESCAPES)
|
236 |
white_pieces = sum(1 for x in range(BOARD_SIZE) for y in range(BOARD_SIZE) if state.board[x, y] == WHITE_SOLDIER)
|
237 |
black_pieces = sum(1 for x in range(BOARD_SIZE) for y in range(BOARD_SIZE) if state.board[x, y] == BLACK_SOLDIER)
|
|
|
238 |
if state.turn == 'WHITE':
|
239 |
return -min_escape_dist * 10 + white_pieces * 5 - black_pieces * 3
|
240 |
else:
|
|
|
276 |
|
277 |
def ai_move(state):
|
278 |
if state.turn != 'BLACK':
|
279 |
+
return state, "Not AI's turn"
|
280 |
+
depth = 3
|
281 |
_, move = minimax(state, depth, -math.inf, math.inf, True)
|
282 |
if move:
|
283 |
from_pos, to_pos = move
|
284 |
new_state = apply_move(state, from_pos, to_pos)
|
285 |
+
return new_state, f"AI moved from {pos_to_coord(from_pos)} to {pos_to_coord(to_pos)}"
|
286 |
+
return state, "AI has no moves"
|
287 |
|
288 |
+
# Board visualization
|
289 |
+
def generate_board_image(state, selected_pos=None):
|
290 |
+
img = Image.new('RGB', (BOARD_SIZE * CELL_SIZE, BOARD_SIZE * CELL_SIZE), color=COLORS['empty'])
|
291 |
+
draw = ImageDraw.Draw(img)
|
292 |
+
for i in range(BOARD_SIZE + 1):
|
293 |
+
draw.line([(i * CELL_SIZE, 0), (i * CELL_SIZE, BOARD_SIZE * CELL_SIZE)], fill=(0,0,0), width=1)
|
294 |
+
draw.line([(0, i * CELL_SIZE), (BOARD_SIZE * CELL_SIZE, i * CELL_SIZE)], fill=(0,0,0), width=1)
|
295 |
for x in range(BOARD_SIZE):
|
296 |
for y in range(BOARD_SIZE):
|
297 |
pos = (x, y)
|
|
|
304 |
fill = COLORS['escape']
|
305 |
if pos == selected_pos:
|
306 |
fill = COLORS['highlight']
|
307 |
+
draw.rectangle([(y * CELL_SIZE, x * CELL_SIZE), ((y + 1) * CELL_SIZE, (x + 1) * CELL_SIZE)], fill=fill)
|
|
|
308 |
for x in range(BOARD_SIZE):
|
309 |
for y in range(BOARD_SIZE):
|
310 |
piece = state.board[x, y]
|
311 |
if piece != EMPTY:
|
312 |
+
center = (y * CELL_SIZE + CELL_SIZE // 2, x * CELL_SIZE + CELL_SIZE // 2)
|
|
|
313 |
color = COLORS['white'] if piece == WHITE_SOLDIER else COLORS['black'] if piece == BLACK_SOLDIER else COLORS['king']
|
314 |
+
draw.ellipse([(center[0] - PIECE_RADIUS, center[1] - PIECE_RADIUS),
|
315 |
+
(center[0] + PIECE_RADIUS, center[1] + PIECE_RADIUS)], fill=color, outline=(0,0,0))
|
316 |
for i in range(BOARD_SIZE):
|
317 |
+
draw.text((5, i * CELL_SIZE + CELL_SIZE // 2 - 5), str(BOARD_SIZE - i), fill=(0,0,0))
|
318 |
+
draw.text((i * CELL_SIZE + CELL_SIZE // 2 - 5, BOARD_SIZE * CELL_SIZE - 15), chr(ord('A') + i), fill=(0,0,0))
|
319 |
+
return img
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
320 |
|
321 |
# Gradio interface functions
|
322 |
def click_board(state, selected_pos, evt: gr.SelectData):
|
323 |
if state.turn != 'WHITE':
|
324 |
+
return state, "It's the AI's turn", generate_board_image(state), selected_pos
|
325 |
+
x = evt.index[1] // CELL_SIZE # Image coordinates are (x, y), board is (row, col)
|
326 |
y = evt.index[0] // CELL_SIZE
|
327 |
pos = (x, y)
|
328 |
if selected_pos is None:
|
|
|
329 |
if state.board[pos] in [WHITE_SOLDIER, KING]:
|
330 |
+
return state, f"Selected {pos_to_coord(pos)}", generate_board_image(state, pos), pos
|
331 |
else:
|
332 |
+
return state, "Select a White piece or King", generate_board_image(state), None
|
333 |
else:
|
|
|
334 |
if is_valid_move(state, selected_pos, pos):
|
335 |
new_state = apply_move(state, selected_pos, pos)
|
336 |
status = check_game_status(new_state)
|
337 |
if status != "CONTINUE":
|
338 |
+
return new_state, status, generate_board_image(new_state), None
|
339 |
+
ai_state, ai_message = ai_move(new_state)
|
|
|
340 |
final_status = check_game_status(ai_state)
|
341 |
+
message = f"Your move to {pos_to_coord(pos)}. {ai_message}. {final_status if final_status != 'CONTINUE' else ''}"
|
342 |
+
return ai_state, message, generate_board_image(ai_state), None
|
343 |
else:
|
344 |
+
return state, "Invalid move", generate_board_image(state), None
|
345 |
|
346 |
def new_game():
|
347 |
state = TablutState()
|
348 |
+
return state, "New game started. Your turn (White).", generate_board_image(state), None
|
349 |
|
350 |
# Gradio interface
|
351 |
with gr.Blocks(title="Tablut Game") as demo:
|
352 |
state = gr.State()
|
353 |
selected_pos = gr.State(value=None)
|
354 |
+
board_image = gr.Image(label="Board", type="pil")
|
355 |
message_label = gr.Label(label="Message")
|
356 |
new_game_button = gr.Button("New Game")
|
357 |
+
board_image.select(fn=click_board, inputs=[state, selected_pos], outputs=[state, message_label, board_image, selected_pos])
|
358 |
+
new_game_button.click(fn=new_game, outputs=[state, message_label, board_image, selected_pos])
|
359 |
+
demo.load(fn=new_game, outputs=[state, message_label, board_image, selected_pos])
|
360 |
|
361 |
# Note: demo.launch() is not needed for HF Spaces
|