darkmaria / templates /index.html
Docfile's picture
Update templates/index.html
8dc0e70 verified
raw
history blame
16.1 kB
<!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</title>
<!-- Utilisation du CDN de Tailwind CSS pour la simplicité -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css" rel="stylesheet">
<style>
/* Styles additionnels si besoin */
.piece-font { /* Optionnel: si vous voulez une police spécifique pour les pièces unicode */
font-family: 'Arial Unicode MS', 'DejaVu Sans', 'Symbola', sans-serif;
}
.selected-square {
background-color: #48bb78 !important; /* Vert pour la sélection */
outline: 2px solid #2f855a;
}
.possible-move {
background-color: rgba(72, 187, 120, 0.3) !important; /* Vert clair pour les coups possibles */
}
.last-move-highlight {
background-color: rgba(251, 191, 36, 0.4) !important; /* Jaune pour le dernier coup */
}
#chessboard {
display: grid;
grid-template-columns: repeat(8, minmax(0, 1fr));
grid-template-rows: repeat(8, minmax(0, 1fr));
width: 400px; /* Ajustez selon vos besoins */
height: 400px; /* Ajustez selon vos besoins */
border: 2px solid #4A5568; /* gray-700 */
}
.square {
display: flex;
align-items: center;
justify-content: center;
font-size: 2.25rem; /* text-4xl */
cursor: pointer;
user-select: none; /* Empêcher la sélection de texte des pièces */
}
.light-square {
background-color: #F7FAFC; /* gray-100 */
}
.dark-square {
background-color: #A0AEC0; /* gray-500 */
}
</style>
</head>
<body class="bg-gray-200 flex flex-col items-center justify-center min-h-screen p-4">
<div class="bg-white p-6 rounded-lg shadow-xl w-full max-w-lg">
<h1 class="text-3xl font-bold text-center text-gray-800 mb-6">Jeu d'Échecs</h1>
<div class="mb-4 flex justify-around">
<button id="new-game-human" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">
Humain vs Humain
</button>
<button id="new-game-ai" class="bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">
Jouer vs IA
</button>
</div>
<div class="flex flex-col items-center">
<div id="chessboard" class="mb-4">
<!-- Les cases de l'échiquier seront générées ici par JavaScript -->
</div>
<div id="turn-indicator" class="text-xl font-semibold text-gray-700 mb-2">
Tour: Blancs
</div>
<div id="status-message" class="text-md text-gray-600 mb-4 h-6">
<!-- Les messages de statut apparaîtront ici -->
</div>
<div id="evaluation-indicator" class="text-sm text-gray-500 mb-4 h-5">
<!-- L'évaluation de Stockfish (optionnel) -->
</div>
</div>
<div class="mt-4 p-4 bg-gray-100 rounded max-h-48 overflow-y-auto">
<h3 class="font-semibold text-gray-700 mb-2">Historique des coups:</h3>
<div id="history-log" class="text-sm text-gray-600">
<!-- L'historique des coups sera affiché ici -->
</div>
</div>
</div>
<script>
const boardElement = document.getElementById('chessboard');
const turnIndicatorElement = document.getElementById('turn-indicator');
const statusMessageElement = document.getElementById('status-message');
const evaluationIndicatorElement = document.getElementById('evaluation-indicator'); // Pour l'évaluation
const newGameAiButton = document.getElementById('new-game-ai');
const newGameHumanButton = document.getElementById('new-game-human');
const historyLogElement = document.getElementById('history-log');
let selectedSquareId = null;
let currentFen = '';
let currentPlayerTurn = 'w'; // 'w' for white, 'b' for black
let gameMode = 'human'; // 'human' or 'ai'
let lastMoveSquares = []; // Pour surligner le dernier coup [from, to]
const pieceUnicode = {
'p': '♟', 'r': '♜', 'n': '♞', 'b': '♝', 'q': '♛', 'k': '♚',
'P': '♙', 'R': '♖', 'N': '♘', 'B': '♗', 'Q': '♕', 'K': '♔'
};
const pieceColor = {
'p': 'b', 'r': 'b', 'n': 'b', 'b': 'b', 'q': 'b', 'k': 'b',
'P': 'w', 'R': 'w', 'N': 'w', 'B': 'w', 'Q': 'w', 'K': 'w'
};
function getSquareId(rankIndex, fileIndex) {
return `${String.fromCharCode(97 + fileIndex)}${8 - rankIndex}`;
}
function clearHighlights() {
document.querySelectorAll('.selected-square').forEach(el => el.classList.remove('selected-square'));
document.querySelectorAll('.possible-move').forEach(el => el.classList.remove('possible-move'));
// Ne pas enlever last-move-highlight ici, on le gère au moment de renderBoard
}
function renderBoard(fen, lastHumanMove = null, lastAiMove = null) {
currentFen = fen;
boardElement.innerHTML = ''; // Clear previous board
const fenParts = fen.split(' ');
const boardState = fenParts[0];
currentPlayerTurn = fenParts[1]; // 'w' or 'b'
// Gestion du surlignage du dernier coup
document.querySelectorAll('.last-move-highlight').forEach(el => el.classList.remove('last-move-highlight'));
lastMoveSquares = [];
if (lastAiMove) { // Si l'IA a joué, son coup est le plus récent
lastMoveSquares = [lastAiMove.substring(0,2), lastAiMove.substring(2,4)];
} else if (lastHumanMove) { // Sinon, c'est le coup humain
lastMoveSquares = [lastHumanMove.substring(0,2), lastHumanMove.substring(2,4)];
}
const ranks = boardState.split('/');
for (let rankIndex = 0; rankIndex < 8; rankIndex++) {
const rank = ranks[rankIndex];
let fileIndex = 0;
for (const char of rank) {
if (isNaN(parseInt(char))) { // It's a piece
const squareId = getSquareId(rankIndex, fileIndex);
const squareElement = createSquareElement(squareId, rankIndex, fileIndex, pieceUnicode[char]);
boardElement.appendChild(squareElement);
fileIndex++;
} else { // It's a number for empty squares
for (let i = 0; i < parseInt(char); i++) {
const squareId = getSquareId(rankIndex, fileIndex);
const squareElement = createSquareElement(squareId, rankIndex, fileIndex, '');
boardElement.appendChild(squareElement);
fileIndex++;
}
}
}
}
updateTurnIndicator();
}
function createSquareElement(id, rankIndex, fileIndex, pieceSymbol) {
const squareElement = document.createElement('div');
squareElement.id = id;
squareElement.classList.add('square', 'piece-font');
squareElement.classList.add((rankIndex + fileIndex) % 2 === 0 ? 'light-square' : 'dark-square');
squareElement.textContent = pieceSymbol;
squareElement.dataset.piece = pieceSymbol; // Stocker la pièce pour une vérification facile
if (lastMoveSquares.includes(id)) {
squareElement.classList.add('last-move-highlight');
}
squareElement.addEventListener('click', handleSquareClick);
return squareElement;
}
function updateTurnIndicator() {
turnIndicatorElement.textContent = `Tour: ${currentPlayerTurn === 'w' ? 'Blancs' : 'Noirs'}`;
}
function updateHistoryLog(historyArray) {
historyLogElement.innerHTML = '';
if (!historyArray || historyArray.length === 0) {
historyLogElement.textContent = "Aucun coup joué.";
return;
}
let moveNumber = 1;
for (let i = 0; i < historyArray.length; i += 2) {
const whiteMove = historyArray[i];
const blackMove = historyArray[i+1] ? historyArray[i+1] : '';
const logEntry = document.createElement('div');
logEntry.textContent = `${moveNumber}. ${whiteMove} ${blackMove}`;
historyLogElement.appendChild(logEntry);
moveNumber++;
}
historyLogElement.scrollTop = historyLogElement.scrollHeight; // Scroll to bottom
}
async function handleSquareClick(event) {
const clickedSquareElement = event.currentTarget; // L'élément div de la case
const clickedSquareId = clickedSquareElement.id;
// Si la partie est terminée, ne rien faire
if (statusMessageElement.textContent.includes("Mat!") || statusMessageElement.textContent.includes("Pat!")) {
return;
}
const pieceSymbol = clickedSquareElement.dataset.piece;
const isPieceOnSquare = pieceSymbol && pieceSymbol.trim() !== '';
if (!selectedSquareId) { // Premier clic: sélection d'une pièce
if (isPieceOnSquare) {
// Vérifier si c'est la pièce du joueur actuel
const colorOfPiece = pieceColor[pieceSymbol];
if (colorOfPiece === currentPlayerTurn) {
selectedSquareId = clickedSquareId;
clickedSquareElement.classList.add('selected-square');
// TODO: Optionnel - appeler le backend pour obtenir les coups légaux et les surligner
}
}
} else { // Deuxième clic: tentative de déplacement
const previousSelectedElement = document.getElementById(selectedSquareId);
previousSelectedElement?.classList.remove('selected-square');
if (selectedSquareId === clickedSquareId) { // Clic sur la même case pour désélectionner
selectedSquareId = null;
return;
}
const moveUci = `${selectedSquareId}${clickedSquareId}`;
// Vérification si c'est une promotion de pion (simplifié, suppose promotion en reine)
const fromSquare = document.getElementById(selectedSquareId);
const fromPiece = fromSquare?.dataset.piece;
const toRank = clickedSquareId.charAt(1);
let finalMoveUci = moveUci;
if (fromPiece === 'P' && toRank === '8') {
finalMoveUci += 'q'; // Promotion en Reine pour les blancs
} else if (fromPiece === 'p' && toRank === '1') {
finalMoveUci += 'q'; // Promotion en Reine pour les noirs
}
selectedSquareId = null; // Réinitialiser la sélection après la tentative de coup
statusMessageElement.textContent = 'Traitement du coup...';
try {
const response = await fetch('/move', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ move: finalMoveUci })
});
const data = await response.json();
if (data.error) {
statusMessageElement.textContent = `Erreur: ${data.error}`;
// Re-render old fen and turn if move was illegal
if(data.fen) renderBoard(data.fen, data.last_move_human, data.last_move_ai); // S'assurer que le dernier coup est passé
if(data.turn) currentPlayerTurn = data.turn;
updateTurnIndicator();
} else {
renderBoard(data.fen, data.last_move_human, data.last_move_ai);
currentPlayerTurn = data.turn; // Le backend doit retourner le tour actuel
statusMessageElement.textContent = data.game_status || (data.last_move_ai ? `L'IA a joué: ${data.last_move_ai}` : "Coup joué.");
updateHistoryLog(data.history);
if (data.evaluation) { // Afficher l'évaluation si fournie
evaluationIndicatorElement.textContent = `Éval: ${data.evaluation.type} ${data.evaluation.value}`;
} else {
evaluationIndicatorElement.textContent = '';
}
if (data.game_status && (data.game_status.includes("Mat!") || data.game_status.includes("Pat!"))) {
// La partie est terminée, on pourrait désactiver les clics ou afficher un message plus grand
turnIndicatorElement.textContent = "Partie terminée";
} else if (gameMode === 'ai' && currentPlayerTurn !== 'w') { // Si c'est le tour de l'IA (assumant que l'IA joue noir pour l'instant)
// Le backend gère déjà le coup de l'IA dans la même réponse /move
}
}
} catch (error) {
console.error('Erreur lors de l\'envoi du coup:', error);
statusMessageElement.textContent = 'Erreur de communication avec le serveur.';
}
}
}
async function startNewGame(mode) {
gameMode = mode;
selectedSquareId = null;
statusMessageElement.textContent = `Démarrage d'une nouvelle partie (${mode === 'ai' ? 'vs IA' : 'Humain vs Humain'})...`;
evaluationIndicatorElement.textContent = '';
historyLogElement.innerHTML = "Aucun coup joué.";
lastMoveSquares = []; // Réinitialiser le surlignage du dernier coup
try {
const response = await fetch('/new_game', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ mode: gameMode })
});
const data = await response.json();
if (data.error) {
statusMessageElement.textContent = `Erreur: ${data.error}`;
} else {
renderBoard(data.fen);
currentPlayerTurn = data.turn;
statusMessageElement.textContent = data.message || 'Partie commencée. Aux Blancs de jouer.';
updateTurnIndicator();
updateHistoryLog([]); // Réinitialise l'historique
}
} catch (error) {
console.error('Erreur lors du démarrage de la nouvelle partie:', error);
statusMessageElement.textContent = 'Erreur de communication avec le serveur.';
}
}
newGameAiButton.addEventListener('click', () => startNewGame('ai'));
newGameHumanButton.addEventListener('click', () => startNewGame('human'));
// Initialisation de la première partie au chargement de la page
document.addEventListener('DOMContentLoaded', () => {
startNewGame('human'); // Ou 'ai' si vous préférez
});
</script>
</body>
</html>