Spaces:
Sleeping
Sleeping
File size: 10,362 Bytes
a7bdaaf 37480e6 a7bdaaf 3a9b614 37480e6 3a9b614 37480e6 3a9b614 37480e6 a7bdaaf 37480e6 a7bdaaf 59e6a95 a7bdaaf 3a9b614 a7bdaaf 3a9b614 a7bdaaf 3a9b614 a7bdaaf 37480e6 a7bdaaf 3a9b614 a7bdaaf 3a9b614 a7bdaaf 4dae495 59e6a95 a7bdaaf 3a9b614 37480e6 3a9b614 a7bdaaf 3a9b614 a7bdaaf 37480e6 3a9b614 a7bdaaf 3a9b614 a7bdaaf 3a9b614 59e6a95 a7bdaaf 59e6a95 a7bdaaf 3a9b614 a7bdaaf 3a9b614 a7bdaaf 3a9b614 a7bdaaf 3a9b614 a7bdaaf 3a9b614 a7bdaaf |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 |
# app.py
import os
import shutil
import gradio as gr
import chess
import chess.svg
import chess.engine
# ---------- Engine discovery ----------
def detect_stockfish_candidates():
cands = []
# env var
env = os.environ.get("STOCKFISH_PATH")
if env:
cands.append(env)
# PATH
which = shutil.which("stockfish")
if which:
cands.append(which)
# common paths
cands += ["/usr/bin/stockfish", "/usr/games/stockfish", "/bin/stockfish", "/opt/conda/bin/stockfish"]
# de-dup while preserving order
seen = set(); uniq = []
for p in cands:
if p and p not in seen:
uniq.append(p); seen.add(p)
return uniq
def choose_stockfish_path(manual_path: str | None = None):
"""Pick first executable path from manual path or detected candidates."""
if manual_path:
if os.path.isfile(manual_path) and os.access(manual_path, os.X_OK):
return manual_path
for p in detect_stockfish_candidates():
if os.path.isfile(p) and os.access(p, os.X_OK):
return p
return None
DEFAULT_THREADS = max(1, os.cpu_count() or 2)
DEFAULT_HASH_MB = 512
DEFAULT_MOVETIME_MS = 1500
def board_svg(board: chess.Board, last_move=None) -> str:
return chess.svg.board(
board=board,
lastmove=last_move,
check=board.is_check(),
coordinates=True,
size=520,
)
def new_engine(path: str, threads=DEFAULT_THREADS, hash_mb=DEFAULT_HASH_MB, ponder=False):
if not path:
raise FileNotFoundError("Geen Stockfish-pad opgegeven.")
if not (os.path.isfile(path) and os.access(path, os.X_OK)):
raise FileNotFoundError(f"Stockfish niet uitvoerbaar op pad: {path}")
engine = chess.engine.SimpleEngine.popen_uci(path)
opts = {
"Threads": int(threads),
"Hash": int(hash_mb),
"UCI_LimitStrength": False,
"Skill Level": 20,
"Ponder": ponder,
"Move Overhead": 30,
}
try:
engine.configure(opts)
except Exception as e:
print("UCI-opties zetten gaf een fout (doorgaan):", e)
return engine
def init_state(engine_color: str, movetime_ms: int, depth: int | None, threads: int, hash_mb: int, engine_path: str | None):
board = chess.Board()
# Probeer engine te starten, maar laat UI werken zonder crash
engine = None
picked = choose_stockfish_path(engine_path)
error = None
if picked:
try:
engine = new_engine(picked, threads=threads, hash_mb=hash_mb)
except Exception as e:
error = str(e)
state = {
"board": board,
"engine": engine,
"engine_color": engine_color,
"movetime_ms": movetime_ms,
"depth": depth if depth and depth > 0 else None,
"last_move": None,
"engine_path": picked or (engine_path or ""),
"engine_error": error,
}
# Alleen automatisch zetten als engine beschikbaar is Γ©n engine wit is
if state["engine"] is not None and engine_color == "white":
_ = engine_move(state)
return state
def engine_limit(state):
if state["depth"] is not None:
return chess.engine.Limit(depth=int(state["depth"]))
return chess.engine.Limit(time=max(0.05, state["movetime_ms"]/1000.0))
def render(state):
svg = board_svg(state["board"], last_move=state["last_move"])
return svg, state["board"].fen()
# --------- Actions ---------
def new_game(engine_color, movetime_ms, depth, threads, hash_mb, engine_path):
state = init_state(engine_color, int(movetime_ms), int(depth) if depth else None, int(threads), int(hash_mb), engine_path.strip() or None)
img, fen = render(state)
if state["engine"]:
msg = f"Nieuwe partij gestart. Engine pad: {state['engine_path']}"
else:
diag = diagnostics() # show where we looked
msg = (
"Nieuwe partij gestart ZONDER engine. "
"Vul het correcte pad in en klik 'Herstart engine'.\n\n"
+ diag
)
return state, img, fen, msg, state["engine_path"]
def diagnostics():
lines = ["π Stockfish-detectie:"]
for p in detect_stockfish_candidates():
ok = "β
" if os.path.isfile(p) and os.access(p, os.X_OK) else "β"
lines.append(f"{ok} {p}")
env = os.environ.get("STOCKFISH_PATH")
if env:
lines.append(f"Env STOCKFISH_PATH={env}")
lines.append("\nTip: zorg dat je in de Space een bestand 'apt.txt' hebt met:\nstockfish\n"
"en rebuild de Space. Of zet Settings β Variables β STOCKFISH_PATH naar bv. /usr/games/stockfish.")
return "\n".join(lines)
def restart_engine(state, threads, hash_mb, engine_path):
# sluit oude engine
try:
if state.get("engine"):
state["engine"].quit()
except Exception:
pass
# start nieuwe
picked = choose_stockfish_path(engine_path.strip() or None)
if not picked:
state["engine"] = None
state["engine_error"] = "Geen uitvoerbare Stockfish gevonden."
msg = "β Geen engine gevonden.\n" + diagnostics()
else:
try:
state["engine"] = new_engine(picked, threads=int(threads), hash_mb=int(hash_mb))
state["engine_error"] = None
state["engine_path"] = picked
msg = f"β
Engine gestart op: {picked}"
except Exception as e:
state["engine"] = None
state["engine_error"] = str(e)
msg = f"β Engine-fout: {e}\n" + diagnostics()
img, fen = render(state)
return state, img, fen, msg, state.get("engine_path","")
def make_move(state, user_move):
board: chess.Board = state["board"]
if board.is_game_over():
return state, *render(state), f"Partij is al afgelopen: {board.result()}."
try:
mv = chess.Move.from_uci(user_move.strip())
if mv not in board.legal_moves:
return state, *render(state), "Ongeldige zet (niet legaal in deze stelling)."
board.push(mv)
state["last_move"] = mv
except Exception as e:
return state, *render(state), f"Ongeldige invoer: {e}"
if board.is_game_over():
img, fen = render(state)
return state, img, fen, f"Partij afgelopen: {board.result()}."
side_to_move = "white" if board.turn == chess.WHITE else "black"
if side_to_move == state["engine_color"]:
if state["engine"] is None:
msg = "Engine niet beschikbaar β vul 'Engine pad' in en klik 'Herstart engine'."
else:
msg = engine_move(state)
else:
msg = "Jij bent aan zet."
img, fen = render(state)
return state, img, fen, msg
def engine_move(state):
board: chess.Board = state["board"]
engine: chess.engine.SimpleEngine = state["engine"]
if engine is None:
return "Engine niet beschikbaar."
if board.is_game_over():
return f"Partij afgelopen: {board.result()}."
try:
result = engine.play(board, engine_limit(state))
board.push(result.move)
state["last_move"] = result.move
if board.is_game_over():
return f"Engine zet {result.move}. Partij afgelopen: {board.result()}."
else:
return f"Engine zet {result.move}. Jij bent aan zet."
except Exception as e:
return f"Engine-fout: {e}"
def undo_move(state):
board: chess.Board = state["board"]
if len(board.move_stack) == 0:
return state, *render(state), "Geen zetten om terug te nemen."
board.pop()
# Neem er eentje extra terug als engine aan zet zou komen
if state["engine_color"] == ("white" if board.turn == chess.WHITE else "black") and len(board.move_stack) > 0:
board.pop()
state["last_move"] = board.move_stack[-1] if board.move_stack else None
return state, *render(state), "Zet(ten) teruggenomen."
def resign(state):
board: chess.Board = state["board"]
outcome = "1-0" if board.turn == chess.BLACK else "0-1"
return state, *render(state), f"Opgegeven. Resultaat: {outcome}"
# ---------- UI ----------
CSS = """
#board { display: flex; justify-content: center; }
#board svg { max-width: 100%; height: auto; }
"""
with gr.Blocks(title="Boss Chess (Stockfish)", css=CSS) as demo:
gr.Markdown("# βοΈ Boss Chess (Stockfish)\nSpeel tegen een sterke NNUE-engine (Stockfish) in je browser.")
with gr.Row():
board_html = gr.HTML(label="Bord", elem_id="board")
fen_out = gr.Textbox(label="FEN", interactive=False)
with gr.Row():
user_move = gr.Textbox(label="Jouw zet (UCI, bv. e2e4)", placeholder="e2e4", scale=2)
btn_move = gr.Button("Zet uitvoeren", variant="primary")
btn_undo = gr.Button("Undo")
btn_resign = gr.Button("Resign")
status = gr.Markdown("Klaar.")
with gr.Accordion("βοΈ Instellingen", open=False):
engine_color = gr.Radio(choices=["white","black"], value="black", label="Engine kleur")
movetime = gr.Slider(200, 5000, value=DEFAULT_MOVETIME_MS, step=100, label="Engine denktijd (ms/zet)")
depth = gr.Slider(0, 30, value=0, step=1, label="Max diepte (0=uit)")
threads = gr.Slider(1, DEFAULT_THREADS, value=DEFAULT_THREADS, step=1, label="Threads")
hash_mb = gr.Slider(64, 2048, value=DEFAULT_HASH_MB, step=64, label="Hash (MB)")
engine_path = gr.Textbox(label="Engine pad (optioneel, bv. /usr/games/stockfish)", placeholder="/usr/games/stockfish", value="")
btn_new = gr.Button("Nieuwe partij / herstart")
btn_restart = gr.Button("Herstart engine")
state = gr.State()
# Bindings
btn_new.click(new_game, [engine_color, movetime, depth, threads, hash_mb, engine_path],
[state, board_html, fen_out, status, engine_path])
btn_restart.click(restart_engine, [state, threads, hash_mb, engine_path],
[state, board_html, fen_out, status, engine_path])
btn_move.click(make_move, [state, user_move], [state, board_html, fen_out, status])
btn_undo.click(undo_move, [state], [state, board_html, fen_out, status])
btn_resign.click(resign, [state], [state, board_html, fen_out, status])
# Auto-start
demo.load(new_game, [engine_color, movetime, depth, threads, hash_mb, engine_path],
[state, board_html, fen_out, status, engine_path])
if __name__ == "__main__":
demo.launch()
|