|
<!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 Flask</title> |
|
<script src="https://cdn.tailwindcss.com"></script> |
|
<style> |
|
|
|
.board-container svg { |
|
display: block; |
|
margin: 0 auto; |
|
max-width: 100%; |
|
height: auto; |
|
} |
|
|
|
.highlight-last-move rect.lastmove { |
|
fill: rgba(255, 255, 0, 0.4) !important; |
|
} |
|
</style> |
|
</head> |
|
<body class="bg-gray-900 text-gray-100 min-h-screen flex flex-col items-center p-4"> |
|
|
|
<div class="container mx-auto max-w-4xl"> |
|
<h1 class="text-4xl font-bold text-center my-6 text-teal-400">Jeu d'Échecs</h1> |
|
|
|
<div class="bg-gray-800 p-6 rounded-lg shadow-xl mb-6"> |
|
<h2 class="text-2xl font-semibold mb-3 text-sky-400">Configuration</h2> |
|
<div class="flex flex-wrap gap-4 items-center"> |
|
<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"> |
|
Humain vs Humain |
|
</button> |
|
<div> |
|
<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"> |
|
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 focus:outline-none focus:shadow-outline mt-2 sm:mt-0 sm:ml-2"> |
|
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 focus:outline-none focus:shadow-outline"> |
|
Nouvelle Partie (Reset) |
|
</button> |
|
</div> |
|
<p class="mt-3 text-sm text-gray-400">Mode Actuel: <span id="currentMode" class="font-semibold">{{ game_mode.upper() }}</span></p> |
|
<p id="playerColorInfo" class="mt-1 text-sm text-gray-400 {% if game_mode != 'ai' %}hidden{% endif %}"> |
|
Vous jouez avec les: <span id="currentPlayerColor" class="font-semibold">{{ player_color.capitalize() if player_color else 'N/A' }}</span> |
|
</p> |
|
</div> |
|
|
|
<div class="grid md:grid-cols-3 gap-6"> |
|
<div class="md:col-span-2 bg-gray-800 p-2 sm:p-6 rounded-lg shadow-xl board-container"> |
|
<div id="boardDisplay" class="highlight-last-move"> |
|
{{ initial_board_svg|safe }} |
|
</div> |
|
</div> |
|
|
|
<div class="bg-gray-800 p-6 rounded-lg shadow-xl"> |
|
<h3 class="text-xl font-semibold mb-3 text-sky-400">Informations</h3> |
|
<p id="turnDisplay" class="mb-2">Tour des: <span class="font-bold">Blancs</span></p> |
|
<p id="status" class="text-yellow-400 font-semibold mb-4 h-6"></p> |
|
|
|
<form id="moveForm" class="space-y-3"> |
|
<div> |
|
<label for="moveInput" class="block text-sm font-medium text-gray-300">Votre coup (ex: e2e4, Nf3):</label> |
|
<input type="text" id="moveInput" name="move" |
|
class="mt-1 block w-full bg-gray-700 border border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-sky-500 focus:border-sky-500 sm:text-sm text-white" |
|
placeholder="e.g., e2e4"> |
|
</div> |
|
<button type="submit" |
|
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"> |
|
Jouer |
|
</button> |
|
</form> |
|
|
|
<div id="outcomeDisplay" class="mt-4 text-lg font-bold text-center"> |
|
{% if is_game_over %} |
|
<p class="text-green-400">{{ outcome }}</p> |
|
{% endif %} |
|
</div> |
|
<div class="mt-4"> |
|
<p class="text-sm text-gray-400">Dernier coup IA:</p> |
|
<p id="lastAIMove" class="text-gray-200 font-mono">-</p> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<script> |
|
const boardDisplay = document.getElementById('boardDisplay'); |
|
const moveForm = document.getElementById('moveForm'); |
|
const moveInput = document.getElementById('moveInput'); |
|
const statusDisplay = document.getElementById('status'); |
|
const turnDisplay = document.getElementById('turnDisplay').querySelector('span'); |
|
const outcomeDisplay = document.getElementById('outcomeDisplay'); |
|
const lastAIMoveDisplay = document.getElementById('lastAIMove'); |
|
|
|
const currentModeDisplay = document.getElementById('currentMode'); |
|
const playerColorInfoDisplay = document.getElementById('playerColorInfo'); |
|
const currentPlayerColorDisplay = document.getElementById('currentPlayerColor'); |
|
|
|
let currentFEN = "{{ initial_fen }}"; |
|
let gameMode = "{{ game_mode }}"; |
|
let playerColor = "{{ player_color if player_color else 'white' }}"; |
|
let isPlayerTurn = true; |
|
|
|
function updateBoard(data) { |
|
if (data.board_svg) { |
|
boardDisplay.innerHTML = data.board_svg; |
|
} |
|
currentFEN = data.fen; |
|
|
|
if (data.turn) { |
|
turnDisplay.textContent = data.turn === 'white' ? 'Blancs' : 'Noirs'; |
|
} else { |
|
|
|
const fenTurn = data.fen.split(' ')[1]; |
|
turnDisplay.textContent = fenTurn === 'w' ? 'Blancs' : 'Noirs'; |
|
} |
|
|
|
if (data.ai_move_uci) { |
|
lastAIMoveDisplay.textContent = data.ai_move_uci; |
|
} else if (!data.game_over) { |
|
|
|
|
|
} |
|
|
|
|
|
if (data.game_over) { |
|
statusDisplay.textContent = "Partie terminée!"; |
|
outcomeDisplay.innerHTML = `<p class="text-green-400">${data.outcome}</p>`; |
|
moveInput.disabled = true; |
|
moveForm.querySelector('button[type="submit"]').disabled = true; |
|
} else { |
|
statusDisplay.textContent = ''; |
|
outcomeDisplay.innerHTML = ''; |
|
moveInput.disabled = false; |
|
moveForm.querySelector('button[type="submit"]').disabled = false; |
|
} |
|
|
|
|
|
updatePlayerTurnState(); |
|
} |
|
|
|
function updatePlayerTurnState() { |
|
const fenTurn = currentFEN.split(' ')[1]; |
|
if (gameMode === 'ai') { |
|
const aiIsWhite = (playerColor === 'black'); |
|
const aiIsBlack = (playerColor === 'white'); |
|
|
|
if ((aiIsWhite && fenTurn === 'w') || (aiIsBlack && fenTurn === 'b')) { |
|
isPlayerTurn = false; |
|
moveInput.disabled = true; |
|
moveForm.querySelector('button[type="submit"]').disabled = true; |
|
statusDisplay.textContent = "L'IA réfléchit..."; |
|
} else { |
|
isPlayerTurn = true; |
|
if (!currentFEN.split(' ')[0].includes('k') || !currentFEN.split(' ')[0].includes('K')) { |
|
moveInput.disabled = false; |
|
moveForm.querySelector('button[type="submit"]').disabled = false; |
|
} |
|
statusDisplay.textContent = "À vous de jouer."; |
|
} |
|
} else { |
|
isPlayerTurn = true; |
|
moveInput.disabled = false; |
|
moveForm.querySelector('button[type="submit"]').disabled = false; |
|
} |
|
} |
|
|
|
|
|
moveForm.addEventListener('submit', async (e) => { |
|
e.preventDefault(); |
|
const move = moveInput.value.trim(); |
|
if (!move) return; |
|
|
|
statusDisplay.textContent = 'Traitement...'; |
|
lastAIMoveDisplay.textContent = "-"; |
|
|
|
try { |
|
const response = await fetch('/make_move', { |
|
method: 'POST', |
|
headers: { 'Content-Type': 'application/json' }, |
|
body: JSON.stringify({ move: move }) |
|
}); |
|
const data = await response.json(); |
|
|
|
if (data.error) { |
|
statusDisplay.textContent = `Erreur: ${data.error}`; |
|
} else { |
|
updateBoard(data); |
|
moveInput.value = ''; |
|
} |
|
} catch (error) { |
|
console.error("Erreur lors de la communication:", error); |
|
statusDisplay.textContent = "Erreur de communication avec le serveur."; |
|
} |
|
}); |
|
|
|
document.getElementById('resetGame').addEventListener('click', async () => { |
|
statusDisplay.textContent = 'Réinitialisation...'; |
|
try { |
|
const response = await fetch('/reset_game', { method: 'POST' }); |
|
const data = await response.json(); |
|
updateBoard(data); |
|
lastAIMoveDisplay.textContent = "-"; |
|
|
|
moveInput.disabled = false; |
|
moveForm.querySelector('button[type="submit"]').disabled = false; |
|
outcomeDisplay.innerHTML = ''; |
|
|
|
currentModeDisplay.textContent = data.game_mode ? data.game_mode.toUpperCase() : 'PVP'; |
|
if (data.game_mode === 'ai' && data.player_color) { |
|
currentPlayerColorDisplay.textContent = data.player_color.charAt(0).toUpperCase() + data.player_color.slice(1); |
|
playerColorInfoDisplay.classList.remove('hidden'); |
|
} else { |
|
playerColorInfoDisplay.classList.add('hidden'); |
|
} |
|
|
|
updatePlayerTurnState(); |
|
|
|
} catch (error) { |
|
console.error("Erreur lors de la réinitialisation:", error); |
|
statusDisplay.textContent = "Erreur lors de la réinitialisation."; |
|
} |
|
}); |
|
|
|
async function setGameMode(mode, pColor = 'white') { |
|
statusDisplay.textContent = `Changement de mode vers ${mode.toUpperCase()}...`; |
|
lastAIMoveDisplay.textContent = "-"; |
|
try { |
|
const response = await fetch('/set_mode', { |
|
method: 'POST', |
|
headers: { 'Content-Type': 'application/json' }, |
|
body: JSON.stringify({ game_mode: mode, 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; |
|
updateBoard(data); |
|
currentModeDisplay.textContent = gameMode.toUpperCase(); |
|
if (gameMode === 'ai') { |
|
currentPlayerColorDisplay.textContent = playerColor.charAt(0).toUpperCase() + playerColor.slice(1); |
|
playerColorInfoDisplay.classList.remove('hidden'); |
|
} else { |
|
playerColorInfoDisplay.classList.add('hidden'); |
|
} |
|
statusDisplay.textContent = data.message; |
|
if (data.initial_ai_move_uci) { |
|
lastAIMoveDisplay.textContent = data.initial_ai_move_uci; |
|
} |
|
} |
|
} 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')); |
|
|
|
|
|
updatePlayerTurnState(); |
|
|
|
if (gameMode === 'ai') { |
|
currentPlayerColorDisplay.textContent = playerColor.charAt(0).toUpperCase() + playerColor.slice(1); |
|
playerColorInfoDisplay.classList.remove('hidden'); |
|
} |
|
|
|
</script> |
|
</body> |
|
</html> |