Marcel0123 commited on
Commit
3a9b614
·
verified ·
1 Parent(s): 37480e6

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +112 -56
app.py CHANGED
@@ -6,24 +6,36 @@ import chess
6
  import chess.svg
7
  import chess.engine
8
 
9
- # -------- Engine binary detection --------
10
- def find_stockfish() -> str | None:
11
- # 1) Environment variable
 
12
  env = os.environ.get("STOCKFISH_PATH")
13
- if env and os.path.isfile(env) and os.access(env, os.X_OK):
14
- return env
15
- # 2) PATH lookup
16
  which = shutil.which("stockfish")
17
  if which:
18
- return which
19
- # 3) Common locations on Debian/Ubuntu images
20
- for p in ("/usr/bin/stockfish", "/usr/games/stockfish", "/bin/stockfish", "/opt/conda/bin/stockfish"):
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  if os.path.isfile(p) and os.access(p, os.X_OK):
22
  return p
23
  return None
24
 
25
- STOCKFISH_PATH = find_stockfish()
26
-
27
  DEFAULT_THREADS = max(1, os.cpu_count() or 2)
28
  DEFAULT_HASH_MB = 512
29
  DEFAULT_MOVETIME_MS = 1500
@@ -37,14 +49,12 @@ def board_svg(board: chess.Board, last_move=None) -> str:
37
  size=520,
38
  )
39
 
40
- def new_engine(threads=DEFAULT_THREADS, hash_mb=DEFAULT_HASH_MB, ponder=False):
41
- if STOCKFISH_PATH is None:
42
- raise FileNotFoundError(
43
- "Stockfish niet gevonden. Voeg een 'apt.txt' toe met de regel 'stockfish' of "
44
- "stel een omgevingsvariabele STOCKFISH_PATH in naar de binary. "
45
- "Controleer ook dat de Space herbouwd is na het toevoegen van apt.txt."
46
- )
47
- engine = chess.engine.SimpleEngine.popen_uci(STOCKFISH_PATH)
48
  opts = {
49
  "Threads": int(threads),
50
  "Hash": int(hash_mb),
@@ -59,9 +69,17 @@ def new_engine(threads=DEFAULT_THREADS, hash_mb=DEFAULT_HASH_MB, ponder=False):
59
  print("UCI-opties zetten gaf een fout (doorgaan):", e)
60
  return engine
61
 
62
- def init_state(engine_color: str, movetime_ms: int, depth: int | None, threads: int, hash_mb: int):
63
  board = chess.Board()
64
- engine = new_engine(threads=threads, hash_mb=hash_mb)
 
 
 
 
 
 
 
 
65
  state = {
66
  "board": board,
67
  "engine": engine,
@@ -69,8 +87,11 @@ def init_state(engine_color: str, movetime_ms: int, depth: int | None, threads:
69
  "movetime_ms": movetime_ms,
70
  "depth": depth if depth and depth > 0 else None,
71
  "last_move": None,
 
 
72
  }
73
- if engine_color == "white":
 
74
  _ = engine_move(state)
75
  return state
76
 
@@ -83,26 +104,58 @@ def render(state):
83
  svg = board_svg(state["board"], last_move=state["last_move"])
84
  return svg, state["board"].fen()
85
 
86
- def new_game(engine_color, movetime_ms, depth, threads, hash_mb):
87
- try:
88
- state = init_state(engine_color, int(movetime_ms), int(depth) if depth else None, int(threads), int(hash_mb))
89
- img, fen = render(state)
90
- msg = f"Nieuwe partij gestart. Engine pad: {STOCKFISH_PATH or 'NIET GEVONDEN'}"
91
- return state, img, fen, msg
92
- except FileNotFoundError as e:
93
- dummy_state = {"board": chess.Board(), "engine": None, "engine_color": engine_color, "movetime_ms": movetime_ms, "depth": depth, "last_move": None}
94
- svg = board_svg(dummy_state["board"])
95
- details = (
96
- f" {e}\n\n"
97
- "Troubleshooting:\n"
98
- "• Controleer dat je Space type 'Gradio' is met apt-ondersteuning.\n"
99
- "• Voeg een bestand 'apt.txt' toe met exact deze inhoud:\n"
100
- " stockfish\n"
101
- "• Herstart/Herbouw de Space na uploaden.\n"
102
- "• Of stel een omgevingvariabele STOCKFISH_PATH in naar de binary.\n"
103
- "• Probeer handmatig: /usr/bin/stockfish of /usr/games/stockfish."
104
  )
105
- return dummy_state, svg, dummy_state["board"].fen(), details
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
 
107
  def make_move(state, user_move):
108
  board: chess.Board = state["board"]
@@ -124,7 +177,10 @@ def make_move(state, user_move):
124
 
125
  side_to_move = "white" if board.turn == chess.WHITE else "black"
126
  if side_to_move == state["engine_color"]:
127
- msg = engine_move(state)
 
 
 
128
  else:
129
  msg = "Jij bent aan zet."
130
  img, fen = render(state)
@@ -134,7 +190,7 @@ def engine_move(state):
134
  board: chess.Board = state["board"]
135
  engine: chess.engine.SimpleEngine = state["engine"]
136
  if engine is None:
137
- return "Engine niet beschikbaar (Stockfish niet gevonden)."
138
  if board.is_game_over():
139
  return f"Partij afgelopen: {board.result()}."
140
  try:
@@ -153,6 +209,7 @@ def undo_move(state):
153
  if len(board.move_stack) == 0:
154
  return state, *render(state), "Geen zetten om terug te nemen."
155
  board.pop()
 
156
  if state["engine_color"] == ("white" if board.turn == chess.WHITE else "black") and len(board.move_stack) > 0:
157
  board.pop()
158
  state["last_move"] = board.move_stack[-1] if board.move_stack else None
@@ -163,14 +220,7 @@ def resign(state):
163
  outcome = "1-0" if board.turn == chess.BLACK else "0-1"
164
  return state, *render(state), f"Opgegeven. Resultaat: {outcome}"
165
 
166
- def stop_engine(state):
167
- try:
168
- if state.get("engine"):
169
- state["engine"].quit()
170
- except Exception:
171
- pass
172
- return "Engine gestopt."
173
-
174
  CSS = """
175
  #board { display: flex; justify-content: center; }
176
  #board svg { max-width: 100%; height: auto; }
@@ -197,18 +247,24 @@ with gr.Blocks(title="Boss Chess (Stockfish)", css=CSS) as demo:
197
  depth = gr.Slider(0, 30, value=0, step=1, label="Max diepte (0=uit)")
198
  threads = gr.Slider(1, DEFAULT_THREADS, value=DEFAULT_THREADS, step=1, label="Threads")
199
  hash_mb = gr.Slider(64, 2048, value=DEFAULT_HASH_MB, step=64, label="Hash (MB)")
 
200
  btn_new = gr.Button("Nieuwe partij / herstart")
 
201
 
202
  state = gr.State()
203
 
204
- btn_new.click(new_game, [engine_color, movetime, depth, threads, hash_mb],
205
- [state, board_html, fen_out, status])
 
 
 
206
  btn_move.click(make_move, [state, user_move], [state, board_html, fen_out, status])
207
  btn_undo.click(undo_move, [state], [state, board_html, fen_out, status])
208
- btn_resign.click(resign, [state], [board_html, fen_out, status])
209
 
210
- demo.load(new_game, [engine_color, movetime, depth, threads, hash_mb],
211
- [state, board_html, fen_out, status])
 
212
 
213
  if __name__ == "__main__":
214
  demo.launch()
 
6
  import chess.svg
7
  import chess.engine
8
 
9
+ # ---------- Engine discovery ----------
10
+ def detect_stockfish_candidates():
11
+ cands = []
12
+ # env var
13
  env = os.environ.get("STOCKFISH_PATH")
14
+ if env:
15
+ cands.append(env)
16
+ # PATH
17
  which = shutil.which("stockfish")
18
  if which:
19
+ cands.append(which)
20
+ # common paths
21
+ cands += ["/usr/bin/stockfish", "/usr/games/stockfish", "/bin/stockfish", "/opt/conda/bin/stockfish"]
22
+ # de-dup while preserving order
23
+ seen = set(); uniq = []
24
+ for p in cands:
25
+ if p and p not in seen:
26
+ uniq.append(p); seen.add(p)
27
+ return uniq
28
+
29
+ def choose_stockfish_path(manual_path: str | None = None):
30
+ """Pick first executable path from manual path or detected candidates."""
31
+ if manual_path:
32
+ if os.path.isfile(manual_path) and os.access(manual_path, os.X_OK):
33
+ return manual_path
34
+ for p in detect_stockfish_candidates():
35
  if os.path.isfile(p) and os.access(p, os.X_OK):
36
  return p
37
  return None
38
 
 
 
39
  DEFAULT_THREADS = max(1, os.cpu_count() or 2)
40
  DEFAULT_HASH_MB = 512
41
  DEFAULT_MOVETIME_MS = 1500
 
49
  size=520,
50
  )
51
 
52
+ def new_engine(path: str, threads=DEFAULT_THREADS, hash_mb=DEFAULT_HASH_MB, ponder=False):
53
+ if not path:
54
+ raise FileNotFoundError("Geen Stockfish-pad opgegeven.")
55
+ if not (os.path.isfile(path) and os.access(path, os.X_OK)):
56
+ raise FileNotFoundError(f"Stockfish niet uitvoerbaar op pad: {path}")
57
+ engine = chess.engine.SimpleEngine.popen_uci(path)
 
 
58
  opts = {
59
  "Threads": int(threads),
60
  "Hash": int(hash_mb),
 
69
  print("UCI-opties zetten gaf een fout (doorgaan):", e)
70
  return engine
71
 
72
+ def init_state(engine_color: str, movetime_ms: int, depth: int | None, threads: int, hash_mb: int, engine_path: str | None):
73
  board = chess.Board()
74
+ # Probeer engine te starten, maar laat UI werken zonder crash
75
+ engine = None
76
+ picked = choose_stockfish_path(engine_path)
77
+ error = None
78
+ if picked:
79
+ try:
80
+ engine = new_engine(picked, threads=threads, hash_mb=hash_mb)
81
+ except Exception as e:
82
+ error = str(e)
83
  state = {
84
  "board": board,
85
  "engine": engine,
 
87
  "movetime_ms": movetime_ms,
88
  "depth": depth if depth and depth > 0 else None,
89
  "last_move": None,
90
+ "engine_path": picked or (engine_path or ""),
91
+ "engine_error": error,
92
  }
93
+ # Alleen automatisch zetten als engine beschikbaar is én engine wit is
94
+ if state["engine"] is not None and engine_color == "white":
95
  _ = engine_move(state)
96
  return state
97
 
 
104
  svg = board_svg(state["board"], last_move=state["last_move"])
105
  return svg, state["board"].fen()
106
 
107
+ # --------- Actions ---------
108
+ def new_game(engine_color, movetime_ms, depth, threads, hash_mb, engine_path):
109
+ state = init_state(engine_color, int(movetime_ms), int(depth) if depth else None, int(threads), int(hash_mb), engine_path.strip() or None)
110
+ img, fen = render(state)
111
+ if state["engine"]:
112
+ msg = f"Nieuwe partij gestart. Engine pad: {state['engine_path']}"
113
+ else:
114
+ diag = diagnostics() # show where we looked
115
+ msg = (
116
+ "Nieuwe partij gestart ZONDER engine. "
117
+ "Vul het correcte pad in en klik 'Herstart engine'.\n\n"
118
+ + diag
 
 
 
 
 
 
119
  )
120
+ return state, img, fen, msg, state["engine_path"]
121
+
122
+ def diagnostics():
123
+ lines = ["🔎 Stockfish-detectie:"]
124
+ for p in detect_stockfish_candidates():
125
+ ok = "✅" if os.path.isfile(p) and os.access(p, os.X_OK) else "❌"
126
+ lines.append(f"{ok} {p}")
127
+ env = os.environ.get("STOCKFISH_PATH")
128
+ if env:
129
+ lines.append(f"Env STOCKFISH_PATH={env}")
130
+ lines.append("\nTip: zorg dat je in de Space een bestand 'apt.txt' hebt met:\nstockfish\n"
131
+ "en rebuild de Space. Of zet Settings → Variables → STOCKFISH_PATH naar bv. /usr/games/stockfish.")
132
+ return "\n".join(lines)
133
+
134
+ def restart_engine(state, threads, hash_mb, engine_path):
135
+ # sluit oude engine
136
+ try:
137
+ if state.get("engine"):
138
+ state["engine"].quit()
139
+ except Exception:
140
+ pass
141
+ # start nieuwe
142
+ picked = choose_stockfish_path(engine_path.strip() or None)
143
+ if not picked:
144
+ state["engine"] = None
145
+ state["engine_error"] = "Geen uitvoerbare Stockfish gevonden."
146
+ msg = "❌ Geen engine gevonden.\n" + diagnostics()
147
+ else:
148
+ try:
149
+ state["engine"] = new_engine(picked, threads=int(threads), hash_mb=int(hash_mb))
150
+ state["engine_error"] = None
151
+ state["engine_path"] = picked
152
+ msg = f"✅ Engine gestart op: {picked}"
153
+ except Exception as e:
154
+ state["engine"] = None
155
+ state["engine_error"] = str(e)
156
+ msg = f"❌ Engine-fout: {e}\n" + diagnostics()
157
+ img, fen = render(state)
158
+ return state, img, fen, msg, state.get("engine_path","")
159
 
160
  def make_move(state, user_move):
161
  board: chess.Board = state["board"]
 
177
 
178
  side_to_move = "white" if board.turn == chess.WHITE else "black"
179
  if side_to_move == state["engine_color"]:
180
+ if state["engine"] is None:
181
+ msg = "Engine niet beschikbaar — vul 'Engine pad' in en klik 'Herstart engine'."
182
+ else:
183
+ msg = engine_move(state)
184
  else:
185
  msg = "Jij bent aan zet."
186
  img, fen = render(state)
 
190
  board: chess.Board = state["board"]
191
  engine: chess.engine.SimpleEngine = state["engine"]
192
  if engine is None:
193
+ return "Engine niet beschikbaar."
194
  if board.is_game_over():
195
  return f"Partij afgelopen: {board.result()}."
196
  try:
 
209
  if len(board.move_stack) == 0:
210
  return state, *render(state), "Geen zetten om terug te nemen."
211
  board.pop()
212
+ # Neem er eentje extra terug als engine aan zet zou komen
213
  if state["engine_color"] == ("white" if board.turn == chess.WHITE else "black") and len(board.move_stack) > 0:
214
  board.pop()
215
  state["last_move"] = board.move_stack[-1] if board.move_stack else None
 
220
  outcome = "1-0" if board.turn == chess.BLACK else "0-1"
221
  return state, *render(state), f"Opgegeven. Resultaat: {outcome}"
222
 
223
+ # ---------- UI ----------
 
 
 
 
 
 
 
224
  CSS = """
225
  #board { display: flex; justify-content: center; }
226
  #board svg { max-width: 100%; height: auto; }
 
247
  depth = gr.Slider(0, 30, value=0, step=1, label="Max diepte (0=uit)")
248
  threads = gr.Slider(1, DEFAULT_THREADS, value=DEFAULT_THREADS, step=1, label="Threads")
249
  hash_mb = gr.Slider(64, 2048, value=DEFAULT_HASH_MB, step=64, label="Hash (MB)")
250
+ engine_path = gr.Textbox(label="Engine pad (optioneel, bv. /usr/games/stockfish)", placeholder="/usr/games/stockfish", value="")
251
  btn_new = gr.Button("Nieuwe partij / herstart")
252
+ btn_restart = gr.Button("Herstart engine")
253
 
254
  state = gr.State()
255
 
256
+ # Bindings
257
+ btn_new.click(new_game, [engine_color, movetime, depth, threads, hash_mb, engine_path],
258
+ [state, board_html, fen_out, status, engine_path])
259
+ btn_restart.click(restart_engine, [state, threads, hash_mb, engine_path],
260
+ [state, board_html, fen_out, status, engine_path])
261
  btn_move.click(make_move, [state, user_move], [state, board_html, fen_out, status])
262
  btn_undo.click(undo_move, [state], [state, board_html, fen_out, status])
263
+ btn_resign.click(resign, [state], [state, board_html, fen_out, status])
264
 
265
+ # Auto-start
266
+ demo.load(new_game, [engine_color, movetime, depth, threads, hash_mb, engine_path],
267
+ [state, board_html, fen_out, status, engine_path])
268
 
269
  if __name__ == "__main__":
270
  demo.launch()