Riichi/frontend/pages/riichi/chap5.html
2026-02-26 19:03:05 +01:00

303 lines
No EOL
8.8 KiB
HTML

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Tuto Riichi</title>
<link rel="stylesheet" href="/frontend/css/style.css">
<link rel="stylesheet" href="/frontend/css/text.css">
<link rel="stylesheet" href="/frontend/css/speech-bubble.css">
<link rel="stylesheet" href="/frontend/css/hand.css">
<style>
#menu {
position: relative;
z-index: 10;
}
#mascotte {
position: absolute;
top: 670px;
right: 100px;
z-index: 999;
transform: scaleX(-1);
cursor: pointer;
}
#mascotte img {
width: 250px;
height: auto;
display: block;
}
.speech-bubble {
position: absolute;
top: 250px;
right: 170px;
z-index: 1;
display: none;
}
.speech-bubble.visible {
display: flex;
}
#tiles-container {
display: flex;
justify-content: center;
align-items: flex-start;
gap: 10px;
padding: 20px;
}
:root {
--game_radius: 1100px;
}
#game-center {
position: absolute;
top: 0px;
left: 350px;
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
}
#game {
position: relatives;
width: var(--game_radius);
height: var(--game_radius);
}
#hand-0 {
position: absolute;
transform: translate(-50%, 50%);
bottom: -10%;
left: 50%;
display: flex;
flex-direction: row;
align-items: flex-end;
gap: 5px;
padding: 20px 0 0 20px;
}
#hand-1 {
position: absolute;
transform: translate(-50%, -400%) rotate(-90deg) translate(0%, 450%);
bottom: -10%;
left: 50%;
display: flex;
flex-direction: row;
align-items: flex-end;
gap: 5px;
padding: 20px 0 0 20px;
}
#hand-2 {
position: absolute;
transform: translate(-50%, -400%) rotate(180deg) translate(0%, 450%);
bottom: -10%;
left: 50%;
display: flex;
flex-direction: row;
align-items: flex-end;
gap: 5px;
padding: 20px 0 0 20px;
}
#hand-3 {
position: absolute;
transform: translate(-50%, -400%) rotate(90deg) translate(0%, 450%);
bottom: -10%;
left: 50%;
display: flex;
flex-direction: row;
align-items: flex-end;
gap: 5px;
padding: 20px 0 0 20px;
}
#hand-0 div:hover {
transform: translateY(-15px);
transition: transform 0.2s;
z-index: 2;
}
</style>
</head>
<body>
<div id="menu"></div>
<h1>Chapitre 5</h1>
<div class="speech-bubble">
<div class="speech-text" id="speech-text"></div>
</div>
<div id="mascotte"></div>
<div id="game-center">
<div id="game">
<div id="hand-0"></div>
<div id="hand-1"></div>
<div id="hand-2"></div>
<div id="hand-3"></div>
</div>
</div>
<script src="/frontend/js/fr/texts.js"></script>
<script>
let isDrawing = false;
// Fonction pour mettre à jour l'état du jeu
function updateGameState() {
fetch('/api/game/state')
.then(r => r.json())
.then(data => {
if (data.state && Array.isArray(data.state.hand_player)) {
window.lastDrawTile = data.state.draw ? data.state.draw.svg : null;
displayHand(data.state.hand_player, 'hand-0');
}
[1, 2, 3].forEach(id => {
fetch(`/api/game/hand/${id}`)
.then(r => r.json())
.then(botData => {
if (botData.hand) {
displayHand(botData.hand, `hand-${id}`);
}
});
});
});
}
setInterval(updateGameState, 500);
// Charger le menu
fetch('/components/menu.html')
.then(r => r.text())
.then(html => document.getElementById('menu').innerHTML = html);
// Afficher la mascotte et le texte
const mascotteEl = document.getElementById('mascotte');
mascotteEl.innerHTML = '<img src="/img/tilineau/idle.png" alt="Tilineau">';
document.getElementById('speech-text').innerHTML = texts.riichi.chap5;
// Toggle bulle de dialogue au clic sur la mascotte
const speechBubble = document.querySelector('.speech-bubble');
mascotteEl.addEventListener('click', () => {
speechBubble.classList.toggle('visible');
mascotteEl.innerHTML = speechBubble.classList.contains('visible')
? '<img class="explaining" src="/img/tilineau/explaining.png" alt="Tilineau">'
: '<img src="/img/tilineau/idle.png" alt="Tilineau">';
});
// Fonction d'affichage d'une main
function displayHand(hand, displayId) {
const display = document.getElementById(displayId);
// Pour hand-0, on compare l'état actuel
if (displayId === 'hand-0') {
// Génère une signature simple de la main
const handSignature = hand.map(tile => tile.id).join(',') + (window.lastDrawTile || '');
if (display.dataset.signature === handSignature) {
// Rien n'a changé, on ne réécrit pas
return;
}
display.dataset.signature = handSignature;
}
display.innerHTML = '';
hand.forEach(tile => {
const tileDiv = document.createElement('div');
// Affiche le dos des tuiles pour les bots
if (displayId !== 'hand-0') {
tileDiv.innerHTML = '<svg viewBox="0 0 310 410" width="60" height="80"><image href="/img/Regular/Back.svg" width="310" height="410"/></svg>';
} else {
tileDiv.innerHTML = tile.svg;
tileDiv.style.cursor = 'pointer';
tileDiv.addEventListener('click', () => discard_tile(tile.id));
}
display.appendChild(tileDiv);
});
// Affiche la tuile draw à la fin si elle existe (pour hand-0)
if (displayId === 'hand-0' && window.lastDrawTile) {
const drawDiv = document.createElement('div');
drawDiv.id = 'draw-tile';
drawDiv.style.marginLeft = '10px';
drawDiv.innerHTML = window.lastDrawTile;
display.appendChild(drawDiv);
}
}
function discard_tile(tileId) {
console.log('[JS] discard_tile', tileId);
fetch('/api/discard_tile', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ tile_id: tileId })
})
.then(r => r.json())
.then(data => {
console.log('[JS] discard response', data);
if (data.state && Array.isArray(data.state.hand_player)) {
window.lastDrawTile = data.state.draw ? data.state.draw.svg : null;
displayHand(data.state.hand_player, 'hand-0');
}
// Affiche la défausse du joueur
if (data.state) {
fetch('/api/game/discards/0')
.then(r => r.json())
.then(discardsData => {
displayDiscards(discardsData.discards, 'discards-0');
});
}
});
}
function displayDiscards(discards, displayId) {
const display = document.getElementById(displayId);
display.innerHTML = '';
discards.forEach(tile => {
const tileDiv = document.createElement('div');
tileDiv.innerHTML = tile.svg;
display.appendChild(tileDiv);
});
}
// Polling pour récupérer la main d'un bot
function fetchBotHandWithRetry(id, tries = 8, delay = 500) {
fetch(`/api/game/hand/${id}`)
.then(r => r.json())
.then(botData => {
if (botData.hand) {
displayHand(botData.hand, `hand-${id}`);
} else if (tries > 0) {
setTimeout(() => fetchBotHandWithRetry(id, tries - 1, delay), delay);
} else {
console.error(`Main vide pour joueur ${id}`);
}
})
.catch(e => {
if (tries > 0) {
setTimeout(() => fetchBotHandWithRetry(id, tries - 1, delay), delay);
} else {
console.error(`Erreur main ${id}`, e);
}
});
}
// Initialiser une partie au chargement du chapitre 5
fetch('/api/game/start', { method: 'POST' })
.then(r => r.json())
.then(data => {
console.log('Réponse API', data);
// Si has_draw est False, on appelle play_draw et on attend le reload
if (data.state && data.state.has_draw === false) {
fetch('/api/play_draw', { method: 'POST' })
.then(() => {
});
return;
}
// Afficher la main du joueur
if (data.state && data.state.hand_player) {
displayHand(data.state.hand_player, 'hand-0');
// Affichage des mains bots (1, 2, 3) avec polling
[1, 2, 3].forEach(id => {
fetchBotHandWithRetry(id);
});
}
});
</script>
</body>
</html>