system HF Staff commited on
Commit
c5a3c49
·
verified ·
1 Parent(s): 2377b60

Deploy from efd0caf

Browse files
Files changed (1) hide show
  1. app.py +44 -26
app.py CHANGED
@@ -12,6 +12,7 @@ Weights are resolved in this order:
12
 
13
  from __future__ import annotations
14
 
 
15
  import os
16
  import sys
17
  from pathlib import Path
@@ -136,13 +137,13 @@ def _bot_move(board: chess.Board, sims: int):
136
 
137
  def on_move(fen: str, sims: int, human_color: str):
138
  """Human just moved; reply with the bot's move (whichever side is to move).
 
139
 
140
  A generator: first yields a "thinking" status (so the player sees the bot is
141
- working during the ~1-3s CPU search), then yields the bot's reply. The board
142
- is shown from White's side, so the eval bar is White-oriented."""
143
  board = chess.Board(fen)
144
  if board.is_game_over():
145
- yield board.fen(), _result_text(board, human_color), eval_bar_html(board, "white")
146
  return
147
 
148
  # Immediate feedback while the search runs (board/eval unchanged for now).
@@ -155,14 +156,21 @@ def on_move(fen: str, sims: int, human_color: str):
155
  else:
156
  eval_str = f" (eval {value:+.2f})" if value is not None else ""
157
  status = f"Bot played **{san}**{eval_str}. Your move."
158
- yield board.fen(), status, eval_bar_html(board, "white")
 
 
 
 
 
 
159
 
160
 
161
  def new_game(play_as: str, sims: int):
162
  """Start a fresh game. If the human plays Black, the bot (White) opens.
163
 
164
- Returns (fen, status, eval_bar_html, human_color) written straight to the
165
- statically-mounted board so a same-colour replay still resets it."""
 
166
  human_color = "black" if play_as == "Black" else "white"
167
  board = chess.Board()
168
  if human_color == "black":
@@ -170,7 +178,9 @@ def new_game(play_as: str, sims: int):
170
  status = f"New game — you are **Black**. Bot opened with **{san}**. Your move."
171
  else:
172
  status = "New game — you are **White**. Make your move."
173
- return board.fen(), status, eval_bar_html(board, "white"), human_color
 
 
174
 
175
 
176
  with gr.Blocks(title="ChessTransformer", theme=gr.themes.Soft()) as demo:
@@ -181,29 +191,23 @@ with gr.Blocks(title="ChessTransformer", theme=gr.themes.Soft()) as demo:
181
  "It reaches **~2100 Elo** vs Stockfish at full strength; this CPU demo "
182
  "runs at a lower sim count so moves come back quickly.\n\n"
183
  "**Pick your color and hit New game.** Drag a piece to move — the bot "
184
- "replies automatically. (The board is shown from White's side.) "
185
  "[GitHub repo →](https://github.com/tchauffi/ChessTransformer)"
186
  )
187
- human_color = gr.State("white")
 
188
 
189
  with gr.Row():
190
- # Solid min_widths so the board container always has a size at mount —
191
- # the chessboard.js component reloads the page if it inits at 0 width.
 
192
  eval_col = gr.Column(scale=0, min_width=44)
193
- board_col = gr.Column(scale=3, min_width=320)
194
  ctrl_col = gr.Column(scale=1, min_width=200)
195
 
196
  with eval_col:
197
  eval_bar = gr.HTML(eval_bar_html(chess.Board(), "white"), visible=False)
198
 
199
- # Board mounted statically (not in a gr.render) so it initializes once on the
200
- # laid-out page — the dynamic remount was failing on mobile and triggering the
201
- # component's window.location.reload() loop.
202
- with board_col:
203
- board = Chessboard(value=START_FEN, game_mode=True, orientation="white",
204
- label="Drag to move", min_width=320)
205
- status = gr.Markdown("You are **White**. Make your move.")
206
-
207
  with ctrl_col:
208
  play_as = gr.Radio(["White", "Black"], value="White", label="Play as")
209
  sims = gr.Slider(32, 1200, value=DEFAULT_SIMS, step=8,
@@ -212,13 +216,27 @@ with gr.Blocks(title="ChessTransformer", theme=gr.themes.Soft()) as demo:
212
  show_eval = gr.Checkbox(False, label="Show evaluation bar")
213
  new_btn = gr.Button("New game", variant="primary")
214
 
215
- # on_move is a generator streams the "thinking" status; show_progress hidden
216
- # so Gradio's overlay doesn't sit over the board.
217
- board.move(on_move, inputs=[board, sims, human_color],
218
- outputs=[board, status, eval_bar], show_progress="hidden")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
219
  show_eval.change(lambda show: gr.update(visible=show), inputs=show_eval, outputs=eval_bar)
220
- new_btn.click(new_game, inputs=[play_as, sims],
221
- outputs=[board, status, eval_bar, human_color])
222
 
223
 
224
  if __name__ == "__main__":
 
12
 
13
  from __future__ import annotations
14
 
15
+ import itertools
16
  import os
17
  import sys
18
  from pathlib import Path
 
137
 
138
  def on_move(fen: str, sims: int, human_color: str):
139
  """Human just moved; reply with the bot's move (whichever side is to move).
140
+ The board orientation matches the human's color, so the eval bar does too.
141
 
142
  A generator: first yields a "thinking" status (so the player sees the bot is
143
+ working during the ~1-3s CPU search), then yields the bot's reply."""
 
144
  board = chess.Board(fen)
145
  if board.is_game_over():
146
+ yield board.fen(), _result_text(board, human_color), eval_bar_html(board, human_color)
147
  return
148
 
149
  # Immediate feedback while the search runs (board/eval unchanged for now).
 
156
  else:
157
  eval_str = f" (eval {value:+.2f})" if value is not None else ""
158
  status = f"Bot played **{san}**{eval_str}. Your move."
159
+ yield board.fen(), status, eval_bar_html(board, human_color)
160
+
161
+
162
+ # Monotonic id so every New game yields a *distinct* setup dict — otherwise a
163
+ # same-colour replay returns an identical dict and the gr.render block, seeing no
164
+ # change, wouldn't rebuild (and reset) the board.
165
+ _game_counter = itertools.count(1)
166
 
167
 
168
  def new_game(play_as: str, sims: int):
169
  """Start a fresh game. If the human plays Black, the bot (White) opens.
170
 
171
+ Returns (setup, eval_bar). The setup dict drives a gr.render block — changing
172
+ it rebuilds the board so its orientation (chessboard.js reads it only at
173
+ construction) actually flips to the human's side."""
174
  human_color = "black" if play_as == "Black" else "white"
175
  board = chess.Board()
176
  if human_color == "black":
 
178
  status = f"New game — you are **Black**. Bot opened with **{san}**. Your move."
179
  else:
180
  status = "New game — you are **White**. Make your move."
181
+ setup = {"fen": board.fen(), "orientation": human_color, "color": human_color,
182
+ "status": status, "nonce": next(_game_counter)}
183
+ return setup, eval_bar_html(board, human_color)
184
 
185
 
186
  with gr.Blocks(title="ChessTransformer", theme=gr.themes.Soft()) as demo:
 
191
  "It reaches **~2100 Elo** vs Stockfish at full strength; this CPU demo "
192
  "runs at a lower sim count so moves come back quickly.\n\n"
193
  "**Pick your color and hit New game.** Drag a piece to move — the bot "
194
+ "replies automatically. "
195
  "[GitHub repo →](https://github.com/tchauffi/ChessTransformer)"
196
  )
197
+ setup = gr.State({"fen": START_FEN, "orientation": "white", "color": "white",
198
+ "status": "You are **White**. Make your move.", "nonce": 0})
199
 
200
  with gr.Row():
201
+ # Solid min_widths so the board container always has a size when the
202
+ # gr.render block (re)mounts it — chessboard.js reloads the whole page if
203
+ # it inits at 0 width, which is what caused the mobile flicker/reload loop.
204
  eval_col = gr.Column(scale=0, min_width=44)
205
+ board_col = gr.Column(scale=3, min_width=340)
206
  ctrl_col = gr.Column(scale=1, min_width=200)
207
 
208
  with eval_col:
209
  eval_bar = gr.HTML(eval_bar_html(chess.Board(), "white"), visible=False)
210
 
 
 
 
 
 
 
 
 
211
  with ctrl_col:
212
  play_as = gr.Radio(["White", "Black"], value="White", label="Play as")
213
  sims = gr.Slider(32, 1200, value=DEFAULT_SIMS, step=8,
 
216
  show_eval = gr.Checkbox(False, label="Show evaluation bar")
217
  new_btn = gr.Button("New game", variant="primary")
218
 
219
+ # The board lives in a gr.render so switching color rebuilds it with the new
220
+ # orientation (chessboard.js reads orientation only at construction). The
221
+ # min_width above keeps the container sized so the remount can't init at 0.
222
+ with board_col:
223
+ @gr.render(inputs=setup)
224
+ def render_board(cfg):
225
+ board = Chessboard(value=cfg["fen"], game_mode=True, min_width=320,
226
+ orientation=cfg["orientation"], label="Drag to move")
227
+ status = gr.Markdown(cfg["status"])
228
+ color = cfg["color"]
229
+
230
+ def handle(fen, s): # generator → streams the "thinking" update first
231
+ yield from on_move(fen, s, color)
232
+
233
+ # show_progress="hidden": our "⏳ thinking…" status is the signal, so
234
+ # Gradio's default overlay doesn't sit over the board.
235
+ board.move(handle, inputs=[board, sims], outputs=[board, status, eval_bar],
236
+ show_progress="hidden")
237
+
238
  show_eval.change(lambda show: gr.update(visible=show), inputs=show_eval, outputs=eval_bar)
239
+ new_btn.click(new_game, inputs=[play_as, sims], outputs=[setup, eval_bar])
 
240
 
241
 
242
  if __name__ == "__main__":