darkmaria / templates /index.html
Docfile's picture
Update templates/index.html
6ab1f80 verified
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Jeu d'Échecs Tactile</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
/* Minimal custom CSS - mostly for things Tailwind doesn't handle easily or for fine-tuning */
.chess-piece-font {
font-family: 'Segoe UI Symbol', 'Apple Symbols', 'Noto Sans Symbols 2', sans-serif; /* Added Noto for better cross-platform symbols */
}
/* Custom style for the dot on possible move squares, if preferred over just a ring */
.possible-move-dot::after {
content: '';
position: absolute;
width: 30%; /* Tailwind: w-[30%] */
height: 30%; /* Tailwind: h-[30%] */
background-color: currentColor; /* Inherits color from text-emerald-500 or similar */
border-radius: 50%; /* Tailwind: rounded-full */
opacity: 0.6; /* Tailwind: opacity-60 */
/* Centering can also be done with flex on parent or absolute positioning with translate */
}
/* Ensuring board maintains aspect ratio and grid structure */
.chess-board-grid {
display: grid;
grid-template-columns: repeat(8, 1fr);
grid-template-rows: repeat(8, 1fr);
aspect-ratio: 1;
}
</style>
</head>
<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">
<div class="container mx-auto max-w-6xl w-full">
<h1 class="text-3xl sm:text-4xl font-bold text-center my-6 text-teal-400 tracking-tight">Jeu d'Échecs Tactile</h1>
<div class="bg-gray-800 p-4 sm:p-6 rounded-lg shadow-xl mb-6">
<h2 class="text-xl sm:text-2xl font-semibold mb-4 text-sky-400">Configuration</h2>
<div class="flex flex-wrap gap-3 items-center">
<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">
Humain vs Humain
</button>
<div class="flex flex-col sm:flex-row gap-2">
<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">
Jouer vs IA (Blancs)
</button>
<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">
Jouer vs IA (Noirs)
</button>
</div>
<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">
Nouvelle Partie
</button>
</div>
<p class="mt-3 text-sm text-gray-400">Mode: <span id="currentMode" class="font-semibold text-gray-200">PVP</span></p>
<p id="playerColorInfo" class="mt-1 text-sm text-gray-400 hidden">
Vous jouez: <span id="currentPlayerColor" class="font-semibold text-gray-200">Blancs</span>
</p>
</div>
<div class="grid lg:grid-cols-3 gap-6">
<div class="lg:col-span-2 bg-gray-800 p-3 sm:p-4 rounded-lg shadow-xl">
<div id="chessBoard" class="chess-board-grid max-w-[600px] mx-auto border-4 border-gray-700 rounded-md overflow-hidden select-none">
<!-- Squares will be generated here by JS -->
</div>
<div class="mt-4 text-center text-xs sm:text-sm text-gray-400">
<p>Cliquez sur une pièce puis sur la destination, ou glissez-déposez.</p>
</div>
</div>
<div class="bg-gray-800 p-4 sm:p-6 rounded-lg shadow-xl lg:col-span-1">
<h3 class="text-lg sm:text-xl font-semibold mb-3 text-sky-400">Informations</h3>
<p id="turnDisplay" class="mb-2 text-sm">Tour: <span class="font-bold text-white">Blancs</span></p>
<p id="status" class="text-yellow-400 font-semibold mb-4 min-h-[1.5em] text-sm"></p>
<div id="outcomeDisplay" class="mb-4 text-lg font-bold text-center text-green-400"></div>
<div class="space-y-3 text-sm">
<div>
<p class="text-gray-400">Dernier coup:</p>
<p id="lastMove" class="text-gray-200 font-mono">-</p>
</div>
<div>
<p class="text-gray-400">Dernier coup IA:</p>
<p id="lastAIMove" class="text-gray-200 font-mono">-</p>
</div>
</div>
<div class="mt-6">
<h4 class="text-md sm:text-lg font-semibold mb-2 text-sky-400">Notation manuelle</h4>
<form id="moveForm" class="space-y-3">
<input type="text" id="moveInput"
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"
placeholder="ex: e2e4, Nf3">
<button type="submit"
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">
Jouer
</button>
</form>
</div>
</div>
</div>
<footer class="text-center text-gray-500 py-8 text-sm">
Jeu d'Échecs Tactile © 2024
</footer>
</div>
<script>
let gameMode = 'pvp';
let playerColor = 'white';
let isPlayerTurn = true;
let gameBoard = {};
let selectedSquare = null;
let currentPossibleMoves = []; // Renamed from possibleMoves to avoid conflict
let lastMoveSquares = [];
const chessBoardEl = document.getElementById('chessBoard');
const moveForm = document.getElementById('moveForm');
const moveInput = document.getElementById('moveInput');
const statusDisplay = document.getElementById('status');
const turnDisplayEl = document.getElementById('turnDisplay').querySelector('span'); // Corrected variable name
const outcomeDisplay = document.getElementById('outcomeDisplay');
const lastMoveDisplay = document.getElementById('lastMove');
const lastAIMoveDisplay = document.getElementById('lastAIMove');
const currentModeDisplay = document.getElementById('currentMode');
const playerColorInfoDisplay = document.getElementById('playerColorInfo');
const currentPlayerColorDisplay = document.getElementById('currentPlayerColor');
const pieceSymbols = {
'K': '♔', 'Q': '♕', 'R': '♖', 'B': '♗', 'N': '♘', 'P': '♙',
'k': '♚', 'q': '♛', 'r': '♜', 'b': '♝', 'n': '♞', 'p': '♟'
};
const initialFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
let currentFEN = initialFEN;
// Tailwind classes for square states
const selectedClasses = ['ring-4', 'ring-yellow-400', 'ring-inset', 'z-20'];
const possibleMoveClasses = ['ring-2', 'ring-emerald-500', 'ring-inset', 'possible-move-dot', 'text-emerald-500']; // Added text-emerald-500 for the dot
const lastMoveHighlightClasses = ['ring-2', 'ring-amber-500', 'ring-inset'];
function createChessBoard() {
chessBoardEl.innerHTML = '';
for (let rank = 8; rank >= 1; rank--) {
for (let file = 0; file < 8; file++) {
const fileChar = String.fromCharCode(97 + file);
const squareId = fileChar + rank;
const squareEl = document.createElement('div');
// Base classes for all squares
squareEl.className = `relative flex items-center justify-center cursor-pointer transition-all duration-150 ease-in-out
${(rank + file) % 2 === 0 ? 'bg-[#f0d9b5]' : 'bg-[#b58863]'}`; // Using specific colors
squareEl.dataset.square = squareId;
if (file === 0) {
const rankCoord = document.createElement('div');
rankCoord.className = 'absolute top-0.5 left-1 text-xs font-bold text-gray-700 opacity-60';
rankCoord.textContent = rank;
squareEl.appendChild(rankCoord);
}
if (rank === 1) {
const fileCoord = document.createElement('div');
fileCoord.className = 'absolute bottom-0.5 right-1 text-xs font-bold text-gray-700 opacity-60';
fileCoord.textContent = fileChar;
squareEl.appendChild(fileCoord);
}
squareEl.addEventListener('click', handleSquareClick);
squareEl.addEventListener('dragover', handleDragOver);
squareEl.addEventListener('drop', handleDrop);
chessBoardEl.appendChild(squareEl);
}
}
}
function updateBoardFromFEN(fen) {
const [boardPart] = fen.split(' ');
const ranks = boardPart.split('/');
gameBoard = {};
document.querySelectorAll('.chess-piece').forEach(piece => piece.remove());
for (let rankIdx = 0; rankIdx < 8; rankIdx++) {
const rank = 8 - rankIdx;
const rankStr = ranks[rankIdx];
let fileIdx = 0;
for (let char of rankStr) {
if (isNaN(parseInt(char))) {
const file = String.fromCharCode(97 + fileIdx);
const squareId = file + rank;
gameBoard[squareId] = char;
const squareEl = document.querySelector(`[data-square="${squareId}"]`);
if (squareEl) {
const pieceEl = document.createElement('div');
// Tailwind classes for pieces
pieceEl.className = `chess-piece-font text-3xl sm:text-4xl md:text-5xl
cursor-grab transition-transform duration-100 ease-in-out z-10
${char === char.toUpperCase() ? 'text-white' : 'text-black'}
hover:scale-110`;
// Note: Piece colors (text-white/text-black) are illustrative.
// You might want more distinct colors or to use the piece SVGs/images.
// For unicode, text-white/text-black on light/dark squares might be hard to see.
// Let's make white pieces one color, black pieces another, clearly distinct from squares
// For example: White pieces: text-neutral-100, Black pieces: text-neutral-800 (adjust based on square colors)
// Overriding with more visible defaults:
pieceEl.className = `chess-piece-font text-4xl sm:text-5xl
cursor-grab transition-transform duration-100 ease-in-out z-10
${char === char.toUpperCase() ? 'text-gray-100' : 'text-gray-900'}
hover:scale-110`;
pieceEl.textContent = pieceSymbols[char];
pieceEl.draggable = true;
pieceEl.dataset.piece = char;
pieceEl.dataset.square = squareId;
pieceEl.addEventListener('dragstart', handleDragStart);
pieceEl.addEventListener('dragend', handleDragEnd);
squareEl.appendChild(pieceEl);
}
fileIdx++;
} else {
fileIdx += parseInt(char);
}
}
}
currentFEN = fen;
updateTurnDisplay();
}
function updateTurnDisplay() {
const turn = currentFEN.split(' ')[1];
turnDisplayEl.textContent = turn === 'w' ? 'Blancs' : 'Noirs';
turnDisplayEl.className = `font-bold ${turn === 'w' ? 'text-white' : 'text-gray-300'}`; // Example dynamic color
if (gameMode === 'ai') {
const aiIsWhite = (playerColor === 'black');
const aiIsBlack = (playerColor === 'white');
if ((aiIsWhite && turn === 'w') || (aiIsBlack && turn === 'b')) {
isPlayerTurn = false;
statusDisplay.textContent = "L'IA réfléchit...";
} else {
isPlayerTurn = true;
statusDisplay.textContent = "À vous de jouer";
}
} else {
isPlayerTurn = true;
statusDisplay.textContent = ""; // Clear status for PvP or set as appropriate
}
// Ensure outcome display is cleared if game is not over
if (!currentFEN.includes(' # ')) { // Simple check, real game over check is in makeMove
outcomeDisplay.innerHTML = '';
}
}
function handleSquareClick(e) {
if (!isPlayerTurn) return;
const squareId = e.currentTarget.dataset.square;
const piece = gameBoard[squareId];
if (selectedSquare) {
if (selectedSquare === squareId) {
clearSelectionAndHighlights();
} else if (currentPossibleMoves.includes(squareId)) {
makeMove(selectedSquare + squareId);
} else if (piece && isPlayerPiece(piece)) {
selectSquare(squareId);
} else {
clearSelectionAndHighlights();
}
} else if (piece && isPlayerPiece(piece)) {
selectSquare(squareId);
}
}
function selectSquare(squareId) {
clearSelectionAndHighlights(); // Clear previous selection and its highlights
selectedSquare = squareId;
const squareEl = document.querySelector(`[data-square="${squareId}"]`);
if (squareEl) squareEl.classList.add(...selectedClasses);
showPossibleMovesForSquare(squareId); // Renamed for clarity
}
function clearSelectionAndHighlights() {
selectedSquare = null;
currentPossibleMoves = [];
document.querySelectorAll('.chess-board-grid > div').forEach(sq => {
sq.classList.remove(...selectedClasses, ...possibleMoveClasses);
});
}
function clearAllHighlights() {
clearSelectionAndHighlights();
document.querySelectorAll('.chess-board-grid > div').forEach(sq => {
sq.classList.remove(...lastMoveHighlightClasses);
});
}
function showPossibleMovesForSquare(fromSquareId) {
// Simulate getting possible moves (replace with actual logic/API call)
const piece = gameBoard[fromSquareId];
currentPossibleMoves = getSimulatedPossibleMoves(fromSquareId, piece); // Renamed
currentPossibleMoves.forEach(toSquareId => {
const squareEl = document.querySelector(`[data-square="${toSquareId}"]`);
if (squareEl) {
squareEl.classList.add(...possibleMoveClasses);
}
});
}
// SIMULATED: Replace with actual chess logic or server call
function getSimulatedPossibleMoves(square, piece) {
const moves = [];
if (!piece) return moves;
const file = square.charCodeAt(0) - 97; // 0-7
const rankVal = parseInt(square[1]); // 1-8
if (piece.toLowerCase() === 'p') {
const direction = piece === 'P' ? 1 : -1;
const nextRank = rankVal + direction;
if (nextRank >= 1 && nextRank <= 8) {
const forwardOne = String.fromCharCode(97 + file) + nextRank;
if (!gameBoard[forwardOne]) {
moves.push(forwardOne);
if ((piece === 'P' && rankVal === 2) || (piece === 'p' && rankVal === 7)) {
const forwardTwo = String.fromCharCode(97 + file) + (rankVal + 2 * direction);
if (!gameBoard[forwardTwo]) moves.push(forwardTwo);
}
}
// Capture diagonally (simplified)
if (file > 0) {
const captureLeft = String.fromCharCode(97 + file - 1) + nextRank;
if (gameBoard[captureLeft] && isOpponentPiece(piece, gameBoard[captureLeft])) moves.push(captureLeft);
}
if (file < 7) {
const captureRight = String.fromCharCode(97 + file + 1) + nextRank;
if (gameBoard[captureRight] && isOpponentPiece(piece, gameBoard[captureRight])) moves.push(captureRight);
}
}
}
// Add more piece logic here for simulation if needed
return moves;
}
function isOpponentPiece(myPiece, targetPiece) {
if (!targetPiece) return false;
const myPieceIsWhite = myPiece === myPiece.toUpperCase();
const targetPieceIsWhite = targetPiece === targetPiece.toUpperCase();
return myPieceIsWhite !== targetPieceIsWhite;
}
function isPlayerPiece(piece) {
if (!piece) return false;
const currentTurnColor = currentFEN.split(' ')[1]; // 'w' or 'b'
const pieceIsWhite = piece === piece.toUpperCase();
if (gameMode === 'pvp') {
return (currentTurnColor === 'w' && pieceIsWhite) || (currentTurnColor === 'b' && !pieceIsWhite);
}
// In AI mode, player can only move their own pieces on their turn
return (playerColor === 'white' && pieceIsWhite && currentTurnColor === 'w') ||
(playerColor === 'black' && !pieceIsWhite && currentTurnColor === 'b');
}
function handleDragStart(e) {
const piece = e.target.dataset.piece;
if (!isPlayerTurn || !isPlayerPiece(piece)) {
e.preventDefault();
return;
}
e.target.classList.add('cursor-grabbing', 'scale-125', 'opacity-75', 'z-50');
e.dataTransfer.setData('text/plain', e.target.dataset.square);
e.dataTransfer.effectAllowed = "move";
selectSquare(e.target.dataset.square); // Show possible moves
}
function handleDragEnd(e) {
e.target.classList.remove('cursor-grabbing', 'scale-125', 'opacity-75', 'z-50');
// No need to clear selection here, drop or click handles it
}
function handleDragOver(e) {
e.preventDefault();
e.dataTransfer.dropEffect = "move";
}
function handleDrop(e) {
e.preventDefault();
const fromSquareId = e.dataTransfer.getData('text/plain');
const toSquareEl = e.target.closest('[data-square]'); // Find the parent square if dropped on piece/coord
if (!toSquareEl) {
clearSelectionAndHighlights();
return;
}
const toSquareId = toSquareEl.dataset.square;
if (fromSquareId && toSquareId && fromSquareId !== toSquareId) {
if (currentPossibleMoves.includes(toSquareId)) {
makeMove(fromSquareId + toSquareId);
} else {
clearSelectionAndHighlights(); // Invalid drop target
}
} else {
clearSelectionAndHighlights(); // Dropped on same square or invalid
}
}
function highlightLastMoveUI(moveUci) { // Renamed to avoid conflict
// Clear previous last move highlights
document.querySelectorAll('.chess-board-grid > div').forEach(sq => {
sq.classList.remove(...lastMoveHighlightClasses);
});
if (moveUci && moveUci.length >= 4) {
const fromSquareId = moveUci.substring(0, 2);
const toSquareId = moveUci.substring(2, 4);
const fromEl = document.querySelector(`[data-square="${fromSquareId}"]`);
const toEl = document.querySelector(`[data-square="${toSquareId}"]`);
if (fromEl) fromEl.classList.add(...lastMoveHighlightClasses);
if (toEl) toEl.classList.add(...lastMoveHighlightClasses);
lastMoveSquares = [fromSquareId, toSquareId];
}
}
async function makeMove(moveStr) {
statusDisplay.textContent = 'Traitement...';
clearSelectionAndHighlights(); // Clear selection and possible moves highlights
try {
const response = await fetch('/make_move', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ move: moveStr })
});
const data = await response.json();
if (data.error) {
statusDisplay.textContent = `Erreur: ${data.error}`;
// Re-enable turn if it was disabled for AI thinking, or re-select piece
updateTurnDisplay();
} else {
currentFEN = data.fen; // Update FEN before updating board
updateBoardFromFEN(data.fen);
lastMoveDisplay.textContent = data.human_move_san || moveStr; // Use SAN if available
highlightLastMoveUI(moveStr); // Highlight human move
if (data.ai_move_uci) {
// Brief delay to make AI move feel more natural
setTimeout(() => {
currentFEN = data.fen; // FEN is already updated by AI on server, re-fetch or use this
updateBoardFromFEN(data.fen); // Update board again for AI move
lastAIMoveDisplay.textContent = data.ai_move_san || data.ai_move_uci;
highlightLastMoveUI(data.ai_move_uci); // Highlight AI move
updateTurnDisplay(); // Update turn after AI move visualised
if (data.game_over) {
handleGameOver(data.outcome);
}
}, gameMode === 'ai' && data.human_move_san ? 500 : 0); // Only delay if AI just played
} else {
updateTurnDisplay(); // Update turn if no AI move (PvP)
if (data.game_over) {
handleGameOver(data.outcome);
}
}
if (data.game_over && !data.ai_move_uci) { // Game over after human move (e.g. PvP checkmate)
handleGameOver(data.outcome);
}
}
} catch (error) {
console.error("Erreur lors de la communication:", error);
statusDisplay.textContent = "Erreur de communication avec le serveur.";
updateTurnDisplay(); // Reset turn status
}
}
function handleGameOver(outcome) {
statusDisplay.textContent = "Partie terminée!";
outcomeDisplay.innerHTML = `<p>${outcome}</p>`;
isPlayerTurn = false; // Disable further moves
}
moveForm.addEventListener('submit', async (e) => {
e.preventDefault();
const move = moveInput.value.trim();
if (!move || !isPlayerTurn) return;
await makeMove(move);
moveInput.value = '';
});
document.getElementById('resetGame').addEventListener('click', async () => {
statusDisplay.textContent = 'Réinitialisation...';
try {
const response = await fetch('/reset_game', { method: 'POST' });
const data = await response.json();
gameMode = data.game_mode || 'pvp';
playerColor = data.player_color || 'white';
currentFEN = data.fen;
updateBoardFromFEN(data.fen);
lastMoveDisplay.textContent = "-";
lastAIMoveDisplay.textContent = "-";
outcomeDisplay.innerHTML = '';
clearAllHighlights(); // Clears selection, possible moves, and last move highlights
currentModeDisplay.textContent = gameMode.toUpperCase();
if (gameMode === 'ai') {
currentPlayerColorDisplay.textContent = playerColor === 'white' ? 'Blancs' : 'Noirs';
playerColorInfoDisplay.classList.remove('hidden');
} else {
playerColorInfoDisplay.classList.add('hidden');
}
statusDisplay.textContent = "Nouvelle partie prête.";
updateTurnDisplay(); // This will set isPlayerTurn correctly
} catch (error) {
console.error("Erreur lors de la réinitialisation:", error);
statusDisplay.textContent = "Erreur lors de la réinitialisation.";
}
});
async function setGameMode(newMode, pColor = 'white') {
statusDisplay.textContent = `Changement de mode...`;
try {
const response = await fetch('/set_mode', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ game_mode: newMode, player_color: pColor })
});
const data = await response.json();
if (data.error) {
statusDisplay.textContent = `Erreur: ${data.error}`;
} else {
gameMode = data.game_mode;
playerColor = data.player_color;
currentFEN = data.fen;
updateBoardFromFEN(data.fen);
clearAllHighlights();
currentModeDisplay.textContent = gameMode.toUpperCase();
if (gameMode === 'ai') {
currentPlayerColorDisplay.textContent = playerColor === 'white' ? 'Blancs' : 'Noirs';
playerColorInfoDisplay.classList.remove('hidden');
} else {
playerColorInfoDisplay.classList.add('hidden');
}
statusDisplay.textContent = data.message || `Mode ${gameMode.toUpperCase()} activé.`;
lastMoveDisplay.textContent = "-";
lastAIMoveDisplay.textContent = "-";
outcomeDisplay.innerHTML = '';
if (data.initial_ai_move_uci) { // If AI plays first
setTimeout(() => {
currentFEN = data.fen; // FEN is already updated by AI on server
updateBoardFromFEN(data.fen);
lastAIMoveDisplay.textContent = data.initial_ai_move_san || data.initial_ai_move_uci;
highlightLastMoveUI(data.initial_ai_move_uci);
updateTurnDisplay();
}, 500);
} else {
updateTurnDisplay();
}
}
} catch (error) {
console.error("Erreur de changement de mode:", error);
statusDisplay.textContent = "Erreur de changement de mode.";
}
}
document.getElementById('setPvP').addEventListener('click', () => setGameMode('pvp'));
document.getElementById('setPvAIWhite').addEventListener('click', () => setGameMode('ai', 'white'));
document.getElementById('setPvAIBlack').addEventListener('click', () => setGameMode('ai', 'black'));
function initializeGame() {
createChessBoard();
// Fetch initial state from server to ensure consistency, or use defaults
// For now, using default FEN and assuming PvP mode initially
fetch('/reset_game', { method: 'POST' }) // Call reset on load to get initial state from server
.then(res => res.json())
.then(data => {
gameMode = data.game_mode || 'pvp';
playerColor = data.player_color || 'white';
currentFEN = data.fen || initialFEN;
updateBoardFromFEN(currentFEN);
currentModeDisplay.textContent = gameMode.toUpperCase();
if (gameMode === 'ai') {
currentPlayerColorDisplay.textContent = playerColor === 'white' ? 'Blancs' : 'Noirs';
playerColorInfoDisplay.classList.remove('hidden');
} else {
playerColorInfoDisplay.classList.add('hidden');
}
statusDisplay.textContent = "Prêt à jouer.";
updateTurnDisplay();
})
.catch(err => {
console.error("Failed to initialize game from server:", err);
// Fallback to local defaults if server init fails
updateBoardFromFEN(initialFEN);
currentModeDisplay.textContent = 'PVP';
playerColorInfoDisplay.classList.add('hidden');
statusDisplay.textContent = "Prêt à jouer (mode local).";
updateTurnDisplay();
});
}
initializeGame();
</script>
</body>
</html>