/* * Le jeu principal */ 'use strict'; const CONFIG = { // gameplay targetDistance: 300, containerRows: 5, containerCols: 4, colors: ['#4fc3f7', '#ffd54f', '#ff8a80'], // physique (pour 60fps) acceleration: 0.6, friction: 0.94, maxSpeed: 10, targetFPS: 60, frameTime: 1000 / 60, // difficulté stabilityThreshold: 0.8, proximityRange: 100, collisionRange: 25, // perf baseMaxIcebergs: 6, collisionCheckInterval: 3, }; // dimensions fenêtre (cache pour éviter les reflows) let viewportWidth = window.innerWidth; let viewportHeight = window.innerHeight; window.addEventListener('resize', () => { viewportWidth = window.innerWidth; viewportHeight = window.innerHeight; }, { passive: true }); class Boat { constructor(elementId, sfxCallback) { this.el = document.getElementById(elementId); this.deck = document.getElementById('container-deck'); this.sfxCallback = sfxCallback; // dimensions du bateau this.width = 50; this.height = 220; this.cachedBounds = null; this.boundsNeedUpdate = true; // position et vitesse this.x = window.innerWidth / 2 - 60; this.y = 20; this.vx = 0; this.lastVx = 0; // détection zigzag (pour pénaliser les changements brusques) this.lastDirection = 0; this.lastChangeTime = 0; this.ZIGZAG_TIME_WINDOW = 800; // Containers this.containers = []; this.activeContainerCount = CONFIG.containerRows * CONFIG.containerCols; this.initDeck(); // touches this.keys = { up: false, down: false, left: false, right: false }; this.bindControls(); // mesurer une fois au début requestAnimationFrame(() => { if (this.el) { this.width = this.el.offsetWidth || 50; this.height = this.el.offsetHeight || 220; } }); } initDeck() { this.deck.innerHTML = ''; const fragment = document.createDocumentFragment(); for (let r = 0; r < CONFIG.containerRows; r++) { const row = document.createElement('div'); row.className = 'container-row'; for (let c = 0; c < CONFIG.containerCols; c++) { const cont = document.createElement('div'); cont.className = 'container'; cont.style.backgroundColor = CONFIG.colors[Math.floor(Math.random() * CONFIG.colors.length)]; this.containers.push({ el: cont, active: true }); row.appendChild(cont); } fragment.appendChild(row); } this.deck.appendChild(fragment); } bindControls() { const handleKey = (e, isPressed) => { const k = e.key.toLowerCase(); if (['arrowup', 'arrowdown', 'arrowleft', 'arrowright'].includes(k)) { e.preventDefault(); } if (k === 'z' || k === 'arrowup') this.keys.up = isPressed; if (k === 's' || k === 'arrowdown') this.keys.down = isPressed; if (k === 'q' || k === 'arrowleft') this.keys.left = isPressed; if (k === 'd' || k === 'arrowright') this.keys.right = isPressed; }; window.addEventListener('keydown', (e) => handleKey(e, true)); window.addEventListener('keyup', (e) => handleKey(e, false)); } checkZigzagConflict() { const now = performance.now(); // Plus précis que Date.now() let currentDirection = 0; if (this.keys.left && !this.keys.right) currentDirection = -1; else if (this.keys.right && !this.keys.left) currentDirection = 1; if (currentDirection !== 0 && currentDirection !== this.lastDirection && this.lastDirection !== 0) { if (now - this.lastChangeTime < this.ZIGZAG_TIME_WINDOW) { this.loseContainer('input_conflict'); this.vx *= 0.3; this.lastDirection = 0; this.lastChangeTime = 0; return true; } } if (currentDirection !== 0 && currentDirection !== this.lastDirection) { this.lastDirection = currentDirection; this.lastChangeTime = now; } return false; } updatePhysics(deltaTime = 1) { if (this.checkZigzagConflict()) { const shake = 5 * Math.sin(performance.now() / 50); this.el.style.transform = `translateX(${this.x}px) rotate(${shake}deg)`; this.el.style.bottom = `${this.y}px`; this.boundsNeedUpdate = true; return; } this.lastVx = this.vx; // Accélération (normalisée par deltaTime) const accel = CONFIG.acceleration * deltaTime; if (this.keys.right) this.vx = Math.min(this.vx + accel, CONFIG.maxSpeed); if (this.keys.left) this.vx = Math.max(this.vx - accel, -CONFIG.maxSpeed); // Mouvement vertical (normalisé par deltaTime) const vStep = 5 * deltaTime; if (this.keys.up) this.y = Math.min(this.y + vStep, viewportHeight - 200); if (this.keys.down) this.y = Math.max(this.y - vStep, 0); // Friction (ajustée pour deltaTime) const frictionFactor = Math.pow(CONFIG.friction, deltaTime); this.vx *= frictionFactor; // Position this.x += this.vx * deltaTime; // Limites const maxX = viewportWidth - this.width; if (this.x < 0) { this.x = 0; this.vx *= -0.5; } if (this.x > maxX) { this.x = maxX; this.vx *= -0.5; } // Rotation visuelle - utiliser transform3d pour GPU acceleration const tilt = this.vx * 2; this.el.style.transform = `translate3d(${this.x}px, 0, 0) rotate(${tilt}deg)`; this.el.style.bottom = `${this.y}px`; this.boundsNeedUpdate = true; } checkStability() { const gForce = Math.abs(this.vx - this.lastVx); if (gForce > CONFIG.stabilityThreshold) { const dropChance = (gForce - CONFIG.stabilityThreshold) * 0.15; if (Math.random() < dropChance) { const direction = (this.vx - this.lastVx) > 0 ? 'left' : 'right'; this.loseContainer(direction); } } } loseContainer(reason) { if (this.activeContainerCount === 0) return false; // Trouver le dernier container actif sans filter() let target = null; for (let i = this.containers.length - 1; i >= 0; i--) { if (this.containers[i].active) { target = this.containers[i]; break; } } if (!target) return false; target.active = false; this.activeContainerCount--; let dropClass; if (reason === 'input_conflict') { dropClass = Math.random() > 0.5 ? 'dropped-left' : 'dropped-right'; } else { dropClass = reason === 'left' ? 'dropped-left' : 'dropped-right'; } target.el.classList.add(dropClass); if (this.sfxCallback) { this.sfxCallback('drop', 0.9); } return true; } getBounds() { // Utiliser le cache si disponible et valide if (!this.boundsNeedUpdate && this.cachedBounds) { return this.cachedBounds; } // Calculer les bounds basés sur la position connue // On utilise les dimensions ORIGINALES du bateau (sans rotation) // pour que la hitbox reste cohérente pendant les virages const top = viewportHeight - this.y - this.height; this.cachedBounds = { left: this.x, top: top, right: this.x + this.width, bottom: top + this.height, width: this.width, height: this.height, centerX: this.x + this.width / 2, centerY: top + this.height / 2 }; this.boundsNeedUpdate = false; return this.cachedBounds; } getContainerCount() { return this.activeContainerCount; } } // images d'icebergs const ICEBERG_IMAGE_COUNT = 12; // précharger pour éviter le lag const ICEBERG_IMAGES = []; let icebergImagesLoaded = false; function preloadIcebergImages() { let loaded = 0; for (let i = 1; i <= ICEBERG_IMAGE_COUNT; i++) { const img = new Image(); img.src = `./assets/img/icebergs/iceberg-${i}.png`; img.onload = () => { loaded++; if (loaded === ICEBERG_IMAGE_COUNT) { icebergImagesLoaded = true; console.log('images icebergs ok'); } }; ICEBERG_IMAGES.push(img); } } // Lancer le préchargement preloadIcebergImages(); class IcebergGenerator { constructor(containerId, difficulty = 1) { this.container = document.getElementById(containerId); this.list = []; this.activeCount = 0; this.pooledElements = []; // max icebergs à l'écran (6 à 15) this.maxIcebergs = Math.min(Math.floor(CONFIG.baseMaxIcebergs + difficulty * 2), 15); } // Récupérer ou créer un élément image getPooledElement(size, imageIndex) { // Chercher un élément dans le pool const pooled = this.pooledElements.pop(); if (pooled) { pooled.style.width = `${size}px`; pooled.style.height = `${size}px`; pooled.style.display = 'block'; pooled.src = ICEBERG_IMAGES[imageIndex % ICEBERG_IMAGE_COUNT].src; return pooled; } // Créer un nouvel élément image const el = document.createElement('img'); el.className = 'iceberg'; el.src = ICEBERG_IMAGES[imageIndex % ICEBERG_IMAGE_COUNT].src; el.alt = 'Iceberg'; el.draggable = false; return el; } // Recycler un élément au lieu de le supprimer recycleElement(el) { el.style.display = 'none'; if (this.pooledElements.length < 20) { // Limite du pool this.pooledElements.push(el); } else { el.remove(); } } spawn(difficulty) { // pas trop d'icebergs en même temps if (this.activeCount >= this.maxIcebergs) return; // attendre que les images soient là if (!icebergImagesLoaded) return; // taille selon difficulté const baseSize = 70 + difficulty * 10; const sizeVariation = 100 + difficulty * 20; const size = Math.min(baseSize + Math.random() * sizeVariation, 300); // trouver une position libre const posX = this.findSafePosition(size); if (posX === null) return; // image aléatoire const imageIndex = Math.floor(Math.random() * ICEBERG_IMAGE_COUNT); const el = this.getPooledElement(size, imageIndex); el.style.width = `${size}px`; el.style.height = `${size}px`; el.style.left = `${posX}px`; el.style.top = '-150px'; if (!el.parentNode) { this.container.appendChild(el); } this.activeCount++; this.list.push({ el, x: posX, y: -150, size, speed: (2 + Math.random() * 2) * difficulty, active: true, inProximity: false }); } findSafePosition(newSize) { const margin = 30; const maxAttempts = 5; for (let attempt = 0; attempt < maxAttempts; attempt++) { const posX = Math.random() * (viewportWidth - newSize); let isSafe = true; // check collision avec les autres icebergs en haut for (const ice of this.list) { if (!ice.active) continue; // Ne vérifier que les icebergs qui sont encore en haut de l'écran if (ice.y > 200) continue; // check horizontal const iceLeft = ice.x; const iceRight = ice.x + ice.size; const newLeft = posX; const newRight = posX + newSize; // overlap avec marge? if (!(newRight + margin < iceLeft || newLeft - margin > iceRight)) { isSafe = false; break; } } if (isSafe) return posX; } return null; // Aucune position sûre trouvée } update(deltaTime = 1) { let needsCleanup = false; const list = this.list; const len = list.length; // Une seule boucle optimisée for (let i = 0; i < len; i++) { const ice = list[i]; if (!ice.active) continue; // Mouvement normalisé par deltaTime ice.y += ice.speed * deltaTime; if (ice.y > viewportHeight) { ice.active = false; ice.el.style.display = 'none'; this.activeCount--; needsCleanup = true; } else { // Mise à jour visuelle directe ice.el.style.transform = `translate3d(0, ${ice.y + 150}px, 0)`; } } // nettoyage si besoin if (needsCleanup) { // Recyclage des éléments inactifs for (let i = 0; i < len; i++) { const ice = list[i]; if (!ice.active && ice.el.style.display === 'none') { if (this.pooledElements.length < 15) { this.pooledElements.push(ice.el); } } } this.list = list.filter(i => i.active); } } checkIcebergOverlap(ice1, ice2, margin = 0) { // Collision rectangulaire avec marge const left1 = ice1.x - margin; const right1 = ice1.x + ice1.size + margin; const top1 = ice1.y - margin; const bottom1 = ice1.y + ice1.size + margin; const left2 = ice2.x; const right2 = ice2.x + ice2.size; const top2 = ice2.y; const bottom2 = ice2.y + ice2.size; return !(right1 < left2 || left1 > right2 || bottom1 < top2 || top1 > bottom2); } } class Game { constructor() { const params = new URLSearchParams(window.location.search); this.difficulty = parseFloat(params.get('difficulty')) || 1; this.score = parseInt(params.get('score')) || 0; this.distance = CONFIG.targetDistance; this.isRunning = false; this.debugMode = false; // touche H pour activer // suivi cargaison sur tous les niveaux this.totalDelivered = parseInt(params.get('totalDelivered')) || 0; this.totalPossible = parseInt(params.get('totalPossible')) || 0; this.checkpoints = parseInt(params.get('checkpoints')) || 0; this.initialContainers = CONFIG.containerRows * CONFIG.containerCols; // 20 // toggle debug this.bindDebugControls(); // sons this.sfx = { drop: document.getElementById('sfx-drop'), alert: document.getElementById('sfx-alert'), break: document.getElementById('sfx-break'), bgm: document.getElementById('background-audio') }; // Volume settings this.masterVolume = 0.8; this.loadVolumeSettings(); // le bateau this.boat = new Boat('player-boat', this.playSound.bind(this)); this.icebergs = new IcebergGenerator('iceberg-layer', this.difficulty); this.lastSpawn = 0; this.spawnRate = 1000 / this.difficulty; this.damageCooldown = 0; // deltaTime this.lastFrameTime = 0; this.frameCount = 0; this.accumulatedTime = 0; // UI this.ui = { dist: document.getElementById('dist-display'), score: document.getElementById('score-display'), cont: document.getElementById('cont-display'), captainName: document.getElementById('captain-name-display') }; // limiter les updates UI this.lastUIUpdate = 0; this.uiUpdateInterval = 200; this.distanceInterval = null; this.loop = this.loop.bind(this); // nom capitaine this.displayCaptainName(); // audio en jeu this.initGameAudioControls(); } loadVolumeSettings() { if (typeof IceBreakerStorage !== 'undefined') { const settings = IceBreakerStorage.getSettings(); this.masterVolume = settings.masterVolume || 0.8; } } displayCaptainName() { if (this.ui.captainName && typeof IceBreakerStorage !== 'undefined') { const name = IceBreakerStorage.getCaptainName(); if (name) { this.ui.captainName.textContent = `Cap. ${name}`; } } } initGameAudioControls() { const toggleBtn = document.getElementById('audio-toggle-game'); const volumeSlider = document.getElementById('volume-slider-game'); if (volumeSlider) { volumeSlider.value = Math.round(this.masterVolume * 100); volumeSlider.addEventListener('input', (e) => { this.masterVolume = parseInt(e.target.value) / 100; this.applyVolume(); if (typeof IceBreakerStorage !== 'undefined') { IceBreakerStorage.saveSettings({ masterVolume: this.masterVolume }); } }); } if (toggleBtn) { toggleBtn.addEventListener('click', () => { if (this.sfx.bgm) { if (this.sfx.bgm.paused) { this.sfx.bgm.play(); toggleBtn.querySelector('.audio-icon').textContent = '🔊'; } else { this.sfx.bgm.pause(); toggleBtn.querySelector('.audio-icon').textContent = '🔇'; } } }); } } applyVolume() { if (this.sfx.bgm) { this.sfx.bgm.volume = this.masterVolume * 0.9; } } preloadSfx() { ['drop', 'alert', 'break'].forEach(name => { const audio = this.sfx[name]; if (audio) { audio.volume = 0; audio.play().then(() => { audio.pause(); audio.currentTime = 0; }).catch(() => { }); } }); } playSound(sfxName, volume = 1.0) { const audio = this.sfx[sfxName]; if (audio && this.isRunning) { audio.currentTime = 0; audio.volume = this.masterVolume * volume; audio.play().catch(() => { }); } } startGame() { if (this.isRunning) return; this.isRunning = true; this.preloadSfx(); this.applyVolume(); this.startGameLoop(); this.startDistanceInterval(); } startGameLoop() { requestAnimationFrame(this.loop); } startDistanceInterval() { // toutes les 200ms this.distanceInterval = setInterval(() => { if (this.isRunning && this.distance > 0) { this.distance--; this.score += Math.ceil(this.difficulty); // UI update géré dans la boucle principale if (this.distance <= 0) this.win(); } }, 200); } loop(timestamp) { if (!this.isRunning) return; // deltaTime (normalisé 60fps) if (this.lastFrameTime === 0) { this.lastFrameTime = timestamp; requestAnimationFrame(this.loop); return; } const rawDelta = timestamp - this.lastFrameTime; this.lastFrameTime = timestamp; // limiter pour éviter les sauts quand on change d'onglet const clampedDelta = Math.min(rawDelta, 33); const deltaTime = clampedDelta / CONFIG.frameTime; this.frameCount++; // physique this.boat.updatePhysics(deltaTime); // stabilité (pas à chaque frame) if (this.frameCount % 4 === 0) { this.boat.checkStability(); } // spawn icebergs if (timestamp - this.lastSpawn > this.spawnRate) { this.icebergs.spawn(this.difficulty); this.lastSpawn = timestamp; this.spawnRate = (1000 + Math.random() * 500) / this.difficulty; } // update icebergs this.icebergs.update(deltaTime); // collisions (toutes les 3 frames) if (this.frameCount % 3 === 0) { this.checkCollisions(); } // UI (pas trop souvent) if (timestamp - this.lastUIUpdate > this.uiUpdateInterval) { this.updateUI(); this.lastUIUpdate = timestamp; } // debug hitboxes (coûte cher) if (this.debugMode && this.frameCount % 6 === 0) { this.updateDebugVisuals(); } requestAnimationFrame(this.loop); } // --- Debug (touche H) --- bindDebugControls() { window.addEventListener('keydown', (e) => { if (e.key === 'h' || e.key === 'H') { this.debugMode = !this.debugMode; console.log('Debug mode:', this.debugMode ? 'ON' : 'OFF'); if (!this.debugMode) { // Supprimer toutes les hitboxes document.querySelectorAll('.debug-hitbox').forEach(el => el.remove()); } } }); } updateDebugVisuals() { // virer les anciens document.querySelectorAll('.debug-hitbox').forEach(el => el.remove()); // hitbox bateau this.drawBoatHitbox(); // hitbox icebergs for (const ice of this.icebergs.list) { if (ice.active) { this.drawIcebergHitbox(ice); } } } drawBoatHitbox() { const boatRect = this.boat.getBounds(); const turbulenceMargin = 30; const tilt = this.boat.vx * 2; // Même rotation que le bateau visuel // Zone de collision (rectangle = taille du bateau) const collisionHitbox = document.createElement('div'); collisionHitbox.className = 'debug-hitbox'; collisionHitbox.style.cssText = ` position: fixed; left: ${boatRect.left}px; top: ${boatRect.top}px; width: ${boatRect.width}px; height: ${boatRect.height}px; border: 3px solid #00ff00; background: rgba(0, 255, 0, 0.15); pointer-events: none; z-index: 9999; box-sizing: border-box; transform: rotate(${tilt}deg); transform-origin: center bottom; `; document.body.appendChild(collisionHitbox); // Zone de turbulence (rectangle 30px plus large) const turbulenceHitbox = document.createElement('div'); turbulenceHitbox.className = 'debug-hitbox'; turbulenceHitbox.style.cssText = ` position: fixed; left: ${boatRect.left - turbulenceMargin}px; top: ${boatRect.top - turbulenceMargin}px; width: ${boatRect.width + turbulenceMargin * 2}px; height: ${boatRect.height + turbulenceMargin * 2}px; border: 2px dashed #ffff00; background: rgba(255, 255, 0, 0.05); pointer-events: none; z-index: 9998; box-sizing: border-box; transform: rotate(${tilt}deg); transform-origin: center bottom; `; document.body.appendChild(turbulenceHitbox); // Label const label = document.createElement('div'); label.className = 'debug-hitbox'; label.style.cssText = ` position: fixed; left: ${boatRect.right + 10}px; top: ${boatRect.top}px; color: #00ff00; font-size: 12px; font-family: monospace; background: rgba(0,0,0,0.7); padding: 2px 6px; pointer-events: none; z-index: 10000; `; label.textContent = `BOAT ${Math.round(boatRect.width)}x${Math.round(boatRect.height)}`; document.body.appendChild(label); } drawIcebergHitbox(ice) { const centerX = ice.x + ice.size / 2; const centerY = ice.y + ice.size / 2; const radius = ice.size / 2; // Hitbox de l'iceberg (cercle) const hitbox = document.createElement('div'); hitbox.className = 'debug-hitbox'; hitbox.style.cssText = ` position: fixed; left: ${centerX - radius}px; top: ${centerY - radius}px; width: ${radius * 2}px; height: ${radius * 2}px; border: 2px solid ${ice.inProximity ? '#ff0000' : '#ff6600'}; border-radius: 50%; background: rgba(255, ${ice.inProximity ? '0' : '102'}, 0, 0.15); pointer-events: none; z-index: 9997; box-sizing: border-box; `; document.body.appendChild(hitbox); } checkCollisions() { const boatRect = this.boat.getBounds(); const turbulenceMargin = 30; const now = performance.now(); const icebergList = this.icebergs.list; const listLength = icebergList.length; if (listLength === 0) return; // valeurs bateau (une fois) const boatLeft = boatRect.left; const boatTop = boatRect.top; const boatRight = boatRect.right; const boatBottom = boatRect.bottom; const boatCenterX = boatRect.centerX; // zone turbulence const turbLeft = boatLeft - turbulenceMargin; const turbTop = boatTop - turbulenceMargin; const turbRight = boatRight + turbulenceMargin; const turbBottom = boatBottom + turbulenceMargin; for (let i = 0; i < listLength; i++) { const ice = icebergList[i]; if (!ice.active) continue; // Early exit: ignorer les icebergs trop loin verticalement const iceBottom = ice.y + ice.size; if (iceBottom < turbTop - 50 || ice.y > turbBottom + 50) continue; // Pré-calcul des valeurs iceberg const halfSize = ice.size * 0.5; const iceCenterX = ice.x + halfSize; const iceCenterY = ice.y + halfSize; // Early exit: ignorer les icebergs trop loin horizontalement if (iceCenterX + halfSize < turbLeft - 20 || iceCenterX - halfSize > turbRight + 20) continue; // Collision avec le bateau (test simplifié AABB d'abord) if (iceCenterX + halfSize > boatLeft && iceCenterX - halfSize < boatRight && iceCenterY + halfSize > boatTop && iceCenterY - halfSize < boatBottom) { this.playSound('break', 1.0); this.gameOver(); return; } // Zone de turbulence (test simplifié AABB) if (iceCenterX + halfSize > turbLeft && iceCenterX - halfSize < turbRight && iceCenterY + halfSize > turbTop && iceCenterY - halfSize < turbBottom) { if (!ice.inProximity) { this.playSound('alert', 0.4); ice.inProximity = true; } if (now - this.damageCooldown > 500) { const dropDirection = (iceCenterX < boatCenterX) ? 'right' : 'left'; if (this.boat.loseContainer(dropDirection)) { this.damageCooldown = now; } } } else { ice.inProximity = false; } } } // Collision entre un rectangle et un cercle checkRectCircleCollision(rectX, rectY, rectW, rectH, circleX, circleY, circleR) { // Trouver le point le plus proche du cercle sur le rectangle const closestX = Math.max(rectX, Math.min(circleX, rectX + rectW)); const closestY = Math.max(rectY, Math.min(circleY, rectY + rectH)); // Calculer la distance entre ce point et le centre du cercle const distX = circleX - closestX; const distY = circleY - closestY; const distanceSquared = distX * distX + distY * distY; return distanceSquared < (circleR * circleR); } updateUI() { if (this.ui.dist) this.ui.dist.textContent = `${this.distance} km`; if (this.ui.score) this.ui.score.textContent = this.score.toLocaleString(); if (this.ui.cont) this.ui.cont.textContent = this.boat.getContainerCount(); } gameOver() { this.isRunning = false; if (this.distanceInterval) clearInterval(this.distanceInterval); // Calculer les totaux pour ce game over (0 containers livrés ce niveau) const containersThisLevel = 0; const totalDelivered = this.totalDelivered + containersThisLevel; const totalPossible = this.totalPossible + this.initialContainers; const distanceTraveled = CONFIG.targetDistance - this.distance; setTimeout(() => { this.showGameOverModal(this.score, totalDelivered, totalPossible, distanceTraveled); }, 300); } showGameOverModal(score, totalDelivered, totalPossible, distance) { const modal = document.getElementById('gameover-modal'); const deliveryRate = totalPossible > 0 ? Math.round((totalDelivered / totalPossible) * 100) : 0; if (!modal) { // Fallback si la modal n'existe pas alert("💥 GAME OVER!\n\nScore final: " + score.toLocaleString()); window.location.href = `index.html?finalScore=${score}&finalDifficulty=${this.difficulty}&totalDelivered=${totalDelivered}&totalPossible=${totalPossible}&checkpoints=${this.checkpoints}`; return; } // Mettre à jour les statistiques const scoreEl = document.getElementById('gameover-score'); const containersEl = document.getElementById('gameover-containers'); const distanceEl = document.getElementById('gameover-distance'); if (scoreEl) scoreEl.textContent = score.toLocaleString(); if (containersEl) containersEl.textContent = `${deliveryRate}% livré`; if (distanceEl) distanceEl.textContent = `${distance} km`; // Afficher la modal modal.classList.remove('hidden'); // Gérer le bouton retour const homeBtn = document.getElementById('gameover-home-btn'); if (homeBtn) { homeBtn.onclick = () => { window.location.href = `index.html?finalScore=${score}&finalDifficulty=${this.difficulty}&totalDelivered=${totalDelivered}&totalPossible=${totalPossible}&checkpoints=${this.checkpoints}`; }; } } win() { this.isRunning = false; if (this.distanceInterval) clearInterval(this.distanceInterval); const containers = this.boat.getContainerCount(); // Passer les données cumulatives au checkpoint window.location.href = `checkpoint.html?score=${this.score}&difficulty=${this.difficulty}&containers=${containers}&totalDelivered=${this.totalDelivered}&totalPossible=${this.totalPossible}&checkpoints=${this.checkpoints}`; } } // --- Démarrage --- function setupAudioPrompt(gameInstance) { const modal = document.getElementById('audio-prompt-modal'); const allowBtn = document.getElementById('allow-audio-button'); const skipBtn = document.getElementById('skip-audio-button'); if (!modal) { gameInstance.startGame(); return; } // vérifier si déjà choisi const settings = typeof IceBreakerStorage !== 'undefined' ? IceBreakerStorage.getSettings() : null; // si déjà choisi, on applique direct if (settings && typeof settings.audioPromptAnswered !== 'undefined') { modal.classList.add('hidden'); if (settings.musicEnabled) { const bgm = gameInstance.sfx.bgm; if (bgm) { bgm.volume = gameInstance.masterVolume * 0.6; bgm.play().catch(() => { }); } } gameInstance.startGame(); return; } const startWithAudio = () => { // sauver le choix if (typeof IceBreakerStorage !== 'undefined') { IceBreakerStorage.saveSettings({ audioPromptAnswered: true, musicEnabled: true }); } const bgm = gameInstance.sfx.bgm; if (bgm) { bgm.volume = gameInstance.masterVolume * 0.6; bgm.play().catch(() => { }); } modal.classList.add('hidden'); gameInstance.startGame(); }; const startWithoutAudio = () => { // sauver le choix if (typeof IceBreakerStorage !== 'undefined') { IceBreakerStorage.saveSettings({ audioPromptAnswered: true, musicEnabled: false }); } modal.classList.add('hidden'); gameInstance.startGame(); }; allowBtn?.addEventListener('click', startWithAudio); skipBtn?.addEventListener('click', startWithoutAudio); } // Démarrage window.addEventListener('DOMContentLoaded', () => { const game = new Game(); setupAudioPrompt(game); });