Enoder commited on
Commit
3cc6861
·
verified ·
1 Parent(s): d483c33

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +138 -302
app.py CHANGED
@@ -1,20 +1,17 @@
1
- #!/usr/bin/env python3
2
- # coding: utf-8
3
- """
4
- Jeu d'échecs + démineur — interface web Gradio (2 joueurs, tour par tour).
5
- - Pièces: p (pion), f (fou), t (tour), c (cavalier), R (reine), k (roi)
6
- MAJ = Blanc, minuscule = Noir
7
- - Mines placées aléatoirement sur cases vides au début.
8
- - Déplacement sur une mine -> pièce éliminée (mine explose).
9
- - Cases sûres révélées comme en démineur (compte des mines adjacentes, cascade si 0).
10
- - Règles de base : mouvements, captures, échec, échec-et-mat, promotion automatique.
11
- - Castling & en passant non implémentés.
12
- """
13
-
14
  import random
15
  import copy
16
  import gradio as gr
17
 
 
 
 
 
 
 
 
 
 
 
18
  FILES = "abcdefgh"
19
  RANKS = "12345678"
20
 
@@ -22,355 +19,194 @@ def sq_to_coords(sq):
22
  sq = sq.lower()
23
  return FILES.index(sq[0]), RANKS.index(sq[1])
24
 
25
- def coords_to_sq(x,y):
26
  return FILES[x] + RANKS[y]
27
 
28
- def in_bounds(x,y):
29
  return 0 <= x < 8 and 0 <= y < 8
30
 
31
- # ---------- initial board ----------
32
  def initial_board():
33
- # board[x][y] with x file 0..7 (a..h), y rank 0..7 (1..8)
34
  board = [[None for _ in range(8)] for _ in range(8)]
35
- # White (bottom) y=0 (rank1) and y=1 (rank2)
36
- white_row = ['t','c','f','R','k','f','c','t'] # types; will display uppercase for white
37
- for x,p in enumerate(white_row):
38
  board[x][0] = ('W', p)
39
- for x in range(8):
40
  board[x][1] = ('W', 'p')
41
- # Black (top) y=7 (rank8) and y=6 (rank7)
42
  black_row = ['t','c','f','R','k','f','c','t']
43
- for x,p in enumerate(black_row):
44
  board[x][7] = ('B', p)
45
- for x in range(8):
46
  board[x][6] = ('B', 'p')
47
  return board
48
 
49
- # ---------- mines and demineur mechanics ----------
50
  def generate_mines(board, density=0.15):
51
- empty = [(x,y) for x in range(8) for y in range(8) if board[x][y] is None]
52
  n = max(1, int(len(empty) * density))
53
  mines = set(random.sample(empty, n))
54
- adj = {}
55
- for x in range(8):
56
- for y in range(8):
57
- if (x,y) in mines:
58
- adj[(x,y)] = -1
59
- else:
60
- cnt = 0
61
- for dx in (-1,0,1):
62
- for dy in (-1,0,1):
63
- if dx==0 and dy==0: continue
64
- nx,ny = x+dx, y+dy
65
- if in_bounds(nx,ny) and (nx,ny) in mines:
66
- cnt += 1
67
- adj[(x,y)] = cnt
68
  revealed = set()
69
  exploded = set()
70
- return mines, revealed, adj, exploded
71
-
72
- def reveal_cascade(revealed, adj, start):
73
- stack = [start]
74
- while stack:
75
- s = stack.pop()
76
- if s in revealed:
77
- continue
78
- revealed.add(s)
79
- if adj.get(s,0) == 0:
80
- x,y = s
81
- for dx in (-1,0,1):
82
- for dy in (-1,0,1):
83
- nx,ny = x+dx, y+dy
84
- if dx==0 and dy==0: continue
85
- if in_bounds(nx,ny) and (nx,ny) not in revealed:
86
- stack.append((nx,ny))
87
-
88
- # ---------- display ----------
89
- def piece_to_char(piece):
90
- if piece is None:
91
- return '.'
92
- col,ptype = piece
93
- ch = ptype
94
- return ch.upper() if col=='W' else ch.lower()
95
 
96
- def render_board(board, mines, revealed, adj, exploded):
97
- lines = []
98
- lines.append(" a b c d e f g h")
99
- lines.append(" -----------------")
100
- for y in range(7,-1,-1):
101
- row = f" {y+1} |"
102
  for x in range(8):
103
- if (x,y) in exploded:
104
- cell = "X"
105
- elif board[x][y] is not None:
106
- cell = piece_to_char(board[x][y])
107
- elif (x,y) in revealed:
108
- cnt = adj.get((x,y), 0)
109
- cell = str(cnt) if cnt>0 else ' '
110
  else:
111
- cell = '.'
112
- row += ' ' + cell
113
- row += f" | {y+1}"
114
- lines.append(row)
115
- lines.append(" -----------------")
116
- lines.append(" a b c d e f g h")
117
- return "\n".join(lines)
118
 
119
- # ---------- moves generation ----------
120
  DIRECTIONS_ROOK = [(1,0),(-1,0),(0,1),(0,-1)]
121
  DIRECTIONS_BISHOP = [(1,1),(1,-1),(-1,1),(-1,-1)]
122
  KNIGHT_MOVES = [(2,1),(2,-1),(-2,1),(-2,-1),(1,2),(1,-2),(-1,2),(-1,-2)]
123
 
124
- def generate_pseudo_legal_moves_from(board, x, y):
125
  piece = board[x][y]
126
  if piece is None:
127
  return []
128
  color, ptype = piece
129
  moves = []
130
- if ptype == 'p':
131
- # pawns: white moves up (y+1), black moves down (y-1)
132
- diry = 1 if color=='W' else -1
133
- start_rank = 1 if color=='W' else 6
134
- # one forward
135
  nx, ny = x, y + diry
136
- if in_bounds(nx,ny) and board[nx][ny] is None:
137
- moves.append((nx,ny))
138
- # two forward from start
139
- nx2, ny2 = x, y + 2*diry
140
- if y == start_rank and in_bounds(nx2,ny2) and board[nx2][ny2] is None:
141
- moves.append((nx2,ny2))
142
- # captures
143
- for dx in (-1,1):
144
- cx, cy = x+dx, y+diry
145
- if in_bounds(cx,cy) and board[cx][cy] is not None and board[cx][cy][0] != color:
146
- moves.append((cx,cy))
147
- elif ptype == 'c':
148
- for dx,dy in KNIGHT_MOVES:
149
- nx,ny = x+dx, y+dy
150
- if in_bounds(nx,ny) and (board[nx][ny] is None or board[nx][ny][0] != color):
151
- moves.append((nx,ny))
152
- elif ptype == 't':
153
- for dx,dy in DIRECTIONS_ROOK:
154
- nx,ny = x+dx, y+dy
155
- while in_bounds(nx,ny):
156
- if board[nx][ny] is None:
157
- moves.append((nx,ny))
158
  else:
159
  if board[nx][ny][0] != color:
160
- moves.append((nx,ny))
161
  break
162
  nx += dx; ny += dy
163
- elif ptype == 'f':
164
- for dx,dy in DIRECTIONS_BISHOP:
165
- nx,ny = x+dx, y+dy
166
- while in_bounds(nx,ny):
167
- if board[nx][ny] is None:
168
- moves.append((nx,ny))
169
  else:
170
  if board[nx][ny][0] != color:
171
- moves.append((nx,ny))
172
  break
173
  nx += dx; ny += dy
174
- elif ptype == 'R':
175
- for dx,dy in DIRECTIONS_ROOK + DIRECTIONS_BISHOP:
176
- nx,ny = x+dx, y+dy
177
- while in_bounds(nx,ny):
178
- if board[nx][ny] is None:
179
- moves.append((nx,ny))
180
  else:
181
  if board[nx][ny][0] != color:
182
- moves.append((nx,ny))
183
  break
184
  nx += dx; ny += dy
185
- elif ptype == 'k':
186
- for dx in (-1,0,1):
187
- for dy in (-1,0,1):
188
- if dx==0 and dy==0: continue
189
- nx,ny = x+dx, y+dy
190
- if in_bounds(nx,ny) and (board[nx][ny] is None or board[nx][ny][0] != color):
191
- moves.append((nx,ny))
192
  return moves
193
 
194
- def is_square_attacked(board, tx, ty, attacker_color):
195
- for x in range(8):
196
- for y in range(8):
197
- p = board[x][y]
198
- if p is not None and p[0] == attacker_color:
199
- for (mx,my) in generate_pseudo_legal_moves_from(board, x, y):
200
- if mx==tx and my==ty:
201
- return True
202
- return False
203
 
204
- def locate_king(board, color):
205
- for x in range(8):
206
- for y in range(8):
207
- p = board[x][y]
208
- if p is not None and p[0]==color and p[1]=='k':
209
- return (x,y)
210
- return None
211
 
212
- def king_in_check(board, color):
213
- kpos = locate_king(board, color)
214
- if kpos is None:
215
- return True
216
- return is_square_attacked(board, kpos[0], kpos[1], 'B' if color=='W' else 'W')
217
 
218
- def all_legal_moves(board, color):
219
- moves = []
220
- for x in range(8):
221
- for y in range(8):
222
- p = board[x][y]
223
- if p is None or p[0]!=color: continue
224
- for (nx,ny) in generate_pseudo_legal_moves_from(board, x, y):
225
- b2 = copy.deepcopy(board)
226
- b2[nx][ny] = b2[x][y]
227
- b2[x][y] = None
228
- # If move captures the opponent king by moving onto it, that's allowed but leads to win
229
- if not king_in_check(b2, color):
230
- moves.append(((x,y),(nx,ny)))
231
- return moves
232
 
233
- # ---------- apply move with mine logic ----------
234
- def apply_move(board, from_xy, to_xy, mines, revealed, adj, exploded):
235
- fx,fy = from_xy; tx,ty = to_xy
236
- piece = board[fx][fy]
237
- if piece is None:
238
- return board, False, False, False
239
- # stepping onto mine?
240
- if (tx,ty) in mines:
241
- # piece eliminated
242
- board[fx][fy] = None
243
- exploded.add((tx,ty))
244
- mines.remove((tx,ty))
245
- revealed.add((tx,ty))
246
- return board, True, False, False
247
- # normal move
248
  capture = board[tx][ty] is not None
249
- board[tx][ty] = board[fx][fy]
250
  board[fx][fy] = None
251
- # reveal
252
- reveal_cascade(revealed, adj, (tx,ty))
253
- # promotion
254
- promotion = False
255
- col, ptype = board[tx][ty]
256
- if ptype == 'p':
257
- if (col=='W' and ty==7) or (col=='B' and ty==0):
258
- board[tx][ty] = (col, 'R')
259
- promotion = True
260
- return board, False, capture, promotion
261
-
262
- # ---------- parsing ----------
263
- def parse_move_input(s):
264
- s = s.strip().lower().replace('-', ' ').replace('->',' ').replace(',', ' ')
265
- parts = s.split()
266
- if len(parts)==1 and len(parts[0])==4:
267
- return parts[0][0:2], parts[0][2:4]
268
- if len(parts)==2 and all(len(p)==2 for p in parts):
269
- return parts[0], parts[1]
270
- return None
271
 
272
- # ---------- Gradio app state helpers ----------
273
- def new_game_state(density):
274
  board = initial_board()
275
- mines, revealed, adj, exploded = generate_mines(board, density=density)
276
- state = {
277
- "board": board,
278
- "mines": mines,
279
- "revealed": revealed,
280
- "adj": adj,
281
- "exploded": exploded,
282
- "turn": 'W',
283
- "move_number": 1,
284
- "log": ["Nouvelle partie créée. Blanc commence."]
285
- }
286
- return state
287
-
288
- # ---------- Gradio callbacks ----------
289
- def start_new_game(density):
290
- state = new_game_state(density)
291
- board = state["board"]; mines=state["mines"]; revealed=state["revealed"]; adj=state["adj"]; exploded=state["exploded"]
292
- board_text = render_board(board, mines, revealed, adj, exploded)
 
 
 
293
  return board_text, "\n".join(state["log"]), state
294
 
295
- def submit_move(move_str, state):
296
- if state is None:
297
- return "Aucune partie en cours.", "Aucune", None
298
- board = state["board"]; mines=state["mines"]; revealed=state["revealed"]; adj=state["adj"]; exploded=state["exploded"]
299
- turn = state["turn"]
300
- log = state["log"]
301
- move = parse_move_input(move_str or "")
302
- if move is None:
303
- log.append("Format invalide. Utilisez ex: e2e4")
304
- return render_board(board,mines,revealed,adj,exploded), "\n".join(log), state
305
- from_sq, to_sq = move
306
- try:
307
- fx,fy = sq_to_coords(from_sq)
308
- tx,ty = sq_to_coords(to_sq)
309
- except Exception:
310
- log.append("Coordonnées invalides.")
311
- return render_board(board,mines,revealed,adj,exploded), "\n".join(log), state
312
- p = board[fx][fy]
313
- if p is None or p[0] != turn:
314
- log.append("Pas de pièce de votre couleur sur la case de départ.")
315
- return render_board(board,mines,revealed,adj,exploded), "\n".join(log), state
316
- legal = all_legal_moves(board, turn)
317
- ok = False
318
- for (a,b),(c,d) in legal:
319
- if a==fx and b==fy and c==tx and d==ty:
320
- ok = True; break
321
- if not ok:
322
- log.append("Coup illégal ou qui laisserait le roi en échec.")
323
- return render_board(board,mines,revealed,adj,exploded), "\n".join(log), state
324
- board, eliminated, capture, promotion = apply_move(board, (fx,fy), (tx,ty), mines, revealed, adj, exploded)
325
- if eliminated:
326
- log.append(f"BOOM ! Mine déclenchée en {to_sq}. Pièce éliminée.")
327
- # check if king remains
328
- if locate_king(board, turn) is None:
329
- winner = 'Noir' if turn=='W' else 'Blanc'
330
- log.append(f"Le roi de {'Blanc' if turn=='W' else 'Noir'} a été éliminé. {winner} gagne.")
331
- return render_board(board,mines,revealed,adj,exploded), "\n".join(log), state
332
- else:
333
- if capture:
334
- log.append(f"Prise en {to_sq}.")
335
- if promotion:
336
- log.append(f"Promotion en Reine en {to_sq}.")
337
- # switch turn
338
- state["turn"] = 'B' if turn=='W' else 'W'
339
- state["move_number"] += 1
340
- # check for check/checkmate/stalemate
341
- next_color = state["turn"]
342
- if king_in_check(board, next_color):
343
- # check if has legal moves
344
- if not all_legal_moves(board, next_color):
345
- winner = 'Blanc' if next_color=='B' else 'Noir'
346
- log.append(f"Échec et mat ! {winner} gagne.")
347
- else:
348
- log.append(f"{'Blanc' if next_color=='W' else 'Noir'} est en échec.")
349
- else:
350
- if not all_legal_moves(board, next_color):
351
- log.append("Pat (stalemate).")
352
- state["log"] = log
353
- return render_board(board,mines,revealed,adj,exploded), "\n".join(log), state
354
-
355
- # ---------- Gradio UI ----------
356
  with gr.Blocks() as demo:
357
- gr.Markdown("# ♟️ Échecs + Démineur (2 joueurs)")
358
- gr.Markdown("Déplacez vos pièces normalement. Les cases vides initiales contiennent des mines aléatoires ; si une de vos pièces marche sur une mine, elle est éliminée.")
359
- with gr.Row():
360
- density = gr.Slider(minimum=0.05, maximum=0.35, value=0.18, step=0.01, label="Densité des mines (fraction des cases vides)")
361
- btn_new = gr.Button("Nouvelle partie")
362
- board_area = gr.Textbox(label="Plateau", interactive=False, lines=12)
363
- log_area = gr.Textbox(label="Journal de partie", interactive=False, lines=8)
364
- move_in = gr.Textbox(label="Votre coup (ex: e2e4)", placeholder="e2e4", lines=1)
365
- btn_move = gr.Button("Jouer le coup")
366
 
367
- state = gr.State(None)
 
368
 
369
- btn_new.click(fn=start_new_game, inputs=[density], outputs=[board_area, log_area, state])
370
- btn_move.click(fn=submit_move, inputs=[move_in, state], outputs=[board_area, log_area, state])
371
 
372
- gr.Markdown("**Règles simplifiées** : promotion automatique en reine, pas de roque, pas d'en-passant.")
373
 
374
- # Launch app
375
  if __name__ == "__main__":
376
  demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import random
2
  import copy
3
  import gradio as gr
4
 
5
+ # === Unicode pour les pièces ===
6
+ UNICODE_PIECES = {
7
+ ("W", "p"): "♙", ("B", "p"): "♟",
8
+ ("W", "t"): "♖", ("B", "t"): "♜",
9
+ ("W", "c"): "♘", ("B", "c"): "♞",
10
+ ("W", "f"): "♗", ("B", "f"): "♝",
11
+ ("W", "R"): "♕", ("B", "R"): "♛",
12
+ ("W", "k"): "♔", ("B", "k"): "♚"
13
+ }
14
+
15
  FILES = "abcdefgh"
16
  RANKS = "12345678"
17
 
 
19
  sq = sq.lower()
20
  return FILES.index(sq[0]), RANKS.index(sq[1])
21
 
22
+ def coords_to_sq(x, y):
23
  return FILES[x] + RANKS[y]
24
 
25
+ def in_bounds(x, y):
26
  return 0 <= x < 8 and 0 <= y < 8
27
 
28
+ # --- initial board ---
29
  def initial_board():
 
30
  board = [[None for _ in range(8)] for _ in range(8)]
31
+ white_row = ['t','c','f','R','k','f','c','t']
32
+ for x, p in enumerate(white_row):
 
33
  board[x][0] = ('W', p)
 
34
  board[x][1] = ('W', 'p')
 
35
  black_row = ['t','c','f','R','k','f','c','t']
36
+ for x, p in enumerate(black_row):
37
  board[x][7] = ('B', p)
 
38
  board[x][6] = ('B', 'p')
39
  return board
40
 
41
+ # --- mines ---
42
  def generate_mines(board, density=0.15):
43
+ empty = [(x, y) for x in range(8) for y in range(8) if board[x][y] is None]
44
  n = max(1, int(len(empty) * density))
45
  mines = set(random.sample(empty, n))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  revealed = set()
47
  exploded = set()
48
+ return mines, revealed, exploded
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
+ # --- affichage ---
51
+ def render_board(board, mines, revealed, exploded):
52
+ grid = []
53
+ for y in range(7, -1, -1):
54
+ row = []
 
55
  for x in range(8):
56
+ if (x, y) in exploded:
57
+ row.append("💥")
58
+ elif board[x][y]:
59
+ row.append(UNICODE_PIECES[board[x][y]])
60
+ elif (x, y) in revealed:
61
+ row.append("⬜")
 
62
  else:
63
+ row.append("⬛" if (x+y) % 2 else "⬜")
64
+ grid.append(row)
65
+ return "\n".join(" ".join(r) for r in grid)
 
 
 
 
66
 
67
+ # --- mouvements ---
68
  DIRECTIONS_ROOK = [(1,0),(-1,0),(0,1),(0,-1)]
69
  DIRECTIONS_BISHOP = [(1,1),(1,-1),(-1,1),(-1,-1)]
70
  KNIGHT_MOVES = [(2,1),(2,-1),(-2,1),(-2,-1),(1,2),(1,-2),(-1,2),(-1,-2)]
71
 
72
+ def generate_pseudo_legal_moves(board, x, y):
73
  piece = board[x][y]
74
  if piece is None:
75
  return []
76
  color, ptype = piece
77
  moves = []
78
+ if ptype == "p":
79
+ diry = 1 if color == "W" else -1
80
+ start_rank = 1 if color == "W" else 6
 
 
81
  nx, ny = x, y + diry
82
+ if in_bounds(nx, ny) and board[nx][ny] is None:
83
+ moves.append((nx, ny))
84
+ if y == start_rank and board[x][y + 2 * diry] is None:
85
+ moves.append((x, y + 2 * diry))
86
+ for dx in (-1, 1):
87
+ cx, cy = x + dx, y + diry
88
+ if in_bounds(cx, cy) and board[cx][cy] and board[cx][cy][0] != color:
89
+ moves.append((cx, cy))
90
+ elif ptype == "c":
91
+ for dx, dy in KNIGHT_MOVES:
92
+ nx, ny = x + dx, y + dy
93
+ if in_bounds(nx, ny) and (not board[nx][ny] or board[nx][ny][0] != color):
94
+ moves.append((nx, ny))
95
+ elif ptype == "t":
96
+ for dx, dy in DIRECTIONS_ROOK:
97
+ nx, ny = x + dx, y + dy
98
+ while in_bounds(nx, ny):
99
+ if not board[nx][ny]:
100
+ moves.append((nx, ny))
 
 
 
101
  else:
102
  if board[nx][ny][0] != color:
103
+ moves.append((nx, ny))
104
  break
105
  nx += dx; ny += dy
106
+ elif ptype == "f":
107
+ for dx, dy in DIRECTIONS_BISHOP:
108
+ nx, ny = x + dx, y + dy
109
+ while in_bounds(nx, ny):
110
+ if not board[nx][ny]:
111
+ moves.append((nx, ny))
112
  else:
113
  if board[nx][ny][0] != color:
114
+ moves.append((nx, ny))
115
  break
116
  nx += dx; ny += dy
117
+ elif ptype == "R":
118
+ for dx, dy in DIRECTIONS_ROOK + DIRECTIONS_BISHOP:
119
+ nx, ny = x + dx, y + dy
120
+ while in_bounds(nx, ny):
121
+ if not board[nx][ny]:
122
+ moves.append((nx, ny))
123
  else:
124
  if board[nx][ny][0] != color:
125
+ moves.append((nx, ny))
126
  break
127
  nx += dx; ny += dy
128
+ elif ptype == "k":
129
+ for dx in (-1, 0, 1):
130
+ for dy in (-1, 0, 1):
131
+ if dx == dy == 0: continue
132
+ nx, ny = x + dx, y + dy
133
+ if in_bounds(nx, ny) and (not board[nx][ny] or board[nx][ny][0] != color):
134
+ moves.append((nx, ny))
135
  return moves
136
 
137
+ # --- gestion du tour ---
138
+ def apply_move(board, mines, revealed, exploded, move, turn):
139
+ from_sq, to_sq = move
140
+ fx, fy = sq_to_coords(from_sq)
141
+ tx, ty = sq_to_coords(to_sq)
142
+ piece = board[fx][fy]
 
 
 
143
 
144
+ if not piece or piece[0] != turn:
145
+ return board, "Coup invalide"
 
 
 
 
 
146
 
147
+ if (tx, ty) in mines:
148
+ exploded.add((tx, ty))
149
+ board[fx][fy] = None
150
+ revealed.add((tx, ty))
151
+ return board, f"💥 {from_sq}->{to_sq} : une mine explose ! Pièce détruite."
152
 
153
+ legal = generate_pseudo_legal_moves(board, fx, fy)
154
+ if (tx, ty) not in legal:
155
+ return board, "Coup illégal."
 
 
 
 
 
 
 
 
 
 
 
156
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
  capture = board[tx][ty] is not None
158
+ board[tx][ty] = piece
159
  board[fx][fy] = None
160
+ revealed.add((tx, ty))
161
+ text = f"{from_sq}->{to_sq}"
162
+ if capture: text += " (prise)"
163
+ return board, text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
 
165
+ # --- partie ---
166
+ def new_game(density):
167
  board = initial_board()
168
+ mines, revealed, exploded = generate_mines(board, density)
169
+ state = dict(
170
+ board=board,
171
+ mines=mines,
172
+ revealed=revealed,
173
+ exploded=exploded,
174
+ turn="W",
175
+ log=["Nouvelle partie ! Blanc commence."]
176
+ )
177
+ return render_board(board, mines, revealed, exploded), "\n".join(state["log"]), state
178
+
179
+ def play_move(move_input, state):
180
+ if not state: return "Aucune partie", "Aucune", None
181
+ move_input = move_input.strip().lower()
182
+ if len(move_input) != 4: return render_board(**state), "Format invalide (ex: e2e4)", state
183
+ from_sq, to_sq = move_input[:2], move_input[2:]
184
+ board, text = apply_move(state["board"], state["mines"], state["revealed"], state["exploded"], (from_sq, to_sq), state["turn"])
185
+ state["board"] = board
186
+ state["log"].append(text)
187
+ state["turn"] = "B" if state["turn"] == "W" else "W"
188
+ board_text = render_board(board, state["mines"], state["revealed"], state["exploded"])
189
  return board_text, "\n".join(state["log"]), state
190
 
191
+ # --- interface ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  with gr.Blocks() as demo:
193
+ gr.Markdown("# ♟️ Échecs + 💣 Démineur")
194
+ gr.Markdown("Deux joueurs. Les cases vides cachent parfois des mines... Si une pièce marche dessus, elle est détruite 💥 !")
195
+
196
+ density = gr.Slider(0.05, 0.4, value=0.18, label="Densité de mines")
197
+ start = gr.Button("Nouvelle partie")
198
+
199
+ board_box = gr.Textbox(label="Plateau", lines=10, interactive=False)
200
+ move_in = gr.Textbox(label="Entrez votre coup (ex: e2e4)")
201
+ play = gr.Button("Jouer le coup")
202
 
203
+ log_box = gr.Textbox(label="Journal", lines=8, interactive=False)
204
+ state = gr.State()
205
 
206
+ start.click(new_game, inputs=[density], outputs=[board_box, log_box, state])
207
+ play.click(play_move, inputs=[move_in, state], outputs=[board_box, log_box, state])
208
 
209
+ gr.Markdown("**Pièces :** ♙♟ Pions | ♖♜ Tours | ♘♞ Cavaliers | ♗♝ Fous | ♕♛ Reines | ♔♚ Rois")
210
 
 
211
  if __name__ == "__main__":
212
  demo.launch()