Docfile commited on
Commit
6ab1f80
·
verified ·
1 Parent(s): 294f52c

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +312 -274
templates/index.html CHANGED
@@ -1,4 +1,4 @@
1
- <!DOCTYPE html>
2
  <html lang="fr">
3
  <head>
4
  <meta charset="UTF-8">
@@ -6,193 +6,122 @@
6
  <title>Jeu d'Échecs Tactile</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <style>
9
- .chess-board {
10
- display: grid;
11
- grid-template-columns: repeat(8, 1fr);
12
- grid-template-rows: repeat(8, 1fr);
13
- aspect-ratio: 1;
14
- max-width: 600px;
15
- margin: 0 auto;
16
- border: 3px solid #374151;
17
- border-radius: 8px;
18
- overflow: hidden;
19
- user-select: none;
20
  }
21
-
22
- .chess-square {
23
- position: relative;
24
- display: flex;
25
- align-items: center;
26
- justify-content: center;
27
- cursor: pointer;
28
- transition: all 0.2s ease;
29
- }
30
-
31
- .chess-square.light {
32
- background-color: #f0d9b5;
33
- }
34
-
35
- .chess-square.dark {
36
- background-color: #b58863;
37
- }
38
-
39
- .chess-square.selected {
40
- box-shadow: inset 0 0 0 4px #fbbf24;
41
- z-index: 2;
42
- }
43
-
44
- .chess-square.possible-move {
45
- box-shadow: inset 0 0 0 3px #10b981;
46
- }
47
-
48
- .chess-square.possible-move:after {
49
  content: '';
50
  position: absolute;
51
- width: 30%;
52
- height: 30%;
53
- background-color: #10b981;
54
- border-radius: 50%;
55
- opacity: 0.7;
56
- }
57
-
58
- .chess-square.last-move {
59
- box-shadow: inset 0 0 0 3px #eab308;
60
- }
61
-
62
- .chess-piece {
63
- font-size: clamp(2rem, 6vw, 3.5rem);
64
- font-family: 'Segoe UI Symbol', 'Apple Symbols', sans-serif;
65
- cursor: grab;
66
- transition: transform 0.1s ease;
67
- z-index: 1;
68
- }
69
-
70
- .chess-piece:hover {
71
- transform: scale(1.1);
72
  }
73
-
74
- .chess-piece.dragging {
75
- cursor: grabbing;
76
- transform: scale(1.2);
77
- z-index: 10;
78
- pointer-events: none;
79
- }
80
-
81
- .coordinates {
82
- color: #6b7280;
83
- font-size: 0.75rem;
84
- font-weight: bold;
85
- }
86
-
87
- .coord-file {
88
- position: absolute;
89
- bottom: 2px;
90
- right: 4px;
91
- }
92
-
93
- .coord-rank {
94
- position: absolute;
95
- top: 2px;
96
- left: 4px;
97
- }
98
-
99
- @media (max-width: 640px) {
100
- .chess-piece {
101
- font-size: 2rem;
102
- }
103
- .coordinates {
104
- font-size: 0.6rem;
105
- }
106
  }
107
  </style>
108
  </head>
109
- <body class="bg-gray-900 text-gray-100 min-h-screen flex flex-col items-center p-4">
110
 
111
- <div class="container mx-auto max-w-6xl">
112
- <h1 class="text-4xl font-bold text-center my-6 text-teal-400">Jeu d'Échecs Tactile</h1>
113
 
114
- <div class="bg-gray-800 p-6 rounded-lg shadow-xl mb-6">
115
- <h2 class="text-2xl font-semibold mb-3 text-sky-400">Configuration</h2>
116
- <div class="flex flex-wrap gap-4 items-center">
117
- <button id="setPvP" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">
118
  Humain vs Humain
119
  </button>
120
- <div>
121
- <button id="setPvAIWhite" class="bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">
122
  Jouer vs IA (Blancs)
123
  </button>
124
- <button id="setPvAIBlack" class="bg-purple-600 hover:bg-purple-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline mt-2 sm:mt-0 sm:ml-2">
125
  Jouer vs IA (Noirs)
126
  </button>
127
  </div>
128
- <button id="resetGame" class="bg-red-600 hover:bg-red-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">
129
  Nouvelle Partie
130
  </button>
131
  </div>
132
- <p class="mt-3 text-sm text-gray-400">Mode: <span id="currentMode" class="font-semibold">PVP</span></p>
133
  <p id="playerColorInfo" class="mt-1 text-sm text-gray-400 hidden">
134
- Vous jouez: <span id="currentPlayerColor" class="font-semibold">Blancs</span>
135
  </p>
136
  </div>
137
 
138
- <div class="grid lg:grid-cols-4 gap-6">
139
- <div class="lg:col-span-3 bg-gray-800 p-4 rounded-lg shadow-xl">
140
- <div id="chessBoard" class="chess-board"></div>
141
- <div class="mt-4 text-center text-sm text-gray-400">
142
- <p>Cliquez sur une pièce puis sur la destination, ou glissez-déposez</p>
 
 
143
  </div>
144
  </div>
145
 
146
- <div class="bg-gray-800 p-6 rounded-lg shadow-xl">
147
- <h3 class="text-xl font-semibold mb-3 text-sky-400">Informations</h3>
148
- <p id="turnDisplay" class="mb-2">Tour: <span class="font-bold text-white">Blancs</span></p>
149
- <p id="status" class="text-yellow-400 font-semibold mb-4 min-h-6"></p>
150
 
151
- <div id="outcomeDisplay" class="mb-4 text-lg font-bold text-center"></div>
152
 
153
- <div class="space-y-3">
154
  <div>
155
- <p class="text-sm text-gray-400">Dernier coup:</p>
156
- <p id="lastMove" class="text-gray-200 font-mono text-sm">-</p>
157
  </div>
158
  <div>
159
- <p class="text-sm text-gray-400">Dernier coup IA:</p>
160
- <p id="lastAIMove" class="text-gray-200 font-mono text-sm">-</p>
161
  </div>
162
  </div>
163
 
164
  <div class="mt-6">
165
- <h4 class="text-lg font-semibold mb-2 text-sky-400">Notation manuelle</h4>
166
  <form id="moveForm" class="space-y-3">
167
  <input type="text" id="moveInput"
168
- class="w-full bg-gray-700 border border-gray-600 rounded-md py-2 px-3 focus:outline-none focus:ring-sky-500 focus:border-sky-500 text-white text-sm"
169
- placeholder="e.g., e2e4, Nf3">
170
  <button type="submit"
171
- class="w-full bg-teal-600 hover:bg-teal-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline transition duration-150 ease-in-out">
172
  Jouer
173
  </button>
174
  </form>
175
  </div>
176
  </div>
177
  </div>
 
 
 
178
  </div>
179
 
180
  <script>
181
- // Configuration du jeu
182
  let gameMode = 'pvp';
183
  let playerColor = 'white';
184
  let isPlayerTurn = true;
185
  let gameBoard = {};
186
  let selectedSquare = null;
187
- let possibleMoves = [];
188
  let lastMoveSquares = [];
189
 
190
- // Éléments DOM
191
  const chessBoardEl = document.getElementById('chessBoard');
192
  const moveForm = document.getElementById('moveForm');
193
  const moveInput = document.getElementById('moveInput');
194
  const statusDisplay = document.getElementById('status');
195
- const turnDisplay = document.getElementById('turnDisplay').querySelector('span');
196
  const outcomeDisplay = document.getElementById('outcomeDisplay');
197
  const lastMoveDisplay = document.getElementById('lastMove');
198
  const lastAIMoveDisplay = document.getElementById('lastAIMove');
@@ -200,42 +129,46 @@
200
  const playerColorInfoDisplay = document.getElementById('playerColorInfo');
201
  const currentPlayerColorDisplay = document.getElementById('currentPlayerColor');
202
 
203
- // Symboles des pièces d'échecs
204
  const pieceSymbols = {
205
  'K': '♔', 'Q': '♕', 'R': '♖', 'B': '♗', 'N': '♘', 'P': '♙',
206
  'k': '♚', 'q': '♛', 'r': '♜', 'b': '♝', 'n': '♞', 'p': '♟'
207
  };
 
 
 
208
 
209
- // Position initiale FEN
210
- let currentFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
 
 
211
 
212
  function createChessBoard() {
213
  chessBoardEl.innerHTML = '';
214
 
215
  for (let rank = 8; rank >= 1; rank--) {
216
  for (let file = 0; file < 8; file++) {
217
- const fileChar = String.fromCharCode(97 + file); // a-h
218
- const square = fileChar + rank;
219
 
220
  const squareEl = document.createElement('div');
221
- squareEl.className = `chess-square ${(rank + file) % 2 === 0 ? 'light' : 'dark'}`;
222
- squareEl.dataset.square = square;
 
 
223
 
224
- // Ajouter les coordonnées
225
  if (file === 0) {
226
  const rankCoord = document.createElement('div');
227
- rankCoord.className = 'coordinates coord-rank';
228
  rankCoord.textContent = rank;
229
  squareEl.appendChild(rankCoord);
230
  }
231
  if (rank === 1) {
232
  const fileCoord = document.createElement('div');
233
- fileCoord.className = 'coordinates coord-file';
234
  fileCoord.textContent = fileChar;
235
  squareEl.appendChild(fileCoord);
236
  }
237
 
238
- // Event listeners pour les interactions
239
  squareEl.addEventListener('click', handleSquareClick);
240
  squareEl.addEventListener('dragover', handleDragOver);
241
  squareEl.addEventListener('drop', handleDrop);
@@ -248,10 +181,8 @@
248
  function updateBoardFromFEN(fen) {
249
  const [boardPart] = fen.split(' ');
250
  const ranks = boardPart.split('/');
251
-
252
  gameBoard = {};
253
 
254
- // Effacer toutes les pièces actuelles
255
  document.querySelectorAll('.chess-piece').forEach(piece => piece.remove());
256
 
257
  for (let rankIdx = 0; rankIdx < 8; rankIdx++) {
@@ -260,20 +191,35 @@
260
  let fileIdx = 0;
261
 
262
  for (let char of rankStr) {
263
- if (isNaN(char)) {
264
- // C'est une pièce
265
  const file = String.fromCharCode(97 + fileIdx);
266
- const square = file + rank;
267
- gameBoard[square] = char;
268
 
269
- const squareEl = document.querySelector(`[data-square="${square}"]`);
270
  if (squareEl) {
271
  const pieceEl = document.createElement('div');
272
- pieceEl.className = 'chess-piece';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
  pieceEl.textContent = pieceSymbols[char];
274
  pieceEl.draggable = true;
275
  pieceEl.dataset.piece = char;
276
- pieceEl.dataset.square = square;
277
 
278
  pieceEl.addEventListener('dragstart', handleDragStart);
279
  pieceEl.addEventListener('dragend', handleDragEnd);
@@ -282,19 +228,18 @@
282
  }
283
  fileIdx++;
284
  } else {
285
- // C'est un nombre (cases vides)
286
  fileIdx += parseInt(char);
287
  }
288
  }
289
  }
290
-
291
  currentFEN = fen;
292
  updateTurnDisplay();
293
  }
294
 
295
  function updateTurnDisplay() {
296
  const turn = currentFEN.split(' ')[1];
297
- turnDisplay.textContent = turn === 'w' ? 'Blancs' : 'Noirs';
 
298
 
299
  if (gameMode === 'ai') {
300
  const aiIsWhite = (playerColor === 'black');
@@ -303,178 +248,205 @@
303
  if ((aiIsWhite && turn === 'w') || (aiIsBlack && turn === 'b')) {
304
  isPlayerTurn = false;
305
  statusDisplay.textContent = "L'IA réfléchit...";
306
- // En mode IA, on pourrait désactiver les interactions ici
307
  } else {
308
  isPlayerTurn = true;
309
  statusDisplay.textContent = "À vous de jouer";
310
  }
311
  } else {
312
  isPlayerTurn = true;
313
- statusDisplay.textContent = "";
 
 
 
 
314
  }
315
  }
316
 
317
  function handleSquareClick(e) {
318
  if (!isPlayerTurn) return;
319
 
320
- const square = e.currentTarget.dataset.square;
321
- const piece = gameBoard[square];
322
 
323
  if (selectedSquare) {
324
- if (selectedSquare === square) {
325
- // Déselection
326
- clearSelection();
327
- } else if (possibleMoves.includes(square)) {
328
- // Coup valide
329
- makeMove(selectedSquare + square);
330
  } else if (piece && isPlayerPiece(piece)) {
331
- // Sélection d'une autre pièce
332
- selectSquare(square);
333
  } else {
334
- clearSelection();
335
  }
336
  } else if (piece && isPlayerPiece(piece)) {
337
- selectSquare(square);
338
  }
339
  }
340
 
341
- function selectSquare(square) {
342
- clearSelection();
343
- selectedSquare = square;
344
 
345
- const squareEl = document.querySelector(`[data-square="${square}"]`);
346
- squareEl.classList.add('selected');
347
 
348
- // Simuler les coups possibles (ici on devrait faire appel au serveur)
349
- showPossibleMoves(square);
350
  }
351
 
352
- function clearSelection() {
353
  selectedSquare = null;
354
- possibleMoves = [];
355
 
356
- document.querySelectorAll('.chess-square').forEach(sq => {
357
- sq.classList.remove('selected', 'possible-move');
 
 
 
 
 
 
 
358
  });
359
  }
360
 
361
- function showPossibleMoves(fromSquare) {
362
- // Pour l'instant, on simule quelques coups possibles
363
- // Dans une vraie implémentation, on ferait appel au serveur
364
- const piece = gameBoard[fromSquare];
365
- possibleMoves = getPossibleMovesForPiece(fromSquare, piece);
366
 
367
- possibleMoves.forEach(square => {
368
- const squareEl = document.querySelector(`[data-square="${square}"]`);
369
  if (squareEl) {
370
- squareEl.classList.add('possible-move');
371
  }
372
  });
373
  }
374
 
375
- function getPossibleMovesForPiece(square, piece) {
376
- // Simulation basique - dans la vraie version, ceci viendrait du serveur
377
  const moves = [];
378
- const file = square.charCodeAt(0) - 97;
379
- const rank = parseInt(square[1]);
 
380
 
381
- // Exemple pour un pion
382
  if (piece.toLowerCase() === 'p') {
383
  const direction = piece === 'P' ? 1 : -1;
384
- const newRank = rank + direction;
385
- if (newRank >= 1 && newRank <= 8) {
386
- const newSquare = String.fromCharCode(97 + file) + newRank;
387
- if (!gameBoard[newSquare]) {
388
- moves.push(newSquare);
389
-
390
- // Double move from starting position
391
- if ((piece === 'P' && rank === 2) || (piece === 'p' && rank === 7)) {
392
- const doubleSquare = String.fromCharCode(97 + file) + (rank + 2 * direction);
393
- if (!gameBoard[doubleSquare]) {
394
- moves.push(doubleSquare);
395
- }
396
  }
397
  }
 
 
 
 
 
 
 
 
 
398
  }
399
  }
400
-
401
  return moves;
402
  }
 
 
 
 
 
 
 
 
403
 
404
  function isPlayerPiece(piece) {
405
- if (gameMode === 'pvp') return true;
 
 
 
 
 
 
406
 
407
- const isWhitePiece = piece === piece.toUpperCase();
408
- return (playerColor === 'white' && isWhitePiece) ||
409
- (playerColor === 'black' && !isWhitePiece);
410
  }
411
 
412
- // Gestion du drag & drop
413
  function handleDragStart(e) {
414
- if (!isPlayerTurn) {
415
- e.preventDefault();
416
- return;
417
- }
418
-
419
  const piece = e.target.dataset.piece;
420
- if (!isPlayerPiece(piece)) {
421
  e.preventDefault();
422
  return;
423
  }
424
 
425
- e.target.classList.add('dragging');
426
  e.dataTransfer.setData('text/plain', e.target.dataset.square);
 
427
 
428
- // Sélectionner la case pour montrer les coups possibles
429
- selectSquare(e.target.dataset.square);
430
  }
431
 
432
  function handleDragEnd(e) {
433
- e.target.classList.remove('dragging');
 
434
  }
435
 
436
  function handleDragOver(e) {
437
- e.preventDefault();
 
438
  }
439
 
440
  function handleDrop(e) {
441
  e.preventDefault();
442
- const fromSquare = e.dataTransfer.getData('text/plain');
443
- const toSquare = e.currentTarget.dataset.square;
 
 
 
 
 
444
 
445
- if (fromSquare && toSquare && fromSquare !== toSquare) {
446
- if (possibleMoves.includes(toSquare)) {
447
- makeMove(fromSquare + toSquare);
 
 
448
  }
 
 
449
  }
450
-
451
- clearSelection();
452
  }
453
-
454
- function highlightLastMove(move) {
455
- // Effacer les anciens highlights
456
- document.querySelectorAll('.chess-square').forEach(sq => {
457
- sq.classList.remove('last-move');
458
  });
459
 
460
- if (move && move.length >= 4) {
461
- const fromSquare = move.substring(0, 2);
462
- const toSquare = move.substring(2, 4);
463
 
464
- const fromEl = document.querySelector(`[data-square="${fromSquare}"]`);
465
- const toEl = document.querySelector(`[data-square="${toSquare}"]`);
466
 
467
- if (fromEl) fromEl.classList.add('last-move');
468
- if (toEl) toEl.classList.add('last-move');
469
 
470
- lastMoveSquares = [fromSquare, toSquare];
471
  }
472
  }
473
 
474
- // Communication avec le serveur (adapté de votre code original)
475
  async function makeMove(moveStr) {
476
  statusDisplay.textContent = 'Traitement...';
477
- clearSelection();
478
 
479
  try {
480
  const response = await fetch('/make_move', {
@@ -486,62 +458,85 @@
486
 
487
  if (data.error) {
488
  statusDisplay.textContent = `Erreur: ${data.error}`;
 
 
489
  } else {
 
490
  updateBoardFromFEN(data.fen);
491
- lastMoveDisplay.textContent = moveStr;
492
- highlightLastMove(moveStr);
493
 
494
  if (data.ai_move_uci) {
495
- lastAIMoveDisplay.textContent = data.ai_move_uci;
496
- highlightLastMove(data.ai_move_uci);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
497
  }
498
 
499
- if (data.game_over) {
500
- statusDisplay.textContent = "Partie terminée!";
501
- outcomeDisplay.innerHTML = `<p class="text-green-400">${data.outcome}</p>`;
502
- isPlayerTurn = false;
503
  }
504
  }
505
  } catch (error) {
506
  console.error("Erreur lors de la communication:", error);
507
  statusDisplay.textContent = "Erreur de communication avec le serveur.";
 
508
  }
509
  }
 
 
 
 
 
 
 
510
 
511
- // Form de saisie manuelle
512
  moveForm.addEventListener('submit', async (e) => {
513
  e.preventDefault();
514
  const move = moveInput.value.trim();
515
- if (!move) return;
516
-
517
  await makeMove(move);
518
  moveInput.value = '';
519
  });
520
 
521
- // Boutons de configuration
522
  document.getElementById('resetGame').addEventListener('click', async () => {
523
  statusDisplay.textContent = 'Réinitialisation...';
524
  try {
525
  const response = await fetch('/reset_game', { method: 'POST' });
526
  const data = await response.json();
527
 
 
 
 
 
528
  updateBoardFromFEN(data.fen);
529
  lastMoveDisplay.textContent = "-";
530
  lastAIMoveDisplay.textContent = "-";
531
  outcomeDisplay.innerHTML = '';
532
- clearSelection();
533
 
534
- currentModeDisplay.textContent = data.game_mode ? data.game_mode.toUpperCase() : 'PVP';
535
- if (data.game_mode === 'ai' && data.player_color) {
536
- currentPlayerColorDisplay.textContent = data.player_color.charAt(0).toUpperCase() + data.player_color.slice(1);
537
  playerColorInfoDisplay.classList.remove('hidden');
538
  } else {
539
  playerColorInfoDisplay.classList.add('hidden');
540
  }
541
-
542
- gameMode = data.game_mode || 'pvp';
543
- playerColor = data.player_color || 'white';
544
- updateTurnDisplay();
545
 
546
  } catch (error) {
547
  console.error("Erreur lors de la réinitialisation:", error);
@@ -549,13 +544,13 @@
549
  }
550
  });
551
 
552
- async function setGameMode(mode, pColor = 'white') {
553
- statusDisplay.textContent = `Changement de mode vers ${mode.toUpperCase()}...`;
554
  try {
555
  const response = await fetch('/set_mode', {
556
  method: 'POST',
557
  headers: { 'Content-Type': 'application/json' },
558
- body: JSON.stringify({ game_mode: mode, player_color: pColor })
559
  });
560
  const data = await response.json();
561
 
@@ -564,22 +559,34 @@
564
  } else {
565
  gameMode = data.game_mode;
566
  playerColor = data.player_color;
 
567
 
568
  updateBoardFromFEN(data.fen);
569
- clearSelection();
570
 
571
  currentModeDisplay.textContent = gameMode.toUpperCase();
572
  if (gameMode === 'ai') {
573
- currentPlayerColorDisplay.textContent = playerColor.charAt(0).toUpperCase() + playerColor.slice(1);
574
  playerColorInfoDisplay.classList.remove('hidden');
575
  } else {
576
  playerColorInfoDisplay.classList.add('hidden');
577
  }
578
 
579
- statusDisplay.textContent = data.message || '';
580
- if (data.initial_ai_move_uci) {
581
- lastAIMoveDisplay.textContent = data.initial_ai_move_uci;
582
- highlightLastMove(data.initial_ai_move_uci);
 
 
 
 
 
 
 
 
 
 
 
583
  }
584
  }
585
  } catch (error) {
@@ -592,9 +599,40 @@
592
  document.getElementById('setPvAIWhite').addEventListener('click', () => setGameMode('ai', 'white'));
593
  document.getElementById('setPvAIBlack').addEventListener('click', () => setGameMode('ai', 'black'));
594
 
595
- // Initialisation
596
- createChessBoard();
597
- updateBoardFromFEN(currentFEN);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
598
  </script>
599
  </body>
600
  </html>
 
1
+ <!DOCTYPE html>
2
  <html lang="fr">
3
  <head>
4
  <meta charset="UTF-8">
 
6
  <title>Jeu d'Échecs Tactile</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <style>
9
+ /* Minimal custom CSS - mostly for things Tailwind doesn't handle easily or for fine-tuning */
10
+ .chess-piece-font {
11
+ font-family: 'Segoe UI Symbol', 'Apple Symbols', 'Noto Sans Symbols 2', sans-serif; /* Added Noto for better cross-platform symbols */
 
 
 
 
 
 
 
 
12
  }
13
+
14
+ /* Custom style for the dot on possible move squares, if preferred over just a ring */
15
+ .possible-move-dot::after {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  content: '';
17
  position: absolute;
18
+ width: 30%; /* Tailwind: w-[30%] */
19
+ height: 30%; /* Tailwind: h-[30%] */
20
+ background-color: currentColor; /* Inherits color from text-emerald-500 or similar */
21
+ border-radius: 50%; /* Tailwind: rounded-full */
22
+ opacity: 0.6; /* Tailwind: opacity-60 */
23
+ /* Centering can also be done with flex on parent or absolute positioning with translate */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  }
25
+
26
+ /* Ensuring board maintains aspect ratio and grid structure */
27
+ .chess-board-grid {
28
+ display: grid;
29
+ grid-template-columns: repeat(8, 1fr);
30
+ grid-template-rows: repeat(8, 1fr);
31
+ aspect-ratio: 1;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  }
33
  </style>
34
  </head>
35
+ <body class="bg-gray-900 text-gray-100 min-h-screen flex flex-col items-center p-4 selection:bg-teal-500 selection:text-white">
36
 
37
+ <div class="container mx-auto max-w-6xl w-full">
38
+ <h1 class="text-3xl sm:text-4xl font-bold text-center my-6 text-teal-400 tracking-tight">Jeu d'Échecs Tactile</h1>
39
 
40
+ <div class="bg-gray-800 p-4 sm:p-6 rounded-lg shadow-xl mb-6">
41
+ <h2 class="text-xl sm:text-2xl font-semibold mb-4 text-sky-400">Configuration</h2>
42
+ <div class="flex flex-wrap gap-3 items-center">
43
+ <button id="setPvP" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-75 transition-colors">
44
  Humain vs Humain
45
  </button>
46
+ <div class="flex flex-col sm:flex-row gap-2">
47
+ <button id="setPvAIWhite" class="bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-4 rounded-md focus:outline-none focus:ring-2 focus:ring-green-400 focus:ring-opacity-75 transition-colors">
48
  Jouer vs IA (Blancs)
49
  </button>
50
+ <button id="setPvAIBlack" class="bg-purple-600 hover:bg-purple-700 text-white font-bold py-2 px-4 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-400 focus:ring-opacity-75 transition-colors">
51
  Jouer vs IA (Noirs)
52
  </button>
53
  </div>
54
+ <button id="resetGame" class="bg-red-600 hover:bg-red-700 text-white font-bold py-2 px-4 rounded-md focus:outline-none focus:ring-2 focus:ring-red-400 focus:ring-opacity-75 transition-colors">
55
  Nouvelle Partie
56
  </button>
57
  </div>
58
+ <p class="mt-3 text-sm text-gray-400">Mode: <span id="currentMode" class="font-semibold text-gray-200">PVP</span></p>
59
  <p id="playerColorInfo" class="mt-1 text-sm text-gray-400 hidden">
60
+ Vous jouez: <span id="currentPlayerColor" class="font-semibold text-gray-200">Blancs</span>
61
  </p>
62
  </div>
63
 
64
+ <div class="grid lg:grid-cols-3 gap-6">
65
+ <div class="lg:col-span-2 bg-gray-800 p-3 sm:p-4 rounded-lg shadow-xl">
66
+ <div id="chessBoard" class="chess-board-grid max-w-[600px] mx-auto border-4 border-gray-700 rounded-md overflow-hidden select-none">
67
+ <!-- Squares will be generated here by JS -->
68
+ </div>
69
+ <div class="mt-4 text-center text-xs sm:text-sm text-gray-400">
70
+ <p>Cliquez sur une pièce puis sur la destination, ou glissez-déposez.</p>
71
  </div>
72
  </div>
73
 
74
+ <div class="bg-gray-800 p-4 sm:p-6 rounded-lg shadow-xl lg:col-span-1">
75
+ <h3 class="text-lg sm:text-xl font-semibold mb-3 text-sky-400">Informations</h3>
76
+ <p id="turnDisplay" class="mb-2 text-sm">Tour: <span class="font-bold text-white">Blancs</span></p>
77
+ <p id="status" class="text-yellow-400 font-semibold mb-4 min-h-[1.5em] text-sm"></p>
78
 
79
+ <div id="outcomeDisplay" class="mb-4 text-lg font-bold text-center text-green-400"></div>
80
 
81
+ <div class="space-y-3 text-sm">
82
  <div>
83
+ <p class="text-gray-400">Dernier coup:</p>
84
+ <p id="lastMove" class="text-gray-200 font-mono">-</p>
85
  </div>
86
  <div>
87
+ <p class="text-gray-400">Dernier coup IA:</p>
88
+ <p id="lastAIMove" class="text-gray-200 font-mono">-</p>
89
  </div>
90
  </div>
91
 
92
  <div class="mt-6">
93
+ <h4 class="text-md sm:text-lg font-semibold mb-2 text-sky-400">Notation manuelle</h4>
94
  <form id="moveForm" class="space-y-3">
95
  <input type="text" id="moveInput"
96
+ class="w-full bg-gray-700 border border-gray-600 rounded-md py-2 px-3 text-white text-sm focus:outline-none focus:ring-2 focus:ring-sky-500 focus:border-sky-500 placeholder-gray-500"
97
+ placeholder="ex: e2e4, Nf3">
98
  <button type="submit"
99
+ class="w-full bg-teal-600 hover:bg-teal-700 text-white font-bold py-2 px-4 rounded-md focus:outline-none focus:ring-2 focus:ring-teal-400 focus:ring-opacity-75 transition duration-150 ease-in-out">
100
  Jouer
101
  </button>
102
  </form>
103
  </div>
104
  </div>
105
  </div>
106
+ <footer class="text-center text-gray-500 py-8 text-sm">
107
+ Jeu d'Échecs Tactile © 2024
108
+ </footer>
109
  </div>
110
 
111
  <script>
 
112
  let gameMode = 'pvp';
113
  let playerColor = 'white';
114
  let isPlayerTurn = true;
115
  let gameBoard = {};
116
  let selectedSquare = null;
117
+ let currentPossibleMoves = []; // Renamed from possibleMoves to avoid conflict
118
  let lastMoveSquares = [];
119
 
 
120
  const chessBoardEl = document.getElementById('chessBoard');
121
  const moveForm = document.getElementById('moveForm');
122
  const moveInput = document.getElementById('moveInput');
123
  const statusDisplay = document.getElementById('status');
124
+ const turnDisplayEl = document.getElementById('turnDisplay').querySelector('span'); // Corrected variable name
125
  const outcomeDisplay = document.getElementById('outcomeDisplay');
126
  const lastMoveDisplay = document.getElementById('lastMove');
127
  const lastAIMoveDisplay = document.getElementById('lastAIMove');
 
129
  const playerColorInfoDisplay = document.getElementById('playerColorInfo');
130
  const currentPlayerColorDisplay = document.getElementById('currentPlayerColor');
131
 
 
132
  const pieceSymbols = {
133
  'K': '♔', 'Q': '♕', 'R': '♖', 'B': '♗', 'N': '♘', 'P': '♙',
134
  'k': '♚', 'q': '♛', 'r': '♜', 'b': '♝', 'n': '♞', 'p': '♟'
135
  };
136
+
137
+ const initialFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
138
+ let currentFEN = initialFEN;
139
 
140
+ // Tailwind classes for square states
141
+ const selectedClasses = ['ring-4', 'ring-yellow-400', 'ring-inset', 'z-20'];
142
+ const possibleMoveClasses = ['ring-2', 'ring-emerald-500', 'ring-inset', 'possible-move-dot', 'text-emerald-500']; // Added text-emerald-500 for the dot
143
+ const lastMoveHighlightClasses = ['ring-2', 'ring-amber-500', 'ring-inset'];
144
 
145
  function createChessBoard() {
146
  chessBoardEl.innerHTML = '';
147
 
148
  for (let rank = 8; rank >= 1; rank--) {
149
  for (let file = 0; file < 8; file++) {
150
+ const fileChar = String.fromCharCode(97 + file);
151
+ const squareId = fileChar + rank;
152
 
153
  const squareEl = document.createElement('div');
154
+ // Base classes for all squares
155
+ squareEl.className = `relative flex items-center justify-center cursor-pointer transition-all duration-150 ease-in-out
156
+ ${(rank + file) % 2 === 0 ? 'bg-[#f0d9b5]' : 'bg-[#b58863]'}`; // Using specific colors
157
+ squareEl.dataset.square = squareId;
158
 
 
159
  if (file === 0) {
160
  const rankCoord = document.createElement('div');
161
+ rankCoord.className = 'absolute top-0.5 left-1 text-xs font-bold text-gray-700 opacity-60';
162
  rankCoord.textContent = rank;
163
  squareEl.appendChild(rankCoord);
164
  }
165
  if (rank === 1) {
166
  const fileCoord = document.createElement('div');
167
+ fileCoord.className = 'absolute bottom-0.5 right-1 text-xs font-bold text-gray-700 opacity-60';
168
  fileCoord.textContent = fileChar;
169
  squareEl.appendChild(fileCoord);
170
  }
171
 
 
172
  squareEl.addEventListener('click', handleSquareClick);
173
  squareEl.addEventListener('dragover', handleDragOver);
174
  squareEl.addEventListener('drop', handleDrop);
 
181
  function updateBoardFromFEN(fen) {
182
  const [boardPart] = fen.split(' ');
183
  const ranks = boardPart.split('/');
 
184
  gameBoard = {};
185
 
 
186
  document.querySelectorAll('.chess-piece').forEach(piece => piece.remove());
187
 
188
  for (let rankIdx = 0; rankIdx < 8; rankIdx++) {
 
191
  let fileIdx = 0;
192
 
193
  for (let char of rankStr) {
194
+ if (isNaN(parseInt(char))) {
 
195
  const file = String.fromCharCode(97 + fileIdx);
196
+ const squareId = file + rank;
197
+ gameBoard[squareId] = char;
198
 
199
+ const squareEl = document.querySelector(`[data-square="${squareId}"]`);
200
  if (squareEl) {
201
  const pieceEl = document.createElement('div');
202
+ // Tailwind classes for pieces
203
+ pieceEl.className = `chess-piece-font text-3xl sm:text-4xl md:text-5xl
204
+ cursor-grab transition-transform duration-100 ease-in-out z-10
205
+ ${char === char.toUpperCase() ? 'text-white' : 'text-black'}
206
+ hover:scale-110`;
207
+ // Note: Piece colors (text-white/text-black) are illustrative.
208
+ // You might want more distinct colors or to use the piece SVGs/images.
209
+ // For unicode, text-white/text-black on light/dark squares might be hard to see.
210
+ // Let's make white pieces one color, black pieces another, clearly distinct from squares
211
+ // For example: White pieces: text-neutral-100, Black pieces: text-neutral-800 (adjust based on square colors)
212
+ // Overriding with more visible defaults:
213
+ pieceEl.className = `chess-piece-font text-4xl sm:text-5xl
214
+ cursor-grab transition-transform duration-100 ease-in-out z-10
215
+ ${char === char.toUpperCase() ? 'text-gray-100' : 'text-gray-900'}
216
+ hover:scale-110`;
217
+
218
+
219
  pieceEl.textContent = pieceSymbols[char];
220
  pieceEl.draggable = true;
221
  pieceEl.dataset.piece = char;
222
+ pieceEl.dataset.square = squareId;
223
 
224
  pieceEl.addEventListener('dragstart', handleDragStart);
225
  pieceEl.addEventListener('dragend', handleDragEnd);
 
228
  }
229
  fileIdx++;
230
  } else {
 
231
  fileIdx += parseInt(char);
232
  }
233
  }
234
  }
 
235
  currentFEN = fen;
236
  updateTurnDisplay();
237
  }
238
 
239
  function updateTurnDisplay() {
240
  const turn = currentFEN.split(' ')[1];
241
+ turnDisplayEl.textContent = turn === 'w' ? 'Blancs' : 'Noirs';
242
+ turnDisplayEl.className = `font-bold ${turn === 'w' ? 'text-white' : 'text-gray-300'}`; // Example dynamic color
243
 
244
  if (gameMode === 'ai') {
245
  const aiIsWhite = (playerColor === 'black');
 
248
  if ((aiIsWhite && turn === 'w') || (aiIsBlack && turn === 'b')) {
249
  isPlayerTurn = false;
250
  statusDisplay.textContent = "L'IA réfléchit...";
 
251
  } else {
252
  isPlayerTurn = true;
253
  statusDisplay.textContent = "À vous de jouer";
254
  }
255
  } else {
256
  isPlayerTurn = true;
257
+ statusDisplay.textContent = ""; // Clear status for PvP or set as appropriate
258
+ }
259
+ // Ensure outcome display is cleared if game is not over
260
+ if (!currentFEN.includes(' # ')) { // Simple check, real game over check is in makeMove
261
+ outcomeDisplay.innerHTML = '';
262
  }
263
  }
264
 
265
  function handleSquareClick(e) {
266
  if (!isPlayerTurn) return;
267
 
268
+ const squareId = e.currentTarget.dataset.square;
269
+ const piece = gameBoard[squareId];
270
 
271
  if (selectedSquare) {
272
+ if (selectedSquare === squareId) {
273
+ clearSelectionAndHighlights();
274
+ } else if (currentPossibleMoves.includes(squareId)) {
275
+ makeMove(selectedSquare + squareId);
 
 
276
  } else if (piece && isPlayerPiece(piece)) {
277
+ selectSquare(squareId);
 
278
  } else {
279
+ clearSelectionAndHighlights();
280
  }
281
  } else if (piece && isPlayerPiece(piece)) {
282
+ selectSquare(squareId);
283
  }
284
  }
285
 
286
+ function selectSquare(squareId) {
287
+ clearSelectionAndHighlights(); // Clear previous selection and its highlights
288
+ selectedSquare = squareId;
289
 
290
+ const squareEl = document.querySelector(`[data-square="${squareId}"]`);
291
+ if (squareEl) squareEl.classList.add(...selectedClasses);
292
 
293
+ showPossibleMovesForSquare(squareId); // Renamed for clarity
 
294
  }
295
 
296
+ function clearSelectionAndHighlights() {
297
  selectedSquare = null;
298
+ currentPossibleMoves = [];
299
 
300
+ document.querySelectorAll('.chess-board-grid > div').forEach(sq => {
301
+ sq.classList.remove(...selectedClasses, ...possibleMoveClasses);
302
+ });
303
+ }
304
+
305
+ function clearAllHighlights() {
306
+ clearSelectionAndHighlights();
307
+ document.querySelectorAll('.chess-board-grid > div').forEach(sq => {
308
+ sq.classList.remove(...lastMoveHighlightClasses);
309
  });
310
  }
311
 
312
+
313
+ function showPossibleMovesForSquare(fromSquareId) {
314
+ // Simulate getting possible moves (replace with actual logic/API call)
315
+ const piece = gameBoard[fromSquareId];
316
+ currentPossibleMoves = getSimulatedPossibleMoves(fromSquareId, piece); // Renamed
317
 
318
+ currentPossibleMoves.forEach(toSquareId => {
319
+ const squareEl = document.querySelector(`[data-square="${toSquareId}"]`);
320
  if (squareEl) {
321
+ squareEl.classList.add(...possibleMoveClasses);
322
  }
323
  });
324
  }
325
 
326
+ // SIMULATED: Replace with actual chess logic or server call
327
+ function getSimulatedPossibleMoves(square, piece) {
328
  const moves = [];
329
+ if (!piece) return moves;
330
+ const file = square.charCodeAt(0) - 97; // 0-7
331
+ const rankVal = parseInt(square[1]); // 1-8
332
 
 
333
  if (piece.toLowerCase() === 'p') {
334
  const direction = piece === 'P' ? 1 : -1;
335
+ const nextRank = rankVal + direction;
336
+ if (nextRank >= 1 && nextRank <= 8) {
337
+ const forwardOne = String.fromCharCode(97 + file) + nextRank;
338
+ if (!gameBoard[forwardOne]) {
339
+ moves.push(forwardOne);
340
+ if ((piece === 'P' && rankVal === 2) || (piece === 'p' && rankVal === 7)) {
341
+ const forwardTwo = String.fromCharCode(97 + file) + (rankVal + 2 * direction);
342
+ if (!gameBoard[forwardTwo]) moves.push(forwardTwo);
 
 
 
 
343
  }
344
  }
345
+ // Capture diagonally (simplified)
346
+ if (file > 0) {
347
+ const captureLeft = String.fromCharCode(97 + file - 1) + nextRank;
348
+ if (gameBoard[captureLeft] && isOpponentPiece(piece, gameBoard[captureLeft])) moves.push(captureLeft);
349
+ }
350
+ if (file < 7) {
351
+ const captureRight = String.fromCharCode(97 + file + 1) + nextRank;
352
+ if (gameBoard[captureRight] && isOpponentPiece(piece, gameBoard[captureRight])) moves.push(captureRight);
353
+ }
354
  }
355
  }
356
+ // Add more piece logic here for simulation if needed
357
  return moves;
358
  }
359
+
360
+ function isOpponentPiece(myPiece, targetPiece) {
361
+ if (!targetPiece) return false;
362
+ const myPieceIsWhite = myPiece === myPiece.toUpperCase();
363
+ const targetPieceIsWhite = targetPiece === targetPiece.toUpperCase();
364
+ return myPieceIsWhite !== targetPieceIsWhite;
365
+ }
366
+
367
 
368
  function isPlayerPiece(piece) {
369
+ if (!piece) return false;
370
+ const currentTurnColor = currentFEN.split(' ')[1]; // 'w' or 'b'
371
+ const pieceIsWhite = piece === piece.toUpperCase();
372
+
373
+ if (gameMode === 'pvp') {
374
+ return (currentTurnColor === 'w' && pieceIsWhite) || (currentTurnColor === 'b' && !pieceIsWhite);
375
+ }
376
 
377
+ // In AI mode, player can only move their own pieces on their turn
378
+ return (playerColor === 'white' && pieceIsWhite && currentTurnColor === 'w') ||
379
+ (playerColor === 'black' && !pieceIsWhite && currentTurnColor === 'b');
380
  }
381
 
 
382
  function handleDragStart(e) {
 
 
 
 
 
383
  const piece = e.target.dataset.piece;
384
+ if (!isPlayerTurn || !isPlayerPiece(piece)) {
385
  e.preventDefault();
386
  return;
387
  }
388
 
389
+ e.target.classList.add('cursor-grabbing', 'scale-125', 'opacity-75', 'z-50');
390
  e.dataTransfer.setData('text/plain', e.target.dataset.square);
391
+ e.dataTransfer.effectAllowed = "move";
392
 
393
+ selectSquare(e.target.dataset.square); // Show possible moves
 
394
  }
395
 
396
  function handleDragEnd(e) {
397
+ e.target.classList.remove('cursor-grabbing', 'scale-125', 'opacity-75', 'z-50');
398
+ // No need to clear selection here, drop or click handles it
399
  }
400
 
401
  function handleDragOver(e) {
402
+ e.preventDefault();
403
+ e.dataTransfer.dropEffect = "move";
404
  }
405
 
406
  function handleDrop(e) {
407
  e.preventDefault();
408
+ const fromSquareId = e.dataTransfer.getData('text/plain');
409
+ const toSquareEl = e.target.closest('[data-square]'); // Find the parent square if dropped on piece/coord
410
+ if (!toSquareEl) {
411
+ clearSelectionAndHighlights();
412
+ return;
413
+ }
414
+ const toSquareId = toSquareEl.dataset.square;
415
 
416
+ if (fromSquareId && toSquareId && fromSquareId !== toSquareId) {
417
+ if (currentPossibleMoves.includes(toSquareId)) {
418
+ makeMove(fromSquareId + toSquareId);
419
+ } else {
420
+ clearSelectionAndHighlights(); // Invalid drop target
421
  }
422
+ } else {
423
+ clearSelectionAndHighlights(); // Dropped on same square or invalid
424
  }
 
 
425
  }
426
+
427
+ function highlightLastMoveUI(moveUci) { // Renamed to avoid conflict
428
+ // Clear previous last move highlights
429
+ document.querySelectorAll('.chess-board-grid > div').forEach(sq => {
430
+ sq.classList.remove(...lastMoveHighlightClasses);
431
  });
432
 
433
+ if (moveUci && moveUci.length >= 4) {
434
+ const fromSquareId = moveUci.substring(0, 2);
435
+ const toSquareId = moveUci.substring(2, 4);
436
 
437
+ const fromEl = document.querySelector(`[data-square="${fromSquareId}"]`);
438
+ const toEl = document.querySelector(`[data-square="${toSquareId}"]`);
439
 
440
+ if (fromEl) fromEl.classList.add(...lastMoveHighlightClasses);
441
+ if (toEl) toEl.classList.add(...lastMoveHighlightClasses);
442
 
443
+ lastMoveSquares = [fromSquareId, toSquareId];
444
  }
445
  }
446
 
 
447
  async function makeMove(moveStr) {
448
  statusDisplay.textContent = 'Traitement...';
449
+ clearSelectionAndHighlights(); // Clear selection and possible moves highlights
450
 
451
  try {
452
  const response = await fetch('/make_move', {
 
458
 
459
  if (data.error) {
460
  statusDisplay.textContent = `Erreur: ${data.error}`;
461
+ // Re-enable turn if it was disabled for AI thinking, or re-select piece
462
+ updateTurnDisplay();
463
  } else {
464
+ currentFEN = data.fen; // Update FEN before updating board
465
  updateBoardFromFEN(data.fen);
466
+ lastMoveDisplay.textContent = data.human_move_san || moveStr; // Use SAN if available
467
+ highlightLastMoveUI(moveStr); // Highlight human move
468
 
469
  if (data.ai_move_uci) {
470
+ // Brief delay to make AI move feel more natural
471
+ setTimeout(() => {
472
+ currentFEN = data.fen; // FEN is already updated by AI on server, re-fetch or use this
473
+ updateBoardFromFEN(data.fen); // Update board again for AI move
474
+ lastAIMoveDisplay.textContent = data.ai_move_san || data.ai_move_uci;
475
+ highlightLastMoveUI(data.ai_move_uci); // Highlight AI move
476
+ updateTurnDisplay(); // Update turn after AI move visualised
477
+
478
+ if (data.game_over) {
479
+ handleGameOver(data.outcome);
480
+ }
481
+ }, gameMode === 'ai' && data.human_move_san ? 500 : 0); // Only delay if AI just played
482
+ } else {
483
+ updateTurnDisplay(); // Update turn if no AI move (PvP)
484
+ if (data.game_over) {
485
+ handleGameOver(data.outcome);
486
+ }
487
  }
488
 
489
+ if (data.game_over && !data.ai_move_uci) { // Game over after human move (e.g. PvP checkmate)
490
+ handleGameOver(data.outcome);
 
 
491
  }
492
  }
493
  } catch (error) {
494
  console.error("Erreur lors de la communication:", error);
495
  statusDisplay.textContent = "Erreur de communication avec le serveur.";
496
+ updateTurnDisplay(); // Reset turn status
497
  }
498
  }
499
+
500
+ function handleGameOver(outcome) {
501
+ statusDisplay.textContent = "Partie terminée!";
502
+ outcomeDisplay.innerHTML = `<p>${outcome}</p>`;
503
+ isPlayerTurn = false; // Disable further moves
504
+ }
505
+
506
 
 
507
  moveForm.addEventListener('submit', async (e) => {
508
  e.preventDefault();
509
  const move = moveInput.value.trim();
510
+ if (!move || !isPlayerTurn) return;
 
511
  await makeMove(move);
512
  moveInput.value = '';
513
  });
514
 
 
515
  document.getElementById('resetGame').addEventListener('click', async () => {
516
  statusDisplay.textContent = 'Réinitialisation...';
517
  try {
518
  const response = await fetch('/reset_game', { method: 'POST' });
519
  const data = await response.json();
520
 
521
+ gameMode = data.game_mode || 'pvp';
522
+ playerColor = data.player_color || 'white';
523
+ currentFEN = data.fen;
524
+
525
  updateBoardFromFEN(data.fen);
526
  lastMoveDisplay.textContent = "-";
527
  lastAIMoveDisplay.textContent = "-";
528
  outcomeDisplay.innerHTML = '';
529
+ clearAllHighlights(); // Clears selection, possible moves, and last move highlights
530
 
531
+ currentModeDisplay.textContent = gameMode.toUpperCase();
532
+ if (gameMode === 'ai') {
533
+ currentPlayerColorDisplay.textContent = playerColor === 'white' ? 'Blancs' : 'Noirs';
534
  playerColorInfoDisplay.classList.remove('hidden');
535
  } else {
536
  playerColorInfoDisplay.classList.add('hidden');
537
  }
538
+ statusDisplay.textContent = "Nouvelle partie prête.";
539
+ updateTurnDisplay(); // This will set isPlayerTurn correctly
 
 
540
 
541
  } catch (error) {
542
  console.error("Erreur lors de la réinitialisation:", error);
 
544
  }
545
  });
546
 
547
+ async function setGameMode(newMode, pColor = 'white') {
548
+ statusDisplay.textContent = `Changement de mode...`;
549
  try {
550
  const response = await fetch('/set_mode', {
551
  method: 'POST',
552
  headers: { 'Content-Type': 'application/json' },
553
+ body: JSON.stringify({ game_mode: newMode, player_color: pColor })
554
  });
555
  const data = await response.json();
556
 
 
559
  } else {
560
  gameMode = data.game_mode;
561
  playerColor = data.player_color;
562
+ currentFEN = data.fen;
563
 
564
  updateBoardFromFEN(data.fen);
565
+ clearAllHighlights();
566
 
567
  currentModeDisplay.textContent = gameMode.toUpperCase();
568
  if (gameMode === 'ai') {
569
+ currentPlayerColorDisplay.textContent = playerColor === 'white' ? 'Blancs' : 'Noirs';
570
  playerColorInfoDisplay.classList.remove('hidden');
571
  } else {
572
  playerColorInfoDisplay.classList.add('hidden');
573
  }
574
 
575
+ statusDisplay.textContent = data.message || `Mode ${gameMode.toUpperCase()} activé.`;
576
+ lastMoveDisplay.textContent = "-";
577
+ lastAIMoveDisplay.textContent = "-";
578
+ outcomeDisplay.innerHTML = '';
579
+
580
+ if (data.initial_ai_move_uci) { // If AI plays first
581
+ setTimeout(() => {
582
+ currentFEN = data.fen; // FEN is already updated by AI on server
583
+ updateBoardFromFEN(data.fen);
584
+ lastAIMoveDisplay.textContent = data.initial_ai_move_san || data.initial_ai_move_uci;
585
+ highlightLastMoveUI(data.initial_ai_move_uci);
586
+ updateTurnDisplay();
587
+ }, 500);
588
+ } else {
589
+ updateTurnDisplay();
590
  }
591
  }
592
  } catch (error) {
 
599
  document.getElementById('setPvAIWhite').addEventListener('click', () => setGameMode('ai', 'white'));
600
  document.getElementById('setPvAIBlack').addEventListener('click', () => setGameMode('ai', 'black'));
601
 
602
+ function initializeGame() {
603
+ createChessBoard();
604
+ // Fetch initial state from server to ensure consistency, or use defaults
605
+ // For now, using default FEN and assuming PvP mode initially
606
+ fetch('/reset_game', { method: 'POST' }) // Call reset on load to get initial state from server
607
+ .then(res => res.json())
608
+ .then(data => {
609
+ gameMode = data.game_mode || 'pvp';
610
+ playerColor = data.player_color || 'white';
611
+ currentFEN = data.fen || initialFEN;
612
+
613
+ updateBoardFromFEN(currentFEN);
614
+ currentModeDisplay.textContent = gameMode.toUpperCase();
615
+ if (gameMode === 'ai') {
616
+ currentPlayerColorDisplay.textContent = playerColor === 'white' ? 'Blancs' : 'Noirs';
617
+ playerColorInfoDisplay.classList.remove('hidden');
618
+ } else {
619
+ playerColorInfoDisplay.classList.add('hidden');
620
+ }
621
+ statusDisplay.textContent = "Prêt à jouer.";
622
+ updateTurnDisplay();
623
+ })
624
+ .catch(err => {
625
+ console.error("Failed to initialize game from server:", err);
626
+ // Fallback to local defaults if server init fails
627
+ updateBoardFromFEN(initialFEN);
628
+ currentModeDisplay.textContent = 'PVP';
629
+ playerColorInfoDisplay.classList.add('hidden');
630
+ statusDisplay.textContent = "Prêt à jouer (mode local).";
631
+ updateTurnDisplay();
632
+ });
633
+ }
634
+
635
+ initializeGame();
636
  </script>
637
  </body>
638
  </html>