nuriyev commited on
Commit
292c059
·
1 Parent(s): 0732c11

removed unnecessary css

Browse files
Files changed (1) hide show
  1. app.py +74 -269
app.py CHANGED
@@ -154,65 +154,55 @@ def get_model_move(fen: str) -> tuple[str, str, str]:
154
  return uci_move, reasoning, raw_output
155
 
156
 
157
- # ============================================================================
158
- # Game State
159
- # ============================================================================
160
-
161
- def create_initial_state():
162
- return {
163
- "board": chess.Board(),
164
- "history": [],
165
- "last_reasoning": "",
166
- "last_raw_output": "",
167
- "game_over": False,
168
- "result": "",
169
- }
170
-
171
-
172
  # ============================================================================
173
  # Game Logic
174
  # ============================================================================
175
 
176
- def make_player_move(fen: str, state: dict) -> tuple[str, dict, str, str, str]:
177
- """Handle player's move from the chessboard."""
178
- if state["game_over"]:
179
- return state["board"].fen(), state, get_status(state), state["last_reasoning"], state["last_raw_output"]
180
-
181
  board = chess.Board(fen)
182
- state["board"] = board
183
- state["history"].append(fen)
184
 
185
- # Check if game is over after player move
186
  if board.is_game_over():
187
- state["game_over"] = True
188
- state["result"] = get_game_result(board)
189
- return board.fen(), state, get_status(state), state["last_reasoning"], state["last_raw_output"]
190
 
191
- # AI's turn (Black)
192
- if not board.turn: # Black's turn
193
- uci_move, reasoning, raw_output = get_model_move(board.fen())
194
- state["last_reasoning"] = reasoning
195
- state["last_raw_output"] = raw_output
196
 
197
  if uci_move:
198
  try:
199
  move = chess.Move.from_uci(uci_move)
200
  if move in board.legal_moves:
201
  board.push(move)
202
- state["board"] = board
203
- state["history"].append(board.fen())
204
  else:
205
- # Try to find a legal move that starts with the same piece
206
- state["last_reasoning"] = f"Model suggested illegal move: {uci_move}. " + reasoning
207
  except:
208
- state["last_reasoning"] = f"Model output invalid move format: {uci_move}. " + reasoning
209
 
210
  # Check if game is over after AI move
211
  if board.is_game_over():
212
- state["game_over"] = True
213
- state["result"] = get_game_result(board)
 
 
 
 
 
214
 
215
- return board.fen(), state, get_status(state), state["last_reasoning"], state["last_raw_output"]
 
 
 
 
 
 
 
 
216
 
217
 
218
  def get_game_result(board: chess.Board) -> str:
@@ -221,262 +211,77 @@ def get_game_result(board: chess.Board) -> str:
221
  winner = "Black" if board.turn else "White"
222
  return f"Checkmate! {winner} wins!"
223
  elif board.is_stalemate():
224
- return "Stalemate! It's a draw."
225
  elif board.is_insufficient_material():
226
- return "Draw by insufficient material."
227
  elif board.is_fifty_moves():
228
- return "Draw by fifty-move rule."
229
  elif board.is_repetition():
230
- return "Draw by repetition."
231
  return "Game Over"
232
 
233
 
234
- def get_status(state: dict) -> str:
235
- """Get current game status."""
236
- if state["game_over"]:
237
- return f"🏁 {state['result']}"
238
-
239
- board = state["board"]
240
- turn = "White (You)" if board.turn else "Black (AI)"
241
-
242
- status = f"**Turn:** {turn}"
243
- if board.is_check():
244
- status += " ⚠️ **CHECK!**"
245
-
246
- move_count = len(state["history"])
247
- status += f"\n**Move:** {move_count // 2 + 1}"
248
 
249
- return status
250
 
251
-
252
- def new_game() -> tuple[str, dict, str, str, str]:
253
- """Start a new game."""
254
- state = create_initial_state()
255
- return state["board"].fen(), state, get_status(state), "", ""
256
-
257
-
258
- def ai_first_move(state: dict) -> tuple[str, dict, str, str, str]:
259
- """Let AI make the first move (play as Black)."""
260
- board = state["board"]
261
-
262
- if len(state["history"]) > 0:
263
- return board.fen(), state, get_status(state) + "\n⚠️ Game already started!", state["last_reasoning"], state["last_raw_output"]
264
-
265
- uci_move, reasoning, raw_output = get_model_move(board.fen())
266
- state["last_reasoning"] = reasoning
267
- state["last_raw_output"] = raw_output
268
-
269
- if uci_move:
270
- try:
271
- move = chess.Move.from_uci(uci_move)
272
- if move in board.legal_moves:
273
- board.push(move)
274
- state["board"] = board
275
- state["history"].append(board.fen())
276
- except:
277
- pass
278
-
279
- return board.fen(), state, get_status(state), reasoning, raw_output
280
 
281
 
282
  # ============================================================================
283
- # Custom CSS for chess.com-like appearance
284
  # ============================================================================
285
 
286
- CUSTOM_CSS = """
287
- /* Main container */
288
- .gradio-container {
289
- max-width: 1200px !important;
290
- margin: auto !important;
291
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif !important;
292
- }
293
-
294
- /* Header styling */
295
- .header-title {
296
- text-align: center;
297
- color: #769656;
298
- font-size: 2.5em;
299
- font-weight: bold;
300
- margin-bottom: 0.2em;
301
- text-shadow: 2px 2px 4px rgba(0,0,0,0.1);
302
- }
303
-
304
- .header-subtitle {
305
- text-align: center;
306
- color: #666;
307
- font-size: 1.1em;
308
- margin-bottom: 1em;
309
- }
310
-
311
- /* Game panel */
312
- .game-panel {
313
- background: linear-gradient(145deg, #312e2b, #272522);
314
- border-radius: 12px;
315
- padding: 20px;
316
- box-shadow: 0 8px 32px rgba(0,0,0,0.3);
317
- }
318
-
319
- /* Status box */
320
- .status-box {
321
- background: #1a1916;
322
- border-radius: 8px;
323
- padding: 15px;
324
- color: #fff;
325
- font-size: 1.1em;
326
- border-left: 4px solid #769656;
327
- }
328
-
329
- /* Reasoning box */
330
- .reasoning-box {
331
- background: #262421;
332
- border-radius: 8px;
333
- padding: 15px;
334
- color: #bababa;
335
- font-family: 'Courier New', monospace;
336
- font-size: 0.95em;
337
- max-height: 200px;
338
- overflow-y: auto;
339
- }
340
-
341
- /* Buttons */
342
- .game-button {
343
- background: #769656 !important;
344
- color: white !important;
345
- border: none !important;
346
- border-radius: 6px !important;
347
- padding: 10px 20px !important;
348
- font-weight: bold !important;
349
- transition: all 0.2s ease !important;
350
- }
351
-
352
- .game-button:hover {
353
- background: #8bac6a !important;
354
- transform: translateY(-1px) !important;
355
- }
356
-
357
- .secondary-button {
358
- background: #4a4745 !important;
359
- color: #bababa !important;
360
- }
361
-
362
- .secondary-button:hover {
363
- background: #5a5755 !important;
364
- }
365
 
366
- /* Accordion */
367
- .reasoning-accordion {
368
- background: #1a1916 !important;
369
- border: 1px solid #333 !important;
370
- border-radius: 8px !important;
371
- }
372
 
373
- /* Footer */
374
- .footer-text {
375
- text-align: center;
376
- color: #666;
377
- font-size: 0.9em;
378
- margin-top: 1em;
379
- }
380
- """
381
 
382
- # ============================================================================
383
- # Gradio Interface
384
- # ============================================================================
385
-
386
- with gr.Blocks(css=CUSTOM_CSS, title="Chess Reasoner", theme=gr.themes.Soft(
387
- primary_hue="green",
388
- secondary_hue="gray",
389
- neutral_hue="gray",
390
- )) as demo:
391
 
392
- # State
393
- game_state = gr.State(create_initial_state)
 
394
 
395
- # Header
396
- gr.HTML("""
397
- <div class="header-title">♟️ Chess Reasoner</div>
398
- <div class="header-subtitle">Play chess against a reasoning AI • You play as White</div>
399
- """)
400
 
401
- with gr.Row():
402
- # Left: Chessboard
403
- with gr.Column(scale=3):
404
- chessboard = Chessboard(
405
- value=chess.STARTING_FEN,
406
- label="",
407
- interactive=True,
408
- )
409
-
410
- # Right: Game controls and info
411
- with gr.Column(scale=2):
412
- with gr.Group(elem_classes="game-panel"):
413
- # Status
414
- gr.Markdown("### 📊 Game Status")
415
- status_display = gr.Markdown(
416
- value="**Turn:** White (You)\n**Move:** 1",
417
- elem_classes="status-box"
418
- )
419
-
420
- gr.Markdown("---")
421
-
422
- # Controls
423
- with gr.Row():
424
- new_game_btn = gr.Button(
425
- "🔄 New Game", elem_classes="game-button", size="lg")
426
- ai_first_btn = gr.Button(
427
- "🤖 AI First", elem_classes="secondary-button", size="lg")
428
-
429
- gr.Markdown("---")
430
-
431
- # AI Reasoning (collapsible)
432
- with gr.Accordion("🧠 AI Reasoning", open=True, elem_classes="reasoning-accordion"):
433
- reasoning_display = gr.Textbox(
434
- value="",
435
- label="Thinking",
436
- lines=3,
437
- interactive=False,
438
- elem_classes="reasoning-box"
439
- )
440
-
441
- with gr.Accordion("📝 Raw Output", open=False):
442
- raw_output_display = gr.Textbox(
443
- value="",
444
- label="Model Output",
445
- lines=5,
446
- interactive=False,
447
- elem_classes="reasoning-box"
448
- )
449
-
450
- # Footer
451
- gr.HTML("""
452
- <div class="footer-text">
453
- Model: <a href="https://huggingface.co/nuriyev/chess-reasoner" target="_blank">nuriyev/chess-reasoner</a>
454
- • Fine-tuned from Qwen3-4B-Instruct • SFT Phase 1
455
- </div>
456
  """)
457
 
458
- # Event handlers
459
- chessboard.change(
460
- fn=make_player_move,
461
- inputs=[chessboard, game_state],
462
- outputs=[chessboard, game_state, status_display,
463
- reasoning_display, raw_output_display],
464
  )
465
 
466
- new_game_btn.click(
467
- fn=new_game,
468
- inputs=[],
469
- outputs=[chessboard, game_state, status_display,
470
- reasoning_display, raw_output_display],
471
  )
472
 
473
  ai_first_btn.click(
474
- fn=ai_first_move,
475
- inputs=[game_state],
476
- outputs=[chessboard, game_state, status_display,
477
- reasoning_display, raw_output_display],
478
  )
479
 
480
-
481
  if __name__ == "__main__":
482
- demo.launch()
 
154
  return uci_move, reasoning, raw_output
155
 
156
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
  # ============================================================================
158
  # Game Logic
159
  # ============================================================================
160
 
161
+ def play_move(fen: str) -> tuple[str, str, str, str]:
162
+ """
163
+ Process the position after player's move and get AI response.
164
+ Returns: (new_fen, status, reasoning, raw_output)
165
+ """
166
  board = chess.Board(fen)
 
 
167
 
168
+ # Check if game is over
169
  if board.is_game_over():
170
+ result = get_game_result(board)
171
+ return fen, f"🏁 {result}", "", ""
 
172
 
173
+ # If it's black's turn (AI), make a move
174
+ if not board.turn:
175
+ uci_move, reasoning, raw_output = get_model_move(fen)
 
 
176
 
177
  if uci_move:
178
  try:
179
  move = chess.Move.from_uci(uci_move)
180
  if move in board.legal_moves:
181
  board.push(move)
 
 
182
  else:
183
+ reasoning = f"⚠️ Model suggested illegal move: {uci_move}. " + reasoning
 
184
  except:
185
+ reasoning = f"⚠️ Model output invalid move: {uci_move}. " + reasoning
186
 
187
  # Check if game is over after AI move
188
  if board.is_game_over():
189
+ result = get_game_result(board)
190
+ return board.fen(), f"🏁 {result}", reasoning, raw_output
191
+
192
+ turn_str = "White (You)" if board.turn else "Black (AI)"
193
+ status = f"**Turn:** {turn_str}"
194
+ if board.is_check():
195
+ status += " ⚠️ CHECK!"
196
 
197
+ return board.fen(), status, reasoning, raw_output
198
+
199
+ # White's turn - just return current state
200
+ turn_str = "White (You)" if board.turn else "Black (AI)"
201
+ status = f"**Turn:** {turn_str}"
202
+ if board.is_check():
203
+ status += " ⚠️ CHECK!"
204
+
205
+ return fen, status, "", ""
206
 
207
 
208
  def get_game_result(board: chess.Board) -> str:
 
211
  winner = "Black" if board.turn else "White"
212
  return f"Checkmate! {winner} wins!"
213
  elif board.is_stalemate():
214
+ return "Stalemate - Draw"
215
  elif board.is_insufficient_material():
216
+ return "Draw - Insufficient material"
217
  elif board.is_fifty_moves():
218
+ return "Draw - 50 move rule"
219
  elif board.is_repetition():
220
+ return "Draw - Repetition"
221
  return "Game Over"
222
 
223
 
224
+ def reset_game() -> tuple[str, str, str, str]:
225
+ """Reset to starting position."""
226
+ return chess.STARTING_FEN, "**Turn:** White (You)", "", ""
 
 
 
 
 
 
 
 
 
 
 
227
 
 
228
 
229
+ def ai_plays_first() -> tuple[str, str, str, str]:
230
+ """Let AI make the opening move."""
231
+ return play_move(chess.STARTING_FEN)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
232
 
233
 
234
  # ============================================================================
235
+ # Gradio Interface
236
  # ============================================================================
237
 
238
+ with gr.Blocks(title="♟️ Chess Reasoner") as demo:
239
+ gr.Markdown("""
240
+ # ♟️ Chess Reasoner
241
+ Play chess against a reasoning AI! You play as **White** - click on pieces to move them.
242
+ """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243
 
244
+ with gr.Row():
245
+ with gr.Column(scale=2):
246
+ board = Chessboard(value=chess.STARTING_FEN,
247
+ label="", game_mode=True)
 
 
248
 
249
+ with gr.Column(scale=1):
250
+ status = gr.Markdown(value="**Turn:** White (You)")
 
 
 
 
 
 
251
 
252
+ with gr.Row():
253
+ reset_btn = gr.Button("🔄 New Game", variant="primary")
254
+ ai_first_btn = gr.Button("🤖 AI First")
 
 
 
 
 
 
255
 
256
+ with gr.Accordion("🧠 AI Reasoning", open=True):
257
+ reasoning = gr.Textbox(
258
+ label="Thinking", lines=3, interactive=False)
259
 
260
+ with gr.Accordion("📝 Raw Output", open=False):
261
+ raw_output = gr.Textbox(
262
+ label="Model Output", lines=5, interactive=False)
 
 
263
 
264
+ gr.Markdown("""
265
+ ---
266
+ **Model:** [nuriyev/chess-reasoner](https://huggingface.co/nuriyev/chess-reasoner) • Fine-tuned from Qwen3-4B-Instruct
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
267
  """)
268
 
269
+ # Events
270
+ board.change(
271
+ fn=play_move,
272
+ inputs=[board],
273
+ outputs=[board, status, reasoning, raw_output]
 
274
  )
275
 
276
+ reset_btn.click(
277
+ fn=reset_game,
278
+ outputs=[board, status, reasoning, raw_output]
 
 
279
  )
280
 
281
  ai_first_btn.click(
282
+ fn=ai_plays_first,
283
+ outputs=[board, status, reasoning, raw_output]
 
 
284
  )
285
 
 
286
  if __name__ == "__main__":
287
+ demo.launch(ssr_mode=False)