|
<!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> |
|
|
|
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css" rel="stylesheet"> |
|
<style> |
|
|
|
.piece-font { |
|
font-family: 'Arial Unicode MS', 'DejaVu Sans', 'Symbola', sans-serif; |
|
} |
|
.selected-square { |
|
background-color: #48bb78 !important; |
|
outline: 2px solid #2f855a; |
|
} |
|
.possible-move { |
|
background-color: rgba(72, 187, 120, 0.3) !important; |
|
} |
|
.last-move-highlight { |
|
background-color: rgba(251, 191, 36, 0.4) !important; |
|
} |
|
#chessboard { |
|
display: grid; |
|
grid-template-columns: repeat(8, minmax(0, 1fr)); |
|
grid-template-rows: repeat(8, minmax(0, 1fr)); |
|
width: 400px; |
|
height: 400px; |
|
border: 2px solid #4A5568; |
|
} |
|
.square { |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
font-size: 2.25rem; |
|
cursor: pointer; |
|
user-select: none; |
|
} |
|
.light-square { |
|
background-color: #F7FAFC; |
|
} |
|
.dark-square { |
|
background-color: #A0AEC0; |
|
} |
|
</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"> |
|
|
|
</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"> |
|
|
|
</div> |
|
<div id="evaluation-indicator" class="text-sm text-gray-500 mb-4 h-5"> |
|
|
|
</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"> |
|
|
|
</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'); |
|
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'; |
|
let gameMode = 'human'; |
|
let lastMoveSquares = []; |
|
|
|
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')); |
|
|
|
} |
|
|
|
function renderBoard(fen, lastHumanMove = null, lastAiMove = null) { |
|
currentFen = fen; |
|
boardElement.innerHTML = ''; |
|
const fenParts = fen.split(' '); |
|
const boardState = fenParts[0]; |
|
currentPlayerTurn = fenParts[1]; |
|
|
|
|
|
document.querySelectorAll('.last-move-highlight').forEach(el => el.classList.remove('last-move-highlight')); |
|
lastMoveSquares = []; |
|
if (lastAiMove) { |
|
lastMoveSquares = [lastAiMove.substring(0,2), lastAiMove.substring(2,4)]; |
|
} else if (lastHumanMove) { |
|
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))) { |
|
const squareId = getSquareId(rankIndex, fileIndex); |
|
const squareElement = createSquareElement(squareId, rankIndex, fileIndex, pieceUnicode[char]); |
|
boardElement.appendChild(squareElement); |
|
fileIndex++; |
|
} else { |
|
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; |
|
|
|
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; |
|
} |
|
|
|
async function handleSquareClick(event) { |
|
const clickedSquareElement = event.currentTarget; |
|
const clickedSquareId = clickedSquareElement.id; |
|
|
|
|
|
if (statusMessageElement.textContent.includes("Mat!") || statusMessageElement.textContent.includes("Pat!")) { |
|
return; |
|
} |
|
|
|
const pieceSymbol = clickedSquareElement.dataset.piece; |
|
const isPieceOnSquare = pieceSymbol && pieceSymbol.trim() !== ''; |
|
|
|
if (!selectedSquareId) { |
|
if (isPieceOnSquare) { |
|
|
|
const colorOfPiece = pieceColor[pieceSymbol]; |
|
if (colorOfPiece === currentPlayerTurn) { |
|
selectedSquareId = clickedSquareId; |
|
clickedSquareElement.classList.add('selected-square'); |
|
|
|
} |
|
} |
|
} else { |
|
const previousSelectedElement = document.getElementById(selectedSquareId); |
|
previousSelectedElement?.classList.remove('selected-square'); |
|
|
|
if (selectedSquareId === clickedSquareId) { |
|
selectedSquareId = null; |
|
return; |
|
} |
|
|
|
const moveUci = `${selectedSquareId}${clickedSquareId}`; |
|
|
|
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'; |
|
} else if (fromPiece === 'p' && toRank === '1') { |
|
finalMoveUci += 'q'; |
|
} |
|
|
|
selectedSquareId = null; |
|
|
|
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}`; |
|
|
|
if(data.fen) renderBoard(data.fen, data.last_move_human, data.last_move_ai); |
|
if(data.turn) currentPlayerTurn = data.turn; |
|
updateTurnIndicator(); |
|
} else { |
|
renderBoard(data.fen, data.last_move_human, data.last_move_ai); |
|
currentPlayerTurn = data.turn; |
|
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) { |
|
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!"))) { |
|
|
|
turnIndicatorElement.textContent = "Partie terminée"; |
|
} else if (gameMode === 'ai' && currentPlayerTurn !== 'w') { |
|
|
|
} |
|
} |
|
} 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 = []; |
|
|
|
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([]); |
|
} |
|
} 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')); |
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => { |
|
startNewGame('human'); |
|
}); |
|
|
|
</script> |
|
</body> |
|
</html> |