Enoder commited on
Commit
747fb8b
·
verified ·
1 Parent(s): 4a64f3d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +417 -213
app.py CHANGED
@@ -1,213 +1,417 @@
1
- #!/usr/bin/env python3
2
- """
3
- demineur.py
4
- Version simple en terminal (ligne de commande).
5
-
6
- Commandes :
7
- r i j -> révéler la case (ligne i, colonne j) (1-based)
8
- f i j -> poser/enlever un drapeau sur la case
9
-
10
- Exemple:
11
- r 3 4
12
- f 2 1
13
- """
14
-
15
- import random
16
- import sys
17
- from typing import List, Set, Tuple
18
-
19
- # ---------- Configuration par défaut ----------
20
- DEFAULT_ROWS = 9
21
- DEFAULT_COLS = 9
22
- DEFAULT_MINES = 10
23
-
24
- # ---------- Génération du plateau ----------
25
- def create_board(rows: int, cols: int, mines: int):
26
- if mines >= rows * cols:
27
- raise ValueError("Le nombre de mines doit être inférieur au nombre total de cases.")
28
- # board: -1 = mine, otherwise number of adjacent mines
29
- board = [[0 for _ in range(cols)] for _ in range(rows)]
30
- # placement aléatoire des mines
31
- all_positions = [(r, c) for r in range(rows) for c in range(cols)]
32
- mine_positions = set(random.sample(all_positions, mines))
33
- for (r, c) in mine_positions:
34
- board[r][c] = -1
35
- # calcul des voisins
36
- for r in range(rows):
37
- for c in range(cols):
38
- if board[r][c] == -1:
39
- continue
40
- count = 0
41
- for dr in (-1, 0, 1):
42
- for dc in (-1, 0, 1):
43
- if dr == 0 and dc == 0:
44
- continue
45
- nr, nc = r + dr, c + dc
46
- if 0 <= nr < rows and 0 <= nc < cols and board[nr][nc] == -1:
47
- count += 1
48
- board[r][c] = count
49
- return board, mine_positions
50
-
51
- # ---------- Affichage ----------
52
- def display(rows:int, cols:int, revealed: Set[Tuple[int,int]], flags: Set[Tuple[int,int]], board: List[List[int]], show_mines=False):
53
- # entêtes colonnes
54
- header = " " + " ".join(f"{c+1:2}" for c in range(cols))
55
- sep = " +" + "---"*cols + "+"
56
- print(header)
57
- print(sep)
58
- for r in range(rows):
59
- row_cells = []
60
- for c in range(cols):
61
- pos = (r, c)
62
- if pos in flags:
63
- cell = " F"
64
- elif pos in revealed:
65
- val = board[r][c]
66
- if val == -1:
67
- cell = " *" # mine
68
- elif val == 0:
69
- cell = " " # vide
70
- else:
71
- cell = f" {val}"
72
- else:
73
- if show_mines and board[r][c] == -1:
74
- cell = " *"
75
- else:
76
- cell = " #"
77
- row_cells.append(cell)
78
- print(f"{r+1:2} |" + "".join(row_cells) + " |")
79
- print(sep)
80
-
81
- # ---------- Découverte en cascade ----------
82
- def flood_fill(board: List[List[int]], start_r:int, start_c:int, rows:int, cols:int, revealed: Set[Tuple[int,int]]):
83
- stack = [(start_r, start_c)]
84
- while stack:
85
- r, c = stack.pop()
86
- if (r,c) in revealed:
87
- continue
88
- revealed.add((r,c))
89
- if board[r][c] == 0:
90
- for dr in (-1, 0, 1):
91
- for dc in (-1, 0, 1):
92
- nr, nc = r + dr, c + dc
93
- if 0 <= nr < rows and 0 <= nc < cols and (nr,nc) not in revealed:
94
- if board[nr][nc] != -1:
95
- stack.append((nr,nc))
96
-
97
- # ---------- Vérifications de victoire ----------
98
- def check_victory(rows:int, cols:int, mines:int, revealed:Set[Tuple[int,int]]):
99
- # victoire si toutes les cases non-mines sont révélées
100
- return len(revealed) == rows*cols - mines
101
-
102
- # ---------- Analyse entrée utilisateur ----------
103
- def parse_command(s: str):
104
- parts = s.strip().split()
105
- if len(parts) != 3:
106
- return None
107
- cmd = parts[0].lower()
108
- if cmd not in ("r", "f"):
109
- return None
110
- try:
111
- i = int(parts[1])
112
- j = int(parts[2])
113
- return (cmd, i, j)
114
- except ValueError:
115
- return None
116
-
117
- # ---------- Jeu principal ----------
118
- def run_game(rows:int, cols:int, mines:int):
119
- board, mine_positions = create_board(rows, cols, mines)
120
- revealed: Set[Tuple[int,int]] = set()
121
- flags: Set[Tuple[int,int]] = set()
122
- lost = False
123
-
124
- print(f"\nBienvenue au Démineur ! {rows}x{cols}, mines: {mines}")
125
- print("Commandes: 'r i j' pour révéler, 'f i j' pour poser/retirer un drapeau. Coordonnées 1-based (ex: r 3 4).")
126
- while True:
127
- display(rows, cols, revealed, flags, board if lost else False)
128
- if lost:
129
- print("💥 BOOM ! Vous avez touché une mine. Partie terminée.")
130
- display(rows, cols, revealed.union(mine_positions), flags, board, show_mines=True)
131
- break
132
- if check_victory(rows, cols, mines, revealed):
133
- print("🎉 Félicitations vous avez gagné ! Toutes les cases sûres ont été révélées.")
134
- display(rows, cols, revealed, flags, board, show_mines=True)
135
- break
136
-
137
- s = input("Entrée (r/f i j) > ").strip()
138
- if s.lower() in ("quit", "exit", "q"):
139
- print("Au revoir !")
140
- break
141
- parsed = parse_command(s)
142
- if not parsed:
143
- print("Commande invalide ex: 'r 3 4' ou 'f 2 1'.")
144
- continue
145
- cmd, i, j = parsed
146
- # convertir en 0-based
147
- r, c = i - 1, j - 1
148
- if not (0 <= r < rows and 0 <= c < cols):
149
- print("Coordonnées hors plage.")
150
- continue
151
- pos = (r, c)
152
- if cmd == "f":
153
- if pos in revealed:
154
- print("Impossible de poser un drapeau sur une case déjà révélée.")
155
- else:
156
- if pos in flags:
157
- flags.remove(pos)
158
- print(f"Drapeau retiré en ({i},{j}).")
159
- else:
160
- flags.add(pos)
161
- print(f"Drapeau posé en ({i},{j}).")
162
- elif cmd == "r":
163
- if pos in flags:
164
- print("Case marquée d'un drapeau retirez le drapeau avant de révéler.")
165
- continue
166
- if pos in revealed:
167
- print("Case déjà révélée.")
168
- continue
169
- if board[r][c] == -1:
170
- # perdu
171
- lost = True
172
- revealed.add(pos)
173
- continue
174
- # si case sans voisins, on fait flood fill, sinon on révèle uniquement
175
- if board[r][c] == 0:
176
- flood_fill(board, r, c, rows, cols, revealed)
177
- else:
178
- revealed.add(pos)
179
-
180
- # ---------- Interface simple pour lancer ----------
181
- def main():
182
- # possibilité de passer dimensions en argument: python demineur.py rows cols mines
183
- rows = DEFAULT_ROWS
184
- cols = DEFAULT_COLS
185
- mines = DEFAULT_MINES
186
- if len(sys.argv) >= 4:
187
- try:
188
- rows = int(sys.argv[1])
189
- cols = int(sys.argv[2])
190
- mines = int(sys.argv[3])
191
- except Exception:
192
- print("Usage: python demineur.py [rows cols mines]")
193
- return
194
- elif len(sys.argv) == 2:
195
- # si un seul argument, on l'interprète comme taille carrée
196
- try:
197
- rows = cols = int(sys.argv[1])
198
- except Exception:
199
- print("Usage: python demineur.py [rows cols mines]")
200
- return
201
-
202
- # borne simple
203
- if rows <= 0 or cols <= 0 or mines <= 0:
204
- print("Les valeurs doivent être positives.")
205
- return
206
- if mines >= rows*cols:
207
- print("Trop de mines pour la taille de la grille.")
208
- return
209
-
210
- run_game(rows, cols, mines)
211
-
212
- if __name__ == "__main__":
213
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect } from 'react';
2
+ import { Bomb } from 'lucide-react';
3
+
4
+ const ChessMinesweeper = () => {
5
+ const [board, setBoard] = useState([]);
6
+ const [selectedPiece, setSelectedPiece] = useState(null);
7
+ const [currentPlayer, setCurrentPlayer] = useState('white');
8
+ const [gameOver, setGameOver] = useState(false);
9
+ const [message, setMessage] = useState('');
10
+ const [capturedPieces, setCapturedPieces] = useState({ white: [], black: [] });
11
+
12
+ const pieceSymbols = {
13
+ white: { king: '♔', queen: '♕', rook: '♖', bishop: '♗', knight: '♘', pawn: '♙' },
14
+ black: { king: '♚', queen: '♛', rook: '♜', bishop: '♝', knight: '♞', pawn: '♟' }
15
+ };
16
+
17
+ useEffect(() => {
18
+ initBoard();
19
+ }, []);
20
+
21
+ const initBoard = () => {
22
+ const newBoard = Array(8).fill(null).map(() => Array(8).fill(null));
23
+
24
+ // Placer les pièces blanches (rangée 7 et 6)
25
+ newBoard[7][0] = { type: 'rook', color: 'white' };
26
+ newBoard[7][1] = { type: 'knight', color: 'white' };
27
+ newBoard[7][2] = { type: 'bishop', color: 'white' };
28
+ newBoard[7][3] = { type: 'queen', color: 'white' };
29
+ newBoard[7][4] = { type: 'king', color: 'white' };
30
+ newBoard[7][5] = { type: 'bishop', color: 'white' };
31
+ newBoard[7][6] = { type: 'knight', color: 'white' };
32
+ newBoard[7][7] = { type: 'rook', color: 'white' };
33
+ for (let i = 0; i < 8; i++) {
34
+ newBoard[6][i] = { type: 'pawn', color: 'white', hasMoved: false };
35
+ }
36
+
37
+ // Placer les pièces noires (rangée 0 et 1)
38
+ newBoard[0][0] = { type: 'rook', color: 'black' };
39
+ newBoard[0][1] = { type: 'knight', color: 'black' };
40
+ newBoard[0][2] = { type: 'bishop', color: 'black' };
41
+ newBoard[0][3] = { type: 'queen', color: 'black' };
42
+ newBoard[0][4] = { type: 'king', color: 'black' };
43
+ newBoard[0][5] = { type: 'bishop', color: 'black' };
44
+ newBoard[0][6] = { type: 'knight', color: 'black' };
45
+ newBoard[0][7] = { type: 'rook', color: 'black' };
46
+ for (let i = 0; i < 8; i++) {
47
+ newBoard[1][i] = { type: 'pawn', color: 'black', hasMoved: false };
48
+ }
49
+
50
+ // Générer les mines dans les rangées du milieu (2-5)
51
+ const mines = new Set();
52
+ const mineCount = 12;
53
+
54
+ while (mines.size < mineCount) {
55
+ const row = 2 + Math.floor(Math.random() * 4);
56
+ const col = Math.floor(Math.random() * 8);
57
+ mines.add(`${row},${col}`);
58
+ }
59
+
60
+ // Ajouter les mines et calculer les nombres adjacents
61
+ for (let row = 2; row <= 5; row++) {
62
+ for (let col = 0; col < 8; col++) {
63
+ const key = `${row},${col}`;
64
+ if (mines.has(key)) {
65
+ newBoard[row][col] = { type: 'mine', revealed: false };
66
+ } else {
67
+ const adjacentMines = countAdjacentMines(row, col, mines);
68
+ newBoard[row][col] = { type: 'empty', revealed: false, count: adjacentMines };
69
+ }
70
+ }
71
+ }
72
+
73
+ // Ajouter les indices de mines pour les rangées 1 et 6 (où sont les pions)
74
+ for (let col = 0; col < 8; col++) {
75
+ // Rangée 1 (pions noirs)
76
+ const count1 = countAdjacentMines(1, col, mines);
77
+ if (count1 > 0) {
78
+ newBoard[1][col] = { ...newBoard[1][col], mineCount: count1 };
79
+ }
80
+
81
+ // Rangée 6 (pions blancs)
82
+ const count6 = countAdjacentMines(6, col, mines);
83
+ if (count6 > 0) {
84
+ newBoard[6][col] = { ...newBoard[6][col], mineCount: count6 };
85
+ }
86
+ }
87
+
88
+ setBoard(newBoard);
89
+ setCurrentPlayer('white');
90
+ setGameOver(false);
91
+ setMessage('');
92
+ setCapturedPieces({ white: [], black: [] });
93
+ };
94
+
95
+ const countAdjacentMines = (row, col, mines) => {
96
+ let count = 0;
97
+ for (let dr = -1; dr <= 1; dr++) {
98
+ for (let dc = -1; dc <= 1; dc++) {
99
+ if (dr === 0 && dc === 0) continue;
100
+ const nr = row + dr;
101
+ const nc = col + dc;
102
+ // Vérifier toutes les cases adjacentes, pas seulement la zone démineur
103
+ if (nr >= 0 && nr < 8 && nc >= 0 && nc < 8) {
104
+ if (mines.has(`${nr},${nc}`)) count++;
105
+ }
106
+ }
107
+ }
108
+ return count;
109
+ };
110
+
111
+ const handleSquareClick = (row, col) => {
112
+ if (gameOver) return;
113
+
114
+ const cell = board[row][col];
115
+ const actualPiece = cell?.piece || cell;
116
+
117
+ // Si on clique sur une pièce
118
+ if (actualPiece && actualPiece.color && ['king', 'queen', 'rook', 'bishop', 'knight', 'pawn'].includes(actualPiece.type)) {
119
+ if (actualPiece.color === currentPlayer) {
120
+ setSelectedPiece({ row, col });
121
+ return;
122
+ }
123
+ }
124
+
125
+ // Si on a une pièce sélectionnée, essayer de la déplacer
126
+ if (selectedPiece) {
127
+ movePiece(selectedPiece.row, selectedPiece.col, row, col);
128
+ }
129
+ };
130
+
131
+ const movePiece = (fromRow, fromCol, toRow, toCol) => {
132
+ const fromCell = board[fromRow][fromCol];
133
+ const piece = fromCell?.piece || fromCell;
134
+ const toCell = board[toRow][toCol];
135
+ const targetPiece = toCell?.piece || toCell;
136
+
137
+ if (!piece || !['king', 'queen', 'rook', 'bishop', 'knight', 'pawn'].includes(piece.type)) {
138
+ setSelectedPiece(null);
139
+ return;
140
+ }
141
+
142
+ // Vérifier que le mouvement est valide
143
+ if (!isValidMove(fromRow, fromCol, toRow, toCol, piece)) {
144
+ setSelectedPiece(null);
145
+ return;
146
+ }
147
+
148
+ const newBoard = board.map(row => [...row]);
149
+ let newMessage = '';
150
+ let pieceCaptured = false;
151
+
152
+ // Si on capture une pièce ennemie
153
+ if (targetPiece && targetPiece.color && targetPiece.color !== piece.color) {
154
+ const newCaptured = { ...capturedPieces };
155
+ newCaptured[targetPiece.color].push(targetPiece.type);
156
+ setCapturedPieces(newCaptured);
157
+ pieceCaptured = true;
158
+ newMessage = `${piece.type} ${piece.color} capture ${targetPiece.type} ${targetPiece.color}!`;
159
+ }
160
+
161
+ // Si on se déplace sur une case du démineur
162
+ const isMinesweeperZone = toRow >= 2 && toRow <= 5;
163
+ if (isMinesweeperZone && toCell && (toCell.type === 'mine' || toCell.type === 'empty')) {
164
+ // Si c'est une mine (révélée ou non)
165
+ if (toCell.type === 'mine') {
166
+ // BOOM! La pièce est éliminée
167
+ newBoard[fromRow][fromCol] = fromCell.revealed ? { ...fromCell, piece: null } : null;
168
+ newBoard[toRow][toCol] = { type: 'mine', revealed: true };
169
+ newMessage = `💥 BOOM! ${piece.type} ${piece.color} a explosé sur une mine!`;
170
+
171
+ const newCaptured = { ...capturedPieces };
172
+ newCaptured[piece.color].push(piece.type);
173
+ setCapturedPieces(newCaptured);
174
+
175
+ if (piece.type === 'king') {
176
+ setGameOver(true);
177
+ newMessage += ` ${piece.color === 'white' ? 'Noirs' : 'Blancs'} gagnent!`;
178
+ }
179
+ } else if (!toCell.revealed) {
180
+ // Case sûre non révélée, révéler le nombre
181
+ const updatedPiece = piece.type === 'pawn' ? { ...piece, hasMoved: true } : piece;
182
+ newBoard[toRow][toCol] = { ...toCell, revealed: true, piece: updatedPiece };
183
+ newBoard[fromRow][fromCol] = fromCell.revealed ? { ...fromCell, piece: null } : null;
184
+ if (!pieceCaptured) {
185
+ newMessage = `Case révélée: ${toCell.count} mine(s) adjacente(s)`;
186
+ }
187
+ } else {
188
+ // Case sûre déjà révélée
189
+ const updatedPiece = piece.type === 'pawn' ? { ...piece, hasMoved: true } : piece;
190
+ newBoard[toRow][toCol] = { ...toCell, piece: updatedPiece };
191
+ newBoard[fromRow][fromCol] = fromCell.revealed ? { ...fromCell, piece: null } : null;
192
+ }
193
+ } else if (toCell && toCell.revealed && toCell.type === 'empty') {
194
+ // Se déplacer sur une case déjà révélée
195
+ const updatedPiece = piece.type === 'pawn' ? { ...piece, hasMoved: true } : piece;
196
+ newBoard[toRow][toCol] = { ...toCell, piece: updatedPiece };
197
+ newBoard[fromRow][fromCol] = fromCell.revealed ? { ...fromCell, piece: null } : null;
198
+ } else {
199
+ // Se déplacer sur une case normale (hors zone démineur ou pièce ennemie)
200
+ const updatedPiece = piece.type === 'pawn' ? { ...piece, hasMoved: true } : piece;
201
+ newBoard[toRow][toCol] = updatedPiece;
202
+ newBoard[fromRow][fromCol] = fromCell.revealed ? { ...fromCell, piece: null } : null;
203
+ }
204
+
205
+ setBoard(newBoard);
206
+ setSelectedPiece(null);
207
+ setMessage(newMessage);
208
+
209
+ if (!gameOver) {
210
+ setCurrentPlayer(currentPlayer === 'white' ? 'black' : 'white');
211
+ }
212
+ };
213
+
214
+ const isValidMove = (fromRow, fromCol, toRow, toCol, piece) => {
215
+ if (fromRow === toRow && fromCol === toCol) return false;
216
+
217
+ const toCell = board[toRow][toCol];
218
+ const targetPiece = toCell?.piece || toCell;
219
+
220
+ // Ne peut pas capturer sa propre pièce
221
+ if (targetPiece && targetPiece.color === piece.color) return false;
222
+
223
+ const rowDiff = toRow - fromRow;
224
+ const colDiff = toCol - fromCol;
225
+
226
+ switch (piece.type) {
227
+ case 'pawn':
228
+ return isValidPawnMove(fromRow, fromCol, toRow, toCol, piece, targetPiece);
229
+ case 'rook':
230
+ return isValidRookMove(fromRow, fromCol, toRow, toCol);
231
+ case 'knight':
232
+ return isValidKnightMove(rowDiff, colDiff);
233
+ case 'bishop':
234
+ return isValidBishopMove(fromRow, fromCol, toRow, toCol);
235
+ case 'queen':
236
+ return isValidQueenMove(fromRow, fromCol, toRow, toCol);
237
+ case 'king':
238
+ return isValidKingMove(rowDiff, colDiff);
239
+ default:
240
+ return false;
241
+ }
242
+ };
243
+
244
+ const isValidPawnMove = (fromRow, fromCol, toRow, toCol, piece, targetPiece) => {
245
+ const direction = piece.color === 'white' ? -1 : 1;
246
+ const rowDiff = toRow - fromRow;
247
+ const colDiff = Math.abs(toCol - fromCol);
248
+
249
+ // Capture en diagonale
250
+ if (rowDiff === direction && colDiff === 1) {
251
+ return targetPiece && targetPiece.color && targetPiece.color !== piece.color;
252
+ }
253
+
254
+ // Avancer d'une case
255
+ if (rowDiff === direction && colDiff === 0) {
256
+ return !targetPiece || !targetPiece.color;
257
+ }
258
+
259
+ // Premier mouvement: avancer de 2 cases
260
+ if (!piece.hasMoved && colDiff === 0 && rowDiff === direction * 2) {
261
+ const middleRow = fromRow + direction;
262
+ const middleCell = board[middleRow][fromCol];
263
+ const middlePiece = middleCell?.piece || middleCell;
264
+ return (!targetPiece || !targetPiece.color) && (!middlePiece || !middlePiece.color);
265
+ }
266
+
267
+ return false;
268
+ };
269
+
270
+ const isValidRookMove = (fromRow, fromCol, toRow, toCol) => {
271
+ if (fromRow !== toRow && fromCol !== toCol) return false;
272
+ return isPathClear(fromRow, fromCol, toRow, toCol);
273
+ };
274
+
275
+ const isValidKnightMove = (rowDiff, colDiff) => {
276
+ return (Math.abs(rowDiff) === 2 && Math.abs(colDiff) === 1) ||
277
+ (Math.abs(rowDiff) === 1 && Math.abs(colDiff) === 2);
278
+ };
279
+
280
+ const isValidBishopMove = (fromRow, fromCol, toRow, toCol) => {
281
+ if (Math.abs(toRow - fromRow) !== Math.abs(toCol - fromCol)) return false;
282
+ return isPathClear(fromRow, fromCol, toRow, toCol);
283
+ };
284
+
285
+ const isValidQueenMove = (fromRow, fromCol, toRow, toCol) => {
286
+ return isValidRookMove(fromRow, fromCol, toRow, toCol) ||
287
+ isValidBishopMove(fromRow, fromCol, toRow, toCol);
288
+ };
289
+
290
+ const isValidKingMove = (rowDiff, colDiff) => {
291
+ return Math.abs(rowDiff) <= 1 && Math.abs(colDiff) <= 1;
292
+ };
293
+
294
+ const isPathClear = (fromRow, fromCol, toRow, toCol) => {
295
+ const rowStep = toRow > fromRow ? 1 : toRow < fromRow ? -1 : 0;
296
+ const colStep = toCol > fromCol ? 1 : toCol < fromCol ? -1 : 0;
297
+
298
+ let currentRow = fromRow + rowStep;
299
+ let currentCol = fromCol + colStep;
300
+
301
+ while (currentRow !== toRow || currentCol !== toCol) {
302
+ const cell = board[currentRow][currentCol];
303
+ const piece = cell?.piece || cell;
304
+ if (piece && piece.color) return false;
305
+ currentRow += rowStep;
306
+ currentCol += colStep;
307
+ }
308
+
309
+ return true;
310
+ };
311
+
312
+ const renderSquare = (row, col) => {
313
+ const cell = board[row][col];
314
+ const piece = cell?.piece || cell;
315
+ const isSelected = selectedPiece && selectedPiece.row === row && selectedPiece.col === col;
316
+ const isDark = (row + col) % 2 === 1;
317
+
318
+ let bgColor = isDark ? 'bg-amber-700' : 'bg-amber-100';
319
+ if (isSelected) bgColor = 'bg-blue-400';
320
+
321
+ const isMinesweeperZone = row >= 2 && row <= 5;
322
+ if (isMinesweeperZone && cell && !cell.revealed) {
323
+ bgColor = isDark ? 'bg-gray-600' : 'bg-gray-400';
324
+ } else if (isMinesweeperZone && cell && cell.revealed) {
325
+ // Cases révélées en gris clair comme dans le vrai démineur
326
+ bgColor = 'bg-gray-300';
327
+ if (isSelected) bgColor = 'bg-blue-400';
328
+ }
329
+
330
+ return (
331
+ <div
332
+ key={`${row}-${col}`}
333
+ onClick={() => handleSquareClick(row, col)}
334
+ className={`w-16 h-16 flex items-center justify-center cursor-pointer border border-gray-800 ${bgColor} hover:opacity-80 transition-all relative`}
335
+ >
336
+ {piece && piece.color && pieceSymbols[piece.color][piece.type] && (
337
+ <>
338
+ <div className={`text-5xl ${piece.color === 'white' ? 'text-white' : 'text-gray-900'} drop-shadow-lg`}>
339
+ {pieceSymbols[piece.color][piece.type]}
340
+ </div>
341
+ {piece.mineCount > 0 && (row === 1 || row === 6) && (
342
+ <div className="absolute bottom-1 right-1 text-xs font-bold bg-yellow-400 rounded-full w-5 h-5 flex items-center justify-center">
343
+ {piece.mineCount}
344
+ </div>
345
+ )}
346
+ </>
347
+ )}
348
+ {cell && cell.revealed && cell.type === 'mine' && (
349
+ <Bomb className="text-red-600" size={32} />
350
+ )}
351
+ {cell && cell.revealed && cell.type === 'empty' && cell.count > 0 && !cell.piece && (
352
+ <div className="text-2xl font-bold text-blue-600">
353
+ {cell.count}
354
+ </div>
355
+ )}
356
+ {cell && cell.revealed && cell.type === 'empty' && cell.piece && (
357
+ <div className="absolute bottom-1 right-1 text-xs font-bold bg-yellow-400 rounded-full w-5 h-5 flex items-center justify-center">
358
+ {cell.count}
359
+ </div>
360
+ )}
361
+ </div>
362
+ );
363
+ };
364
+
365
+ return (
366
+ <div className="flex flex-col items-center justify-center min-h-screen bg-gradient-to-br from-slate-800 to-slate-900 p-8">
367
+ <h1 className="text-4xl font-bold text-white mb-4">♔ Échecs Démineur ♚</h1>
368
+
369
+ <div className="mb-4 text-center">
370
+ <div className="text-xl text-white mb-2">
371
+ Tour: <span className={currentPlayer === 'white' ? 'text-white font-bold' : 'text-gray-300 font-bold'}>
372
+ {currentPlayer === 'white' ? 'Blancs ♔' : 'Noirs ♚'}
373
+ </span>
374
+ </div>
375
+ {message && (
376
+ <div className="bg-yellow-500 text-black px-4 py-2 rounded-lg font-semibold max-w-md">
377
+ {message}
378
+ </div>
379
+ )}
380
+ </div>
381
+
382
+ <div className="flex gap-8 mb-4">
383
+ <div className="bg-gray-800 p-3 rounded-lg">
384
+ <div className="text-white font-bold mb-2">Pièces capturées (Blancs)</div>
385
+ <div className="text-3xl">{capturedPieces.white.map((p, i) => pieceSymbols.white[p]).join(' ')}</div>
386
+ </div>
387
+ <div className="bg-gray-800 p-3 rounded-lg">
388
+ <div className="text-white font-bold mb-2">Pièces capturées (Noirs)</div>
389
+ <div className="text-3xl">{capturedPieces.black.map((p, i) => pieceSymbols.black[p]).join(' ')}</div>
390
+ </div>
391
+ </div>
392
+
393
+ <div className="inline-block border-4 border-gray-900 shadow-2xl mb-4">
394
+ {board.map((row, rowIndex) => (
395
+ <div key={rowIndex} className="flex">
396
+ {row.map((_, colIndex) => renderSquare(rowIndex, colIndex))}
397
+ </div>
398
+ ))}
399
+ </div>
400
+
401
+ <button
402
+ onClick={initBoard}
403
+ className="bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-6 rounded-lg transition-colors"
404
+ >
405
+ Nouvelle Partie
406
+ </button>
407
+
408
+ <div className="mt-4 text-gray-300 text-sm max-w-md text-center">
409
+ <p>♟️ Règles complètes des échecs appliquées</p>
410
+ <p>💣 Attention: Les 4 rangées du milieu cachent des mines!</p>
411
+ <p>🎯 Capturez le roi ennemi ou faites-le exploser pour gagner</p>
412
+ </div>
413
+ </div>
414
+ );
415
+ };
416
+
417
+ export default ChessMinesweeper;