Riichi/frontend/pages/riichi/chap5.html
2026-03-25 10:31:07 +01:00

1184 lines
No EOL
38 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: 101;
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: -100px;
left: 350px;
display: none;
align-items: center;
justify-content: center;
min-height: 100vh;
}
#start-screen {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100vw;
height: 100vh;
background: transparent;
z-index: 100;
}
#start-screen h2 {
color: white;
font-size: 3em;
margin-bottom: 20px;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
}
#start-screen p {
color: white;
font-size: 1.2em;
margin-bottom: 40px;
text-align: center;
max-width: 500px;
}
#start-button {
padding: 15px 50px;
font-size: 1.3em;
background-color: #FFD700;
color: #333;
border: none;
border-radius: 50px;
cursor: pointer;
font-weight: bold;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
transition: all 0.3s ease;
}
#start-button:hover {
background-color: #FFA500;
transform: scale(1.05);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3);
}
#start-button:active {
transform: scale(0.98);
}
#game {
position: relative;
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-tiles {
display: flex;
flex-direction: row;
gap: 5px;
}
.hand-with-draw {
display: flex;
flex-direction: row;
align-items: flex-end;
}
.draw-tile {
margin-left: 12px;
position: relative;
top: 0;
display: inline-block;
}
.hand-groups {
display: block;
position: absolute;
pointer-events: none;
height: 50px;
}
.hand-group {
display: flex;
flex-direction: row;
gap: 2px;
align-items: center;
position: absolute;
top: 0;
margin-right: 20px;
z-index: 10;
}
.hand-group:nth-child(1) { z-index: 11; }
.hand-group:nth-child(2) { z-index: 10; }
.hand-group:nth-child(3) { z-index: 9; }
.hand-group:nth-child(4) { z-index: 8; }
.hand-group:nth-child(5) { z-index: 7; }
.hand-group svg {
height: 40px;
width: 30px;
}
.stolen-tile {
display: flex;
align-items: center;
justify-content: center;
width: 30px;
margin: 0;
flex-shrink: 0;
}
.stolen-tile svg {
height: 40px;
width: 30px;
transform: rotate(90deg);
}
/* Augmenter l'écart quand la tuile penchée est à une extrémité */
.hand-group.stolen-left .stolen-tile {
margin-right: 5px;
}
.hand-group.stolen-right .stolen-tile {
margin-left: 5px;
}
/* Gap différent si la tuile volée est au centre */
.hand-group.stolen-middle {
gap: 8px;
}
#groups-1 .stolen-tile svg {
transform: rotate(90deg);
}
#groups-3 .stolen-tile svg {
transform: rotate(-90deg);
}
#groups-0 {
bottom: -7%;
right: 10%;
transform: translateX(50%);
}
#groups-1 {
top: 28%;
right: 12%;
transform: translateX(50%) translateY(-50%) rotate(90deg) scaleX(-1) rotateZ(180deg);
transform-origin: center;
}
#groups-2 {
top: 27%;
left: 11%;
transform: translateX(-50%) rotate(180deg);
}
#groups-3 {
top: 106%;
right: 88%;
transform: translateX(50%) translateY(-50%) rotate(-90deg) scaleX(-1) rotateZ(180deg);
transform-origin: center;
}
#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;
}
.player-tile:hover {
transform: translateY(-15px);
transition: transform 0.2s;
z-index: 2;
}
#game-info {
position: absolute;
top: 65.5%;
left: 49%;
transform: translate(-50%, -50%);
width: 259px;
height: 259px;
background-color: #f5f5f0;
border-radius: 20px;
padding: 20px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
z-index: 1;
}
#discard-0 {
position: absolute;
transform: translate(-50%, 50%);
bottom: 16%;
left: 48%;
display: block;
width: 250px;
height: auto;
padding: 10px;
}
#discard-1 {
position: absolute;
transform: translate(-50%, -400%) rotate(-90deg) translate(0%, 450%);
bottom: 25%;
left: 58%;
display: block;
width: 250px;
height: auto;
padding: 10px;
}
#discard-2 {
position: absolute;
transform: translate(-50%, -400%) rotate(180deg) translate(0%, 450%);
bottom: 35%;
left: 50.5%;
display: block;
width: 250px;
height: auto;
padding: 10px;
}
#discard-3 {
position: absolute;
transform: translate(-50%, -400%) rotate(90deg) translate(0%, 450%);
bottom: 27.5%;
left: 40%;
display: block;
width: 250px;
height: auto;
padding: 10px;
}
#discard-0 div,
#discard-1 div,
#discard-2 div,
#discard-3 div {
flex-shrink: 0;
}
.discard-tile svg {
height: 65px;
width: 48px;
}
.turn-indicator {
position: absolute;
display: none;
height: 6px;
background: linear-gradient(90deg, #FFD700, #FFA500);
border-radius: 3px;
box-shadow: 0 0 10px rgba(255, 215, 0, 0.6), inset 0 1px 2px rgba(255, 255, 255, 0.3);
}
#turn-indicator-0 {
transform: translate(-50%, 50%);
bottom: 20%;
left: 49%;
width: 250px;
}
#turn-indicator-1 {
transform: translate(-50%, -400%) rotate(-90deg) translate(0%, 450%);
bottom: 32.5%;
left: 61%;
width: 250px;
}
#turn-indicator-2 {
transform: translate(-50%, -400%) rotate(180deg) translate(0%, 450%);
bottom: 44%;
left: 49%;
width: 250px;
}
#turn-indicator-3 {
transform: translate(-50%, -400%) rotate(90deg) translate(0%, 450%);
bottom: 32%;
left: 37%;
width: 250px;
}
#call-panel {
position: absolute;
transform: translate(-50%, 50%);
bottom: -3%;
left: 78%;
display: none;
gap: 10px;
z-index: 100;
white-space: nowrap;
}
.call-button {
padding: 16px 32px;
font-size: 1.3em;
font-weight: bold;
border: none;
border-radius: 8px;
cursor: pointer;
color: white;
transition: all 0.3s ease;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
.call-button:hover {
transform: scale(1.05);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3);
}
.call-button:active {
transform: scale(0.95);
}
/* Code couleur pour les 6 appels */
#pon-button {
background-color: #FF6B6B; /* Rouge */
}
#chii-button {
background-color: #4ECDC4; /* Turquoise */
}
#kan-button {
background-color: #95E1D3; /* Menthe */
}
#tsumo-button {
background-color: #FFD700; /* Or */
color: #333;
}
#ron-button {
background-color: #9B59B6; /* Violet */
}
#skip-button {
background-color: #808080; /* Gris */
}
@keyframes ponAnimation {
0% {
opacity: 0;
transform: translateX(30px) scale(0.3) rotate(-15deg);
}
10% {
opacity: 1;
transform: translateX(0) scale(1.2) rotate(0deg);
}
20% {
opacity: 1;
transform: translateX(0) scale(1) rotate(0deg);
}
70% {
opacity: 1;
transform: translateX(0) scale(1) rotate(0deg);
}
85% {
opacity: 1;
transform: translateX(0) scale(1) rotate(0deg);
}
100% {
opacity: 0;
transform: translateX(-60px) scale(0.5) rotate(20deg);
}
}
.pon-text {
position: fixed;
font-size: 3.5em;
font-weight: 900;
color: #CC3333;
z-index: 999;
animation: ponAnimation 1.2s cubic-bezier(0.68, -0.55, 0.27, 1.55) forwards;
pointer-events: none;
text-shadow: 0 0 15px rgba(204, 51, 51, 0.7), 0 0 30px rgba(204, 51, 51, 0.3);
transform: translate(-50%, -50%);
white-space: nowrap;
}
</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="start-screen">
<h2>Riichi Mahjong</h2>
<p>Entraînez-vous à maîtriser les appels en jouant une partie contre trois bots</p>
<button id="start-button">Lancer la partie</button>
</div>
<div id="game-center">
<div id="game">
<div id="game-info"></div>
<div id="hand-0"></div>
<div id="discard-0"></div>
<div id="turn-indicator-0" class="turn-indicator"></div>
<div id="hand-1"></div>
<div id="discard-1"></div>
<div id="turn-indicator-1" class="turn-indicator"></div>
<div id="hand-2"></div>
<div id="discard-2"></div>
<div id="turn-indicator-2" class="turn-indicator"></div>
<div id="hand-3"></div>
<div id="discard-3"></div>
<div id="turn-indicator-3" class="turn-indicator"></div>
<div id="call-panel">
<button id="pon-button" class="call-button">Pon</button>
<button id="chii-button" class="call-button" style="display: none;">Chii</button>
<button id="kan-button" class="call-button" style="display: none;">Kan</button>
<button id="tsumo-button" class="call-button" style="display: none;">Tsumo</button>
<button id="ron-button" class="call-button" style="display: none;">Ron</button>
<button id="skip-button" class="call-button">Skip</button>
</div>
</div>
</div>
<script src="/frontend/js/fr/texts.js"></script>
<script src="/frontend/js/tile.js"></script>
<script type="module">
import { Game } from '/frontend/js/game.js';
window.addEventListener('DOMContentLoaded', () => {
// Gestion du bouton de démarrage
const startButton = document.getElementById('start-button');
const startScreen = document.getElementById('start-screen');
const gameCenter = document.getElementById('game-center');
startButton.addEventListener('click', () => {
startScreen.style.display = 'none';
gameCenter.style.display = 'flex';
// Instancier une nouvelle partie avec mains vides pour distribution animée
game = new Game(true);
// Lancer l'animation de distribution
distributeHands();
});
// Déclarer les variables et fonctions ici pour qu'elles soient accessibles partout
let game;
let isDistributing = false;
let canPlayerAct = false;
let lastDiscarder = -1;
// 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">';
});
// Instancier une nouvelle partie et afficher les mains
async function distributeHands() {
isDistributing = true;
// Distribution 4 par 4, puis 1 par 1 comme dans la vraie vie
// 3 rounds de 4 tuiles par joueur = 12 tuiles
for (let round = 0; round < 3; round++) {
for (let player = 0; player < 4; player++) {
for (let i = 0; i < 4; i++) {
game.hands[player].tiles.push(game.wall.drawTile());
}
renderHands();
await new Promise(resolve => setTimeout(resolve, 300));
}
}
// Distribution finale : 1 par 1 pour la 13e tuile
for (let player = 0; player < 4; player++) {
game.hands[player].tiles.push(game.wall.drawTile());
renderHands();
await new Promise(resolve => setTimeout(resolve, 300));
}
// Trier les mains avec animation pour le joueur
const playerHandContainer = document.getElementById('hand-0').querySelector('.hand-tiles');
const playerTiles = game.hands[0].tiles;
const playerTileDivs = Array.from(playerHandContainer.children);
// Créer un map pour associer chaque tuile à son élément DOM
const tileToElement = new Map();
playerTileDivs.forEach((div, index) => {
tileToElement.set(playerTiles[index], div);
});
// Enregistrer les positions avant tri (en pixels relatifs au conteneur)
const originalPositions = new Map();
playerTileDivs.forEach((div, index) => {
originalPositions.set(div, index * (60 + 5)); // calcul basé sur width + gap
});
// Trier les mains
for (let hand of game.hands) {
hand.sort();
}
// Récupérer les nouvelles positions
const newPositions = new Map();
game.hands[0].tiles.forEach((tile, newIndex) => {
newPositions.set(tileToElement.get(tile), newIndex * (60 + 5));
});
// Appliquer une translation pour que les éléments restent à leur place visuelle
playerTileDivs.forEach(div => {
const originalX = originalPositions.get(div);
const newX = newPositions.get(div);
const deltaX = originalX - newX;
div.style.transform = `translateX(${deltaX}px)`;
div.style.transition = 'none';
});
// Forcer un reflow
void playerHandContainer.offsetWidth;
// Ré-ordonner les éléments dans le DOM selon le tri
const sortedDivs = game.hands[0].tiles.map(tile => tileToElement.get(tile));
sortedDivs.forEach(div => playerHandContainer.appendChild(div));
// Forcer un autre reflow
void playerHandContainer.offsetWidth;
// Animer vers les positions finales
playerTileDivs.forEach(div => {
div.style.transition = 'transform 0.6s ease-in-out';
div.style.transform = 'translateX(0)';
});
// Attendre la fin de l'animation
await new Promise(resolve => setTimeout(resolve, 600));
// Nettoyer les styles
playerTileDivs.forEach(div => {
div.style.transition = '';
div.style.transform = '';
});
// Re-rendre les bots sans animation
for (let i = 1; i < 4; i++) {
const botHandDiv = document.getElementById(`hand-${i}`);
const botHandTiles = document.createElement('div');
botHandTiles.className = 'hand-tiles';
for (let k = 0; k < game.hands[i].tiles.length; k++) {
const tileDiv = document.createElement('div');
tileDiv.innerHTML = `<svg class="tile tile-back" viewBox="0 0 310 410" height="82" width="60">
<image href="/img/Regular/Gray.svg" x="10" y="10" height="410" width="310"/>
<image href="/img/Regular/Back.svg" x="0" y="0" height="410" width="310"/>
</svg>`;
botHandTiles.appendChild(tileDiv);
}
const botHandWithDraw = botHandDiv.querySelector('.hand-with-draw') || botHandDiv;
const oldHandTiles = botHandWithDraw.querySelector('.hand-tiles');
if (oldHandTiles) oldHandTiles.remove();
botHandWithDraw.insertBefore(botHandTiles, botHandWithDraw.firstChild);
}
// Le joueur 0 pioche sa tuile supplémentaire
await new Promise(resolve => setTimeout(resolve, 500));
game.draw(game.turn);
renderHands();
updateTurnIndicator();
isDistributing = false;
canPlayerAct = true;
}
function renderHands() {
for (let i = 0; i < 4; i++) {
const handDiv = document.getElementById(`hand-${i}`);
handDiv.innerHTML = '';
// Conteneur main + draw
const handWithDraw = document.createElement('div');
handWithDraw.className = 'hand-with-draw';
// Sous-div pour les tuiles de la main
const handTiles = document.createElement('div');
handTiles.className = 'hand-tiles';
for (let k = 0; k < game.hands[i].tiles.length; k++) {
const tile = game.hands[i].tiles[k];
const tileDiv = document.createElement('div');
// Ajouter la classe 'player-tile' uniquement pour le joueur
if (i === 0) {
tileDiv.className = 'player-tile';
tileDiv.innerHTML = tile.getSVG();
tileDiv.style.cursor = 'pointer';
const handleClick = (() => {
const tileToDiscard = tile;
return () => {
// Ne rien faire pendant la distribution ou si ce n'est pas son tour
if (isDistributing || !canPlayerAct) return;
// Trouver l'index actuel de la tuile
const currentIdx = game.hands[0].tiles.findIndex(t => t === tileToDiscard);
if (currentIdx !== -1) {
canPlayerAct = false;
game.discard(0, currentIdx);
game.nextTurn();
updateTurnIndicator();
renderHands();
renderDiscards();
playBotsSequentially();
}
};
})();
tileDiv.addEventListener('click', handleClick);
} else {
// Bots : afficher le dos
tileDiv.innerHTML = `<svg class="tile tile-back" viewBox="0 0 310 410" height="82" width="60">
<image href="/img/Regular/Gray.svg" x="10" y="10" height="410" width="310"/>
<image href="/img/Regular/Back.svg" x="0" y="0" height="410" width="310"/>
</svg>`;
}
handTiles.appendChild(tileDiv);
}
handWithDraw.appendChild(handTiles);
// Afficher la tuile draw si elle existe
if (game.hands[i].drawn) {
const drawDiv = document.createElement('div');
drawDiv.className = 'draw-tile';
if (i === 0) {
drawDiv.classList.add('player-tile');
drawDiv.innerHTML = game.hands[i].drawn.getSVG();
drawDiv.style.cursor = 'pointer';
const drawnTile = game.hands[i].drawn;
const handleDrawnClick = (() => {
return () => {
// Ne rien faire si ce n'est pas son tour ou pendant la distribution
if (isDistributing || !canPlayerAct) return;
// Défausser la tuile piochée (index === tiles.length)
canPlayerAct = false;
lastDiscarder = 0;
game.discard(0, game.hands[0].tiles.length);
game.nextTurn();
updateTurnIndicator();
renderHands();
renderDiscards();
playBotsSequentially();
};
})();
drawDiv.addEventListener('click', handleDrawnClick);
} else {
drawDiv.innerHTML = `<svg class="tile tile-back" viewBox="0 0 310 410" height="82" width="60">
<image href="/img/Regular/Gray.svg" x="10" y="10" height="410" width="310"/>
<image href="/img/Regular/Back.svg" x="0" y="0" height="410" width="310"/>
</svg>`;
}
handWithDraw.appendChild(drawDiv);
}
handDiv.appendChild(handWithDraw);
}
renderGroups();
}
function renderGroups() {
const gameDiv = document.getElementById('game');
// Créer les conteneurs de groupes pour chaque joueur s'ils n'existent pas
for (let i = 0; i < 4; i++) {
if (!document.getElementById(`groups-${i}`)) {
const groupsContainer = document.createElement('div');
groupsContainer.id = `groups-${i}`;
groupsContainer.className = 'hand-groups';
gameDiv.appendChild(groupsContainer);
}
}
// Mettre à jour les groupes pour chaque joueur
for (let i = 0; i < 4; i++) {
const groupsContainer = document.getElementById(`groups-${i}`);
const currentGroupDivs = Array.from(groupsContainer.children);
// Ajouter les nouveaux groupes
for (let groupIndex = game.hands[i].groups.length - 1; groupIndex >= 0; groupIndex--) {
if (groupIndex >= currentGroupDivs.length) {
// Nouveau groupe à ajouter
const group = game.hands[i].groups[groupIndex];
const groupDiv = createGroupDiv(group);
// Ajouter au début du conteneur (à gauche)
groupsContainer.insertBefore(groupDiv, groupsContainer.firstChild);
}
}
// Recalculer toutes les positions
const allGroupDivs = Array.from(groupsContainer.children);
if (i === 0 || i === 2) {
// Joueurs 0 et 2 : ancrage sur right, positions inversées
let rightOffset = 0;
const groupsToPosition = allGroupDivs.slice().reverse();
for (let j = 0; j < groupsToPosition.length; j++) {
const groupDiv = groupsToPosition[j];
const groupWidth = (groupDiv.children.length) * 30;
groupDiv.style.right = rightOffset + 'px';
groupDiv.style.left = 'auto';
rightOffset += groupWidth + 12; // +12 pour le gap entre les groupes
}
} else {
// Bots 1 et 3 : ancrage sur left, ordre inversé
let offset = 0;
const groupsToPosition = allGroupDivs.slice().reverse();
for (let j = 0; j < groupsToPosition.length; j++) {
const groupDiv = groupsToPosition[j];
const groupWidth = (groupDiv.children.length) * 30;
groupDiv.style.left = offset + 'px';
groupDiv.style.right = 'auto';
offset += groupWidth + 12; // +12 pour le gap entre les groupes
}
}
}
}
function createGroupDiv(group) {
const groupDiv = document.createElement('div');
groupDiv.className = 'hand-group';
// Pour un Pon, placer la tuile volée selon la direction (dir: 1=gauche, 2=milieu, 3=droite)
if (group.stollenTile && group.dir) {
if (group.dir === 1) {
// Tuile volée à gauche
groupDiv.classList.add('stolen-left');
const stolenDiv = document.createElement('div');
stolenDiv.innerHTML = group.stollenTile.getSVG();
stolenDiv.className = 'stolen-tile';
groupDiv.appendChild(stolenDiv);
for (let j = 0; j < group.tiles.length; j++) {
const tileDiv = document.createElement('div');
tileDiv.innerHTML = group.tiles[j].getSVG();
// Ajouter la classe middle-tile à la tuile du milieu
if (j === Math.floor(group.tiles.length / 2)) {
tileDiv.classList.add('middle-tile');
}
groupDiv.appendChild(tileDiv);
}
} else if (group.dir === 2) {
// Tuile volée au milieu
groupDiv.classList.add('stolen-middle');
for (let j = 0; j < group.tiles.length; j++) {
if (j === Math.floor(group.tiles.length / 2)) {
const stolenDiv = document.createElement('div');
stolenDiv.innerHTML = group.stollenTile.getSVG();
stolenDiv.className = 'stolen-tile stolen-middle';
groupDiv.appendChild(stolenDiv);
}
const tileDiv = document.createElement('div');
tileDiv.innerHTML = group.tiles[j].getSVG();
groupDiv.appendChild(tileDiv);
}
} else if (group.dir === 3) {
// Tuile volée à droite
groupDiv.classList.add('stolen-right');
for (let j = 0; j < group.tiles.length; j++) {
const tileDiv = document.createElement('div');
tileDiv.innerHTML = group.tiles[j].getSVG();
// Ajouter la classe middle-tile à la tuile du milieu
if (j === Math.floor(group.tiles.length / 2)) {
tileDiv.classList.add('middle-tile');
}
groupDiv.appendChild(tileDiv);
}
const stolenDiv = document.createElement('div');
stolenDiv.innerHTML = group.stollenTile.getSVG();
stolenDiv.className = 'stolen-tile';
groupDiv.appendChild(stolenDiv);
}
} else {
// Groupe fermé ou non volé : afficher les tuiles normalement
for (let tile of group.tiles) {
const tileDiv = document.createElement('div');
tileDiv.innerHTML = tile.getSVG();
groupDiv.appendChild(tileDiv);
}
}
return groupDiv;
}
function renderAll() {
renderHands();
renderGroups();
renderDiscards();
}
function renderDiscards() {
for (let i = 0; i < 4; i++) {
const discardDiv = document.getElementById(`discard-${i}`);
discardDiv.innerHTML = '';
for (let tileIndex = 0; tileIndex < game.discards[i].tiles.length; tileIndex++) {
const tile = game.discards[i].tiles[tileIndex];
const tileDiv = document.createElement('div');
tileDiv.className = 'discard-tile';
tileDiv.innerHTML = tile.getSVG();
// Calculer la position en grille (6 tuiles par ligne)
const row = Math.floor(tileIndex / 6);
const col = tileIndex % 6;
const x = col * 50;
const y = row * 67;
tileDiv.style.position = 'absolute';
tileDiv.style.left = x + 'px';
tileDiv.style.top = y + 'px';
discardDiv.appendChild(tileDiv);
}
}
}
async function showPonAnimation(player) {
// Obtenir la position du joueur
const handDiv = document.getElementById(`hand-${player}`);
const rect = handDiv.getBoundingClientRect();
// Positions spécifiques pour chaque joueur
const positionOffsets = {
0: { left: rect.left + rect.width / 2 - 50, top: rect.top - 100 },
1: { left: rect.left + rect.width - 250, top: rect.top + rect.height / 2 - 50},
2: { left: rect.left + rect.width / 2 - 75, top: rect.top + rect.height + 30 },
3: { left: rect.left +150, top: rect.top + rect.height / 2 - 50 }
};
const pos = positionOffsets[player] || { left: rect.left + rect.width / 2, top: rect.top - 50 };
// Créer l'élément de texte "Pon"
const ponText = document.createElement('div');
ponText.className = 'pon-text';
ponText.textContent = 'Pon';
ponText.style.left = pos.left + 'px';
ponText.style.top = pos.top + 'px';
document.body.appendChild(ponText);
// Attendre la fin de l'animation
await new Promise(resolve => setTimeout(resolve, 1500));
// Supprimer l'élément
ponText.remove();
}
async function continueBotPlay() {
// Continue la boucle des bots après une réponse du joueur
canPlayerAct = false;
while (game.turn !== 0) {
await new Promise(resolve => setTimeout(resolve, 500));
game.draw(game.turn);
renderHands();
// Pause aléatoire pour simuler la réflexion
const randomDelay = 1000 + Math.random() * 1000;
await new Promise(resolve => setTimeout(resolve, randomDelay));
// Défausser le bot
game.botPlay();
renderDiscards();
// Vérifier si d'autres bots peuvent faire un pon (mais pas sur leur propre défausse)
let ponMade = false;
for (let player = 1; player < 4; player++) {
if (player !== game.turn && game.canPon(player)) {
// Ce bot fait un pon
const lastTile = game.discards[game.turn].tiles[game.discards[game.turn].tiles.length - 1];
const tiles = game.hands[player].tiles.filter(t => t.equals(lastTile)).slice(0, 2);
game.pon(player, tiles, (player - game.turn) % 4);
game.discards[game.turn].beeingStollen();
await showPonAnimation(player);
// Après un Pon, le tour passe au joueur qui appelle le Pon
game.turn = player;
updateTurnIndicator();
renderHands();
renderDiscards();
ponMade = true;
break;
}
}
if (ponMade) continue;
// Vérifier si le joueur peut faire un appel
if (game.canPon(0)) {
// Le joueur peut faire un pon, lui proposer
canPlayerAct = true;
showCallPanelForPon();
return; // Attendre la réponse du joueur
}
// Sinon, passer au joueur suivant
game.nextTurn();
updateTurnIndicator();
renderHands();
}
// C'est au tour du joueur - il pioche d'abord
await new Promise(resolve => setTimeout(resolve, 500));
game.draw(0);
renderHands();
canPlayerAct = true;
}
async function playBotsSequentially() {
// Le joueur ne peut plus agir pendant que les bots jouent
canPlayerAct = false;
// Les bots jouent après le joueur humain
while (game.turn !== 0) {
await new Promise(resolve => setTimeout(resolve, 500));
game.draw(game.turn);
renderHands();
// Pause aléatoire pour simuler la réflexion
const randomDelay = 1000 + Math.random() * 1000;
await new Promise(resolve => setTimeout(resolve, randomDelay));
// Défausser le bot
game.botPlay();
renderDiscards();
// Vérifier si d'autres bots peuvent faire un pon (mais pas sur leur propre défausse)
let ponMade = false;
for (let player = 1; player < 4; player++) {
if (player !== game.turn && game.canPon(player)) {
// Ce bot fait un pon
const lastTile = game.discards[game.turn].tiles[game.discards[game.turn].tiles.length - 1];
const tiles = game.hands[player].tiles.filter(t => t.equals(lastTile)).slice(0, 2);
game.pon(player, tiles, (player - game.turn) % 4);
game.discards[game.turn].beeingStollen();
await showPonAnimation(player);
// Après un Pon, le tour passe au joueur qui appelle le Pon
game.turn = player;
updateTurnIndicator();
renderHands();
renderDiscards();
ponMade = true;
break;
}
}
if (ponMade) continue;
// Vérifier si le joueur peut faire un appel
if (game.canPon(0)) {
// Le joueur peut faire un pon, lui proposer
canPlayerAct = true;
showCallPanelForPon();
return; // Attendre la réponse du joueur
}
// Sinon, passer au joueur suivant
game.nextTurn();
updateTurnIndicator();
renderHands();
}
// C'est au tour du joueur - il pioche d'abord
await new Promise(resolve => setTimeout(resolve, 500));
game.draw(0);
renderHands();
canPlayerAct = true;
}
function updateTurnIndicator() {
for (let i = 0; i < 4; i++) {
const indicator = document.getElementById(`turn-indicator-${i}`);
if (i === game.turn) {
indicator.style.display = 'block';
} else {
indicator.style.display = 'none';
}
}
}
function hideCallPanel() {
document.getElementById('call-panel').style.display = 'none';
}
function showCallPanelForPon() {
const callPanel = document.getElementById('call-panel');
// Afficher seulement Pon et Skip
document.getElementById('pon-button').style.display = 'block';
document.getElementById('chii-button').style.display = 'none';
document.getElementById('kan-button').style.display = 'none';
document.getElementById('tsumo-button').style.display = 'none';
document.getElementById('ron-button').style.display = 'none';
callPanel.style.display = 'flex';
}
function showCallPanel() {
const callPanel = document.getElementById('call-panel');
// Ne pas afficher les appels si c'est le joueur qui vient de défausser
if (lastDiscarder === 0) {
callPanel.style.display = 'none';
return;
}
// Le joueur peut agir
canPlayerAct = true;
// Vérifier les appels possibles pour le joueur
const canPon = game.canPon(0);
// Pour l'instant, on implémente seulement Pon
document.getElementById('pon-button').style.display = canPon ? 'block' : 'none';
// Cacher les autres appels pour l'instant
document.getElementById('chii-button').style.display = 'none';
document.getElementById('kan-button').style.display = 'none';
document.getElementById('tsumo-button').style.display = 'none';
document.getElementById('ron-button').style.display = 'none';
// Afficher le panel si au moins un appel est possible
if (canPon) {
callPanel.style.display = 'flex';
} else {
callPanel.style.display = 'none';
}
}
// Gestionnaires des boutons d'appels
document.getElementById('pon-button').addEventListener('click', async () => {
if (!canPlayerAct) return;
canPlayerAct = false;
const lastTile = game.discards[game.turn].tiles[game.discards[game.turn].tiles.length - 1];
const tiles = game.hands[0].tiles.filter(t => t.equals(lastTile)).slice(0, 2);
game.pon(0, tiles, (0 - game.turn) % 4);
game.discards[game.turn].beeingStollen();
await showPonAnimation(0);
// Après un Pon, le tour passe au joueur qui appelle le Pon
game.turn = 0;
hideCallPanel();
updateTurnIndicator();
renderHands();
renderDiscards();
continueBotPlay();
});
document.getElementById('skip-button').addEventListener('click', () => {
if (!canPlayerAct) return;
canPlayerAct = false;
hideCallPanel();
continueBotPlay();
});
// Ne pas rendre les mains si le jeu n'a pas commencé
if (game) {
renderHands();
updateTurnIndicator();
}
});
</script>
</body>
</html>