Marcel0123 commited on
Commit
a7bdaaf
·
verified ·
1 Parent(s): 8622f22

Upload 3 files

Browse files
Files changed (3) hide show
  1. app.py +186 -0
  2. apt.txt +1 -0
  3. requirements.txt +2 -0
app.py ADDED
@@ -0,0 +1,186 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py
2
+ import os
3
+ import base64
4
+ import gradio as gr
5
+ import chess
6
+ import chess.svg
7
+ import chess.engine
8
+
9
+ # -------- Engine-config --------
10
+ STOCKFISH_PATH = "stockfish" # dankzij apt.txt staat de binary in PATH
11
+
12
+ DEFAULT_THREADS = max(1, os.cpu_count() or 2)
13
+ DEFAULT_HASH_MB = 512 # pas gerust omhoog aan als je Space genoeg RAM heeft
14
+ DEFAULT_MOVETIME_MS = 1500 # engine denktijd per zet
15
+
16
+ # -------- Helpers --------
17
+ def board_svg(board: chess.Board, last_move=None) -> str:
18
+ """Geef een SVG-string van het bord terug."""
19
+ return chess.svg.board(
20
+ board=board,
21
+ lastmove=last_move,
22
+ check=board.is_check(),
23
+ coordinates=True,
24
+ size=480,
25
+ )
26
+
27
+ def svg_to_data_url(svg_text: str) -> str:
28
+ """Returneer data-URL van SVG (zodat Gradio 'm kan tonen in HTML)."""
29
+ b64 = base64.b64encode(svg_text.encode("utf-8")).decode("ascii")
30
+ return f"data:image/svg+xml;base64,{b64}"
31
+
32
+ def new_engine(threads=DEFAULT_THREADS, hash_mb=DEFAULT_HASH_MB, ponder=False):
33
+ engine = chess.engine.SimpleEngine.popen_uci(STOCKFISH_PATH)
34
+ # Sterke UCI-opts
35
+ opts = {
36
+ "Threads": int(threads),
37
+ "Hash": int(hash_mb),
38
+ "UCI_LimitStrength": False,
39
+ "Skill Level": 20,
40
+ "Ponder": ponder,
41
+ "Move Overhead": 30,
42
+ }
43
+ try:
44
+ engine.configure(opts)
45
+ except Exception as e:
46
+ print("UCI-opties zetten gaf een fout (doorgaan):", e)
47
+ return engine
48
+
49
+ # -------- Gradio statestructuur --------
50
+ def init_state(engine_color: str, movetime_ms: int, depth: int | None, threads: int, hash_mb: int):
51
+ board = chess.Board()
52
+ engine = new_engine(threads=threads, hash_mb=hash_mb)
53
+ state = {
54
+ "board": board,
55
+ "engine": engine,
56
+ "engine_color": engine_color, # "white" or "black"
57
+ "movetime_ms": movetime_ms,
58
+ "depth": depth if depth and depth > 0 else None,
59
+ "last_move": None,
60
+ }
61
+ # Als engine wit is, laat ‘m meteen zetten
62
+ if engine_color == "white":
63
+ _ = engine_move(state)
64
+ return state
65
+
66
+ def engine_limit(state):
67
+ if state["depth"] is not None:
68
+ return chess.engine.Limit(depth=int(state["depth"]))
69
+ return chess.engine.Limit(time=max(0.05, state["movetime_ms"]/1000.0))
70
+
71
+ def render(state):
72
+ svg = board_svg(state["board"], last_move=state["last_move'])
73
+ return svg_to_data_url(svg), state["board"].fen()
74
+
75
+ # -------- Acties --------
76
+ def new_game(engine_color, movetime_ms, depth, threads, hash_mb):
77
+ state = init_state(engine_color, int(movetime_ms), int(depth) if depth else None, int(threads), int(hash_mb))
78
+ img, fen = render(state)
79
+ return state, img, fen, "Nieuwe partij gestart."
80
+
81
+ def make_move(state, user_move):
82
+ board: chess.Board = state["board"]
83
+ if board.is_game_over():
84
+ return state, *render(state), f"Partij is al afgelopen: {board.result()}."
85
+
86
+ try:
87
+ # UCI notatie (b.v. e2e4 of e7e8q)
88
+ mv = chess.Move.from_uci(user_move.strip())
89
+ if mv not in board.legal_moves:
90
+ return state, *render(state), "Ongeldige zet (niet legaal in deze stelling)."
91
+ board.push(mv)
92
+ state["last_move"] = mv
93
+ except Exception as e:
94
+ return state, *render(state), f"Ongeldige invoer: {e}"
95
+
96
+ if board.is_game_over():
97
+ img, fen = render(state)
98
+ return state, img, fen, f"Partij afgelopen: {board.result()}."
99
+
100
+ # engine aan zet?
101
+ side_to_move = "white" if board.turn == chess.WHITE else "black"
102
+ if side_to_move == state["engine_color"]:
103
+ msg = engine_move(state)
104
+ else:
105
+ msg = "Jij bent aan zet."
106
+ img, fen = render(state)
107
+ return state, img, fen, msg
108
+
109
+ def engine_move(state):
110
+ board: chess.Board = state["board"]
111
+ engine: chess.engine.SimpleEngine = state["engine"]
112
+ if board.is_game_over():
113
+ return f"Partij afgelopen: {board.result()}."
114
+ try:
115
+ result = engine.play(board, engine_limit(state))
116
+ board.push(result.move)
117
+ state["last_move"] = result.move
118
+ if board.is_game_over():
119
+ return f"Engine zet {result.move}. Partij afgelopen: {board.result()}."
120
+ else:
121
+ return f"Engine zet {result.move}. Jij bent aan zet."
122
+ except Exception as e:
123
+ return f"Engine-fout: {e}"
124
+
125
+ def undo_move(state):
126
+ board: chess.Board = state["board"]
127
+ if len(board.move_stack) == 0:
128
+ return state, *render(state), "Geen zetten om terug te nemen."
129
+ # Neem 1 of 2 zetten terug zodat jij weer aan zet bent
130
+ board.pop()
131
+ if state["engine_color"] == ("white" if board.turn == chess.WHITE else "black") and len(board.move_stack) > 0:
132
+ board.pop()
133
+ state["last_move"] = board.move_stack[-1] if board.move_stack else None
134
+ return state, *render(state), "Zet(ten) teruggenomen."
135
+
136
+ def resign(state):
137
+ board: chess.Board = state["board"]
138
+ outcome = "1-0" if board.turn == chess.BLACK else "0-1"
139
+ return state, *render(state), f"Opgegeven. Resultaat: {outcome}"
140
+
141
+ def stop_engine(state):
142
+ try:
143
+ state["engine"].quit()
144
+ except Exception:
145
+ pass
146
+ return "Engine gestopt."
147
+
148
+ # -------- Gradio UI --------
149
+ with gr.Blocks(title="Boss Chess (Stockfish)") as demo:
150
+ gr.Markdown("# ♟️ Boss Chess (Stockfish)\nSpeel tegen een sterke NNUE-engine (Stockfish) in je browser.")
151
+
152
+ with gr.Row():
153
+ board_html = gr.HTML(label="Bord")
154
+ fen_out = gr.Textbox(label="FEN", interactive=False)
155
+
156
+ with gr.Row():
157
+ user_move = gr.Textbox(label="Jouw zet (UCI, bv. e2e4)", placeholder="e2e4", scale=2)
158
+ btn_move = gr.Button("Zet uitvoeren", variant="primary")
159
+ btn_undo = gr.Button("Undo")
160
+ btn_resign = gr.Button("Resign")
161
+
162
+ status = gr.Markdown("Klaar.")
163
+
164
+ with gr.Accordion("⚙️ Instellingen", open=False):
165
+ engine_color = gr.Radio(choices=["white","black"], value="black", label="Engine kleur")
166
+ movetime = gr.Slider(200, 5000, value=DEFAULT_MOVETIME_MS, step=100, label="Engine denktijd (ms/zet)")
167
+ depth = gr.Slider(0, 30, value=0, step=1, label="Max diepte (0=uit)")
168
+ threads = gr.Slider(1, DEFAULT_THREADS, value=DEFAULT_THREADS, step=1, label="Threads")
169
+ hash_mb = gr.Slider(64, 2048, value=DEFAULT_HASH_MB, step=64, label="Hash (MB)")
170
+ btn_new = gr.Button("Nieuwe partij / herstart")
171
+
172
+ state = gr.State()
173
+
174
+ # Callbacks
175
+ btn_new.click(new_game, [engine_color, movetime, depth, threads, hash_mb],
176
+ [state, board_html, fen_out, status])
177
+ btn_move.click(make_move, [state, user_move], [state, board_html, fen_out, status])
178
+ btn_undo.click(undo_move, [state], [state, board_html, fen_out, status])
179
+ btn_resign.click(resign, [state], [state, board_html, fen_out, status])
180
+
181
+ # Auto-start een partij
182
+ demo.load(new_game, [engine_color, movetime, depth, threads, hash_mb],
183
+ [state, board_html, fen_out, status])
184
+
185
+ if __name__ == "__main__":
186
+ demo.launch()
apt.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ stockfish
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ gradio>=4.31
2
+ python-chess>=1.999