|
<!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> |
|
<script src="https://cdn.tailwindcss.com"></script> |
|
<style> |
|
.chess-board { |
|
display: grid; |
|
grid-template-columns: repeat(8, 1fr); |
|
grid-template-rows: repeat(8, 1fr); |
|
aspect-ratio: 1; |
|
max-width: 600px; |
|
margin: 0 auto; |
|
border: 4px solid #8B4513; |
|
} |
|
|
|
.chess-square { |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
font-size: 2.5rem; |
|
cursor: pointer; |
|
transition: all 0.2s; |
|
user-select: none; |
|
} |
|
|
|
.chess-square.light { |
|
background-color: #F0D9B5; |
|
} |
|
|
|
.chess-square.dark { |
|
background-color: #B58863; |
|
} |
|
|
|
.chess-square.selected { |
|
background-color: #7fc97f !important; |
|
box-shadow: inset 0 0 0 3px #4CAF50; |
|
} |
|
|
|
.chess-square.legal-move { |
|
background-color: #90EE90 !important; |
|
} |
|
|
|
.chess-square:hover { |
|
opacity: 0.8; |
|
} |
|
|
|
.piece-white { color: white; text-shadow: 1px 1px 1px black; } |
|
.piece-black { color: black; text-shadow: 1px 1px 1px white; } |
|
|
|
.status-bar { |
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|
} |
|
</style> |
|
</head> |
|
<body class="bg-gray-100 min-h-screen"> |
|
<div class="container mx-auto px-4 py-8"> |
|
|
|
<div class="text-center mb-8"> |
|
<h1 class="text-4xl font-bold text-gray-800 mb-4">♔ Jeu d'Échecs ♛</h1> |
|
<div class="status-bar text-white px-6 py-4 rounded-lg shadow-lg"> |
|
<div id="game-status" class="text-xl font-semibold"> |
|
Choisissez un mode de jeu pour commencer |
|
</div> |
|
<div id="current-player" class="text-sm mt-1 opacity-90"></div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="flex flex-wrap justify-center gap-4 mb-8"> |
|
<button id="btn-human-mode" class="px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-semibold transition-colors shadow-lg"> |
|
🤝 Mode Humain vs Humain |
|
</button> |
|
<button id="btn-ai-mode" class="px-6 py-3 bg-purple-600 hover:bg-purple-700 text-white rounded-lg font-semibold transition-colors shadow-lg"> |
|
🤖 Mode vs IA |
|
</button> |
|
<button id="btn-reset" class="px-6 py-3 bg-green-600 hover:bg-green-700 text-white rounded-lg font-semibold transition-colors shadow-lg"> |
|
🔄 Nouvelle Partie |
|
</button> |
|
</div> |
|
|
|
|
|
<div class="flex justify-center mb-8"> |
|
<div class="bg-white p-6 rounded-xl shadow-2xl"> |
|
<div id="chess-board" class="chess-board"> |
|
|
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="grid md:grid-cols-2 gap-6 max-w-4xl mx-auto"> |
|
|
|
<div class="bg-white rounded-lg shadow-lg p-6"> |
|
<h3 class="text-xl font-bold text-gray-800 mb-4">📝 Historique des coups</h3> |
|
<div id="moves-history" class="max-h-60 overflow-y-auto text-sm"> |
|
<p class="text-gray-500 italic">Aucun coup joué</p> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="bg-white rounded-lg shadow-lg p-6"> |
|
<h3 class="text-xl font-bold text-gray-800 mb-4">ℹ️ Instructions</h3> |
|
<ul class="text-sm text-gray-600 space-y-2"> |
|
<li><strong>Mode Humain:</strong> Deux joueurs alternent sur le même appareil</li> |
|
<li><strong>Mode IA:</strong> Jouez contre l'intelligence artificielle</li> |
|
<li><strong>Comment jouer:</strong> Cliquez sur une pièce puis sur la case de destination</li> |
|
<li><strong>Cases vertes:</strong> Coups légaux possibles</li> |
|
</ul> |
|
<div class="mt-4 text-xs text-gray-500"> |
|
<p>🔴 Cases rouges = sélection | 🟢 Cases vertes = coups possibles</p> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<script> |
|
class ChessGame { |
|
constructor() { |
|
this.gameState = null; |
|
this.selectedSquare = null; |
|
this.legalMoves = []; |
|
this.initializeBoard(); |
|
this.setupEventListeners(); |
|
} |
|
|
|
initializeBoard() { |
|
const board = document.getElementById('chess-board'); |
|
board.innerHTML = ''; |
|
|
|
for (let row = 0; row < 8; row++) { |
|
for (let col = 0; col < 8; col++) { |
|
const square = document.createElement('div'); |
|
square.className = `chess-square ${(row + col) % 2 === 0 ? 'light' : 'dark'}`; |
|
square.dataset.square = this.getSquareName(row, col); |
|
square.addEventListener('click', (e) => this.handleSquareClick(e)); |
|
board.appendChild(square); |
|
} |
|
} |
|
} |
|
|
|
getSquareName(row, col) { |
|
const files = 'abcdefgh'; |
|
const ranks = '87654321'; |
|
return files[col] + ranks[row]; |
|
} |
|
|
|
getSquareFromName(squareName) { |
|
const files = 'abcdefgh'; |
|
const ranks = '87654321'; |
|
const col = files.indexOf(squareName[0]); |
|
const row = ranks.indexOf(squareName[1]); |
|
return { row, col }; |
|
} |
|
|
|
pieceToUnicode(piece) { |
|
const pieces = { |
|
'K': '♔', 'Q': '♕', 'R': '♖', 'B': '♗', 'N': '♘', 'P': '♙', |
|
'k': '♚', 'q': '♛', 'r': '♜', 'b': '♝', 'n': '♞', 'p': '♟' |
|
}; |
|
return pieces[piece] || ''; |
|
} |
|
|
|
updateBoard() { |
|
if (!this.gameState) return; |
|
|
|
|
|
const fenParts = this.gameState.fen.split(' '); |
|
const position = fenParts[0]; |
|
const rows = position.split('/'); |
|
|
|
const squares = document.querySelectorAll('.chess-square'); |
|
squares.forEach(square => { |
|
square.textContent = ''; |
|
square.className = square.className.replace(/piece-(white|black)/, ''); |
|
}); |
|
|
|
rows.forEach((row, rowIndex) => { |
|
let colIndex = 0; |
|
for (let char of row) { |
|
if (char >= '1' && char <= '8') { |
|
colIndex += parseInt(char); |
|
} else { |
|
const squareName = this.getSquareName(rowIndex, colIndex); |
|
const square = document.querySelector(`[data-square="${squareName}"]`); |
|
if (square) { |
|
square.textContent = this.pieceToUnicode(char); |
|
square.classList.add(char === char.toUpperCase() ? 'piece-white' : 'piece-black'); |
|
} |
|
colIndex++; |
|
} |
|
} |
|
}); |
|
} |
|
|
|
updateGameStatus() { |
|
const statusEl = document.getElementById('game-status'); |
|
const playerEl = document.getElementById('current-player'); |
|
|
|
if (!this.gameState) { |
|
statusEl.textContent = 'Choisissez un mode de jeu pour commencer'; |
|
playerEl.textContent = ''; |
|
return; |
|
} |
|
|
|
if (this.gameState.game_over) { |
|
if (this.gameState.winner === 'draw') { |
|
statusEl.textContent = '🤝 Match nul !'; |
|
} else { |
|
const winner = this.gameState.winner === 'white' ? 'Blancs' : 'Noirs'; |
|
statusEl.textContent = `🏆 ${winner} gagnent !`; |
|
} |
|
playerEl.textContent = 'Partie terminée'; |
|
} else { |
|
const currentPlayer = this.gameState.current_player === 'white' ? 'Blancs' : 'Noirs'; |
|
const modeText = this.gameState.mode === 'ai' ? 'vs IA' : 'Humain vs Humain'; |
|
statusEl.textContent = `🎮 ${modeText}`; |
|
playerEl.textContent = `Au tour des ${currentPlayer}`; |
|
} |
|
} |
|
|
|
updateMovesHistory() { |
|
const historyEl = document.getElementById('moves-history'); |
|
|
|
if (!this.gameState || this.gameState.moves_history.length === 0) { |
|
historyEl.innerHTML = '<p class="text-gray-500 italic">Aucun coup joué</p>'; |
|
return; |
|
} |
|
|
|
let historyHtml = '<div class="grid grid-cols-2 gap-2 text-sm">'; |
|
this.gameState.moves_history.forEach((move, index) => { |
|
const moveNumber = Math.floor(index / 2) + 1; |
|
const isWhite = index % 2 === 0; |
|
|
|
if (isWhite) { |
|
historyHtml += `<div class="font-semibold">${moveNumber}. ${move}</div>`; |
|
} else { |
|
historyHtml += `<div class="pl-4">${move}</div>`; |
|
} |
|
}); |
|
historyHtml += '</div>'; |
|
historyEl.innerHTML = historyHtml; |
|
|
|
|
|
historyEl.scrollTop = historyEl.scrollHeight; |
|
} |
|
|
|
async handleSquareClick(event) { |
|
const square = event.target; |
|
const squareName = square.dataset.square; |
|
|
|
if (!this.gameState || this.gameState.game_over) return; |
|
|
|
|
|
if (!this.selectedSquare) { |
|
if (square.textContent && this.canSelectPiece(square)) { |
|
this.selectSquare(square); |
|
} |
|
return; |
|
} |
|
|
|
|
|
if (this.selectedSquare === square) { |
|
this.deselectSquare(); |
|
return; |
|
} |
|
|
|
|
|
if (square.textContent && this.canSelectPiece(square)) { |
|
this.selectSquare(square); |
|
return; |
|
} |
|
|
|
|
|
const fromSquare = this.selectedSquare.dataset.square; |
|
const toSquare = squareName; |
|
const move = fromSquare + toSquare; |
|
|
|
await this.makeMove(move); |
|
} |
|
|
|
canSelectPiece(square) { |
|
if (!square.textContent) return false; |
|
|
|
const isWhitePiece = square.classList.contains('piece-white'); |
|
const isCurrentPlayerWhite = this.gameState.current_player === 'white'; |
|
|
|
return isWhitePiece === isCurrentPlayerWhite; |
|
} |
|
|
|
selectSquare(square) { |
|
this.deselectSquare(); |
|
this.selectedSquare = square; |
|
square.classList.add('selected'); |
|
this.highlightLegalMoves(square.dataset.square); |
|
} |
|
|
|
deselectSquare() { |
|
if (this.selectedSquare) { |
|
this.selectedSquare.classList.remove('selected'); |
|
this.selectedSquare = null; |
|
} |
|
this.clearHighlights(); |
|
} |
|
|
|
highlightLegalMoves(fromSquare) { |
|
this.clearHighlights(); |
|
|
|
if (!this.gameState.legal_moves) return; |
|
|
|
const movesFromSquare = this.gameState.legal_moves.filter(move => |
|
move.startsWith(fromSquare) |
|
); |
|
|
|
movesFromSquare.forEach(move => { |
|
const toSquare = move.substring(2, 4); |
|
const square = document.querySelector(`[data-square="${toSquare}"]`); |
|
if (square) { |
|
square.classList.add('legal-move'); |
|
} |
|
}); |
|
} |
|
|
|
clearHighlights() { |
|
document.querySelectorAll('.legal-move').forEach(square => { |
|
square.classList.remove('legal-move'); |
|
}); |
|
} |
|
|
|
async makeMove(move) { |
|
try { |
|
const response = await fetch('/api/make_move', { |
|
method: 'POST', |
|
headers: { |
|
'Content-Type': 'application/json' |
|
}, |
|
body: JSON.stringify({ move }) |
|
}); |
|
|
|
const data = await response.json(); |
|
|
|
if (data.success) { |
|
this.gameState = data.game_state; |
|
this.updateDisplay(); |
|
this.deselectSquare(); |
|
|
|
|
|
if (data.ai_move) { |
|
setTimeout(() => { |
|
console.log('IA a joué:', data.ai_move); |
|
}, 500); |
|
} |
|
} else { |
|
alert('Coup invalide: ' + data.error); |
|
} |
|
} catch (error) { |
|
console.error('Erreur lors du coup:', error); |
|
alert('Erreur de connexion'); |
|
} |
|
} |
|
|
|
async startNewGame(mode) { |
|
try { |
|
const response = await fetch('/api/new_game', { |
|
method: 'POST', |
|
headers: { |
|
'Content-Type': 'application/json' |
|
}, |
|
body: JSON.stringify({ mode }) |
|
}); |
|
|
|
const data = await response.json(); |
|
|
|
if (data.success) { |
|
this.gameState = data.game_state; |
|
this.updateDisplay(); |
|
this.deselectSquare(); |
|
} else { |
|
alert('Erreur lors de la création de la partie'); |
|
} |
|
} catch (error) { |
|
console.error('Erreur:', error); |
|
alert('Erreur de connexion'); |
|
} |
|
} |
|
|
|
async resetGame() { |
|
if (!this.gameState) return; |
|
|
|
try { |
|
const response = await fetch('/api/reset_game', { |
|
method: 'POST', |
|
headers: { |
|
'Content-Type': 'application/json' |
|
} |
|
}); |
|
|
|
const data = await response.json(); |
|
|
|
if (data.success) { |
|
this.gameState = data.game_state; |
|
this.updateDisplay(); |
|
this.deselectSquare(); |
|
} |
|
} catch (error) { |
|
console.error('Erreur:', error); |
|
alert('Erreur de connexion'); |
|
} |
|
} |
|
|
|
updateDisplay() { |
|
this.updateBoard(); |
|
this.updateGameStatus(); |
|
this.updateMovesHistory(); |
|
} |
|
|
|
setupEventListeners() { |
|
document.getElementById('btn-human-mode').addEventListener('click', () => { |
|
this.startNewGame('human'); |
|
}); |
|
|
|
document.getElementById('btn-ai-mode').addEventListener('click', () => { |
|
this.startNewGame('ai'); |
|
}); |
|
|
|
document.getElementById('btn-reset').addEventListener('click', () => { |
|
if (confirm('Êtes-vous sûr de vouloir recommencer la partie ?')) { |
|
this.resetGame(); |
|
} |
|
}); |
|
} |
|
} |
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => { |
|
new ChessGame(); |
|
}); |
|
</script> |
|
</body> |
|
</html> |