diff --git a/game.js b/game.js index 1ffcd92..5d0dbac 100644 --- a/game.js +++ b/game.js @@ -18,9 +18,12 @@ const mainMenu = document.getElementById('mainMenu'); const gameWrapper = document.getElementById('gameWrapper'); const playBtn = document.getElementById('playBtn'); const scoresBtn = document.getElementById('scoresBtn'); +const rulesBtn = document.getElementById('rulesBtn'); const backToMenuBtn = document.getElementById('backToMenuBtn'); const leaderboardModal = document.getElementById('leaderboardModal'); const closeModalBtn = document.getElementById('closeModalBtn'); +const rulesModal = document.getElementById('rulesModal'); +const closeRulesBtn = document.getElementById('closeRulesBtn'); const menuLeaderboard = document.getElementById('menuLeaderboard'); const leaderboardContainer = document.getElementById('leaderboardContainer'); const closeLeaderboardBtn = document.getElementById('closeLeaderboardBtn'); @@ -37,6 +40,22 @@ const EMPTY = 0; const TUNNEL = 3; const BONUS_CHERRY = 4; const BONUS_LUDO = 5; +const BONUS_SPEED = 6; +const BONUS_SHIELD = 7; +const BONUS_BOMB = 8; +const BONUS_MULTIPLIER = 9; + +// Types de fantômes +const GHOST_HUNTER = 'hunter'; +const GHOST_PATROL = 'patrol'; +const GHOST_FAST = 'fast'; +const GHOST_INVISIBLE = 'invisible'; +const GHOST_NORMAL = 'normal'; + +// Zones spéciales +const ZONE_TELEPORT = 10; +const ZONE_BONUS = 11; +const ZONE_DANGER = 12; const originalMaze1 = [ [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], @@ -156,6 +175,44 @@ let lives = 3; let dotsRemainingElement = document.getElementById('dotsRemaining'); let pursuitIndicator = document.getElementById('pursuitIndicator'); let pursuitTimerElement = document.getElementById('pursuitTimer'); +let comboDisplay = document.getElementById('comboDisplay'); +let comboValueElement = document.getElementById('comboValue'); +let frenzyIndicator = document.getElementById('frenzyIndicator'); +let speedBoostIndicator = document.getElementById('speedBoostIndicator'); +let shieldIndicator = document.getElementById('shieldIndicator'); +let multiplierIndicator = document.getElementById('multiplierIndicator'); +let multiplierValueElement = document.getElementById('multiplierValue'); + +// Système de combo +let comboCount = 0; +let comboMultiplier = 1; +let lastDotTime = 0; +const COMBO_TIMEOUT = 3000; // 3 secondes + +// Power-ups actifs +let speedBoostActive = false; +let speedBoostTimer = 0; +let shieldActive = false; +let shieldTimer = 0; +let scoreMultiplierActive = false; +let scoreMultiplierTimer = 0; +let scoreMultiplierValue = 1; + +// Mode Frenzy +let frenzyModeActive = false; +let frenzyTimer = 0; +let frenzyCooldown = 0; +const FRENZY_INTERVAL = 30000; // 30 secondes +const FRENZY_DURATION = 10000; // 10 secondes + + +// Obstacles temporaires +let temporaryWalls = []; +let slowZones = []; +let traps = []; + +// Zones spéciales +let specialZones = []; class Pacman { constructor() { @@ -233,7 +290,30 @@ class Pacman { collectDot() { if (maze[this.y][this.x] === DOT) { maze[this.y][this.x] = EMPTY; - score += 10; + + // Système de combo + const currentTime = Date.now(); + if (currentTime - lastDotTime < COMBO_TIMEOUT) { + comboCount++; + comboMultiplier = Math.min(1 + Math.floor(comboCount / 5), 5); // Max x5 + } else { + comboCount = 1; + comboMultiplier = 1; + } + lastDotTime = currentTime; + + // Afficher le combo + if (comboDisplay && comboValueElement) { + if (comboMultiplier > 1) { + comboDisplay.style.display = 'block'; + comboValueElement.textContent = comboMultiplier; + } else { + comboDisplay.style.display = 'none'; + } + } + + let points = 10 * comboMultiplier * (frenzyModeActive ? 3 : 1) * scoreMultiplierValue; + score += Math.floor(points); scoreElement.textContent = score; totalDots--; if (dotsRemainingElement) { @@ -276,9 +356,71 @@ class Pacman { } } else if (maze[this.y][this.x] === BONUS_LUDO) { maze[this.y][this.x] = EMPTY; - score += 15; + let points = 200 * (frenzyModeActive ? 3 : 1) * scoreMultiplierValue; + score += Math.floor(points); scoreElement.textContent = score; bonuses = bonuses.filter(b => !(b.x === this.x && b.y === this.y && b.type === BONUS_LUDO)); + } else if (maze[this.y][this.x] === BONUS_SPEED) { + maze[this.y][this.x] = EMPTY; + speedBoostActive = true; + speedBoostTimer = 600; // 10 secondes à 60 FPS + this.speed = this.baseSpeed * 2; + bonuses = bonuses.filter(b => !(b.x === this.x && b.y === this.y && b.type === BONUS_SPEED)); + if (speedBoostIndicator) speedBoostIndicator.style.display = 'block'; + } else if (maze[this.y][this.x] === BONUS_SHIELD) { + maze[this.y][this.x] = EMPTY; + shieldActive = true; + shieldTimer = 900; // 15 secondes + bonuses = bonuses.filter(b => !(b.x === this.x && b.y === this.y && b.type === BONUS_SHIELD)); + if (shieldIndicator) shieldIndicator.style.display = 'block'; + } else if (maze[this.y][this.x] === BONUS_BOMB) { + maze[this.y][this.x] = EMPTY; + // Repousser tous les fantômes + for (let ghost of ghosts) { + const dx = ghost.x - this.x; + const dy = ghost.y - this.y; + const distance = Math.sqrt(dx * dx + dy * dy); + if (distance < 5) { + ghost.x = Math.max(0, Math.min(COLS - 1, ghost.x + Math.sign(dx) * 3)); + ghost.y = Math.max(0, Math.min(ROWS - 1, ghost.y + Math.sign(dy) * 3)); + ghost.pixelX = ghost.x * CELL_SIZE + CELL_SIZE / 2; + ghost.pixelY = ghost.y * CELL_SIZE + CELL_SIZE / 2; + } + } + bonuses = bonuses.filter(b => !(b.x === this.x && b.y === this.y && b.type === BONUS_BOMB)); + } else if (maze[this.y][this.x] === BONUS_MULTIPLIER) { + maze[this.y][this.x] = EMPTY; + scoreMultiplierActive = true; + scoreMultiplierTimer = 1800; // 30 secondes + scoreMultiplierValue = 2; + bonuses = bonuses.filter(b => !(b.x === this.x && b.y === this.y && b.type === BONUS_MULTIPLIER)); + if (multiplierIndicator && multiplierValueElement) { + multiplierIndicator.style.display = 'block'; + multiplierValueElement.textContent = scoreMultiplierValue; + } + } else if (maze[this.y][this.x] === ZONE_TELEPORT) { + // Téléportation aléatoire + let newX, newY; + do { + newX = Math.floor(Math.random() * COLS); + newY = Math.floor(Math.random() * ROWS); + } while (maze[newY][newX] === WALL || (newX === this.x && newY === this.y)); + + this.x = newX; + this.y = newY; + this.pixelX = newX * CELL_SIZE + CELL_SIZE / 2; + this.pixelY = newY * CELL_SIZE + CELL_SIZE / 2; + } else if (maze[this.y][this.x] === ZONE_BONUS) { + // Zone bonus : double les points + // Déjà géré par le système de combo + } else if (maze[this.y][this.x] === ZONE_DANGER) { + // Zone danger : ralentit Pacman + this.speed = this.baseSpeed * 0.5; + setTimeout(() => { + if (!speedBoostActive) { + this.speed = this.baseSpeed; + } + }, 2000); } } @@ -308,10 +450,11 @@ class Pacman { } class Ghost { - constructor(x, y, color) { + constructor(x, y, color, type = GHOST_NORMAL) { this.x = x; this.y = y; this.color = color; + this.type = type; this.direction = Math.floor(Math.random() * 4); this.baseSpeed = 0.15; this.speed = this.baseSpeed; @@ -323,15 +466,40 @@ class Ghost { this.vulnerableTimer = 0; this.startX = x; this.startY = y; + this.isInvisible = false; + this.invisibleTimer = 0; + this.patrolTarget = null; + this.patrolIndex = 0; + + // Ajustements selon le type + if (type === GHOST_FAST) { + this.baseSpeed = 0.25; + } else if (type === GHOST_INVISIBLE && level >= 5) { + this.isInvisible = true; + this.invisibleTimer = 300; + } } updateSpeed() { - this.speed = this.baseSpeed * (1 + (level - 1) * 0.2); + let speedMultiplier = 1 + (level - 1) * 0.2; + if (this.type === GHOST_FAST) { + speedMultiplier *= 1.5; + } + this.speed = this.baseSpeed * speedMultiplier; } update() { if (!gameRunning || isPaused) return; + // Gestion de l'invisibilité + if (this.type === GHOST_INVISIBLE && level >= 5) { + this.invisibleTimer--; + if (this.invisibleTimer <= 0) { + this.isInvisible = !this.isInvisible; + this.invisibleTimer = this.isInvisible ? 300 : 300; + } + } + // Gestion de la vulnérabilité if (this.isVulnerable) { this.vulnerableTimer--; @@ -367,8 +535,17 @@ class Ghost { // Fuir le joueur quand vulnérable this.direction = this.getDirectionAwayFromPacman(possibleDirections); } else { - // Toujours poursuivre le joueur - this.direction = this.getDirectionToPacman(possibleDirections); + // Comportement selon le type + if (this.type === GHOST_HUNTER) { + this.direction = this.getDirectionToPacman(possibleDirections); + } else if (this.type === GHOST_PATROL) { + this.direction = this.getPatrolDirection(possibleDirections); + } else if (this.type === GHOST_FAST || this.type === GHOST_INVISIBLE) { + this.direction = this.getDirectionToPacman(possibleDirections); + } else { + // Normal : toujours poursuivre + this.direction = this.getDirectionToPacman(possibleDirections); + } } } this.moveCounter = 0; @@ -462,6 +639,50 @@ class Ghost { return bestDirection; } + getPatrolDirection(possibleDirections) { + // Patrouille : bloque les passages stratégiques + const dx = [0, 1, 0, -1]; + const dy = [-1, 0, 1, 0]; + + // Chercher à se placer entre Pacman et les sorties + const pacmanX = pacman.x; + const pacmanY = pacman.y; + + // Si proche de Pacman, le poursuivre + const distance = Math.sqrt( + Math.pow(pacmanX - this.x, 2) + + Math.pow(pacmanY - this.y, 2) + ); + + if (distance < 5) { + return this.getDirectionToPacman(possibleDirections); + } + + // Sinon, patrouiller vers les zones centrales + const centerX = COLS / 2; + const centerY = ROWS / 2; + + let bestDirection = possibleDirections[0]; + let minDistance = Infinity; + + for (let dir of possibleDirections) { + const nextX = this.x + dx[dir]; + const nextY = this.y + dy[dir]; + + const dist = Math.sqrt( + Math.pow(centerX - nextX, 2) + + Math.pow(centerY - nextY, 2) + ); + + if (dist < minDistance) { + minDistance = dist; + bestDirection = dir; + } + } + + return bestDirection; + } + canMove(direction) { const dx = [0, 1, 0, -1]; const dy = [-1, 0, 1, 0]; @@ -477,6 +698,14 @@ class Ghost { } draw() { + // Si invisible et niveau >= 5, ne pas dessiner 50% du temps + if (this.type === GHOST_INVISIBLE && this.isInvisible && level >= 5) { + const shouldDraw = Math.floor(Date.now() / 200) % 2 === 0; + if (!shouldDraw) { + return; // Ne pas dessiner quand invisible + } + } + ctx.save(); ctx.translate(this.pixelX, this.pixelY); @@ -488,6 +717,10 @@ class Ghost { ctx.fillStyle = flash === 0 ? '#0000ff' : '#ffffff'; } else { ctx.fillStyle = this.color; + // Fantôme invisible : opacité réduite + if (this.type === GHOST_INVISIBLE && this.isInvisible && level >= 5) { + ctx.globalAlpha = 0.5; + } } ctx.strokeStyle = '#000000'; ctx.lineWidth = 2; @@ -528,6 +761,7 @@ class Ghost { ctx.arc(size * 0.2, -size * 0.1, size * 0.06, 0, Math.PI * 2); ctx.fill(); + ctx.globalAlpha = 1.0; ctx.restore(); } } @@ -611,6 +845,62 @@ class Bonus { ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('L', 0, 0); + } else if (this.type === BONUS_SPEED) { + // Étoile de vitesse + ctx.fillStyle = '#00ffff'; + ctx.strokeStyle = '#0088ff'; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.arc(0, 0, CELL_SIZE * 0.3, 0, Math.PI * 2); + ctx.fill(); + ctx.stroke(); + ctx.fillStyle = '#ffffff'; + ctx.font = `bold ${CELL_SIZE * 0.4}px Arial`; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillText('⚡', 0, 0); + } else if (this.type === BONUS_SHIELD) { + // Bouclier + ctx.fillStyle = '#00ff00'; + ctx.strokeStyle = '#008800'; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.arc(0, 0, CELL_SIZE * 0.3, 0, Math.PI * 2); + ctx.fill(); + ctx.stroke(); + ctx.fillStyle = '#ffffff'; + ctx.font = `bold ${CELL_SIZE * 0.4}px Arial`; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillText('🛡', 0, 0); + } else if (this.type === BONUS_BOMB) { + // Bombe + ctx.fillStyle = '#ff0000'; + ctx.strokeStyle = '#880000'; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.arc(0, 0, CELL_SIZE * 0.3, 0, Math.PI * 2); + ctx.fill(); + ctx.stroke(); + ctx.fillStyle = '#ffffff'; + ctx.font = `bold ${CELL_SIZE * 0.4}px Arial`; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillText('💣', 0, 0); + } else if (this.type === BONUS_MULTIPLIER) { + // Multiplicateur + ctx.fillStyle = '#ff00ff'; + ctx.strokeStyle = '#880088'; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.arc(0, 0, CELL_SIZE * 0.3, 0, Math.PI * 2); + ctx.fill(); + ctx.stroke(); + ctx.fillStyle = '#ffffff'; + ctx.font = `bold ${CELL_SIZE * 0.4}px Arial`; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillText('✨', 0, 0); } ctx.restore(); @@ -618,12 +908,46 @@ class Bonus { } let pacman = new Pacman(); -const ghosts = [ - new Ghost(14, 11, '#ff0000'), - new Ghost(15, 11, '#ff00ff'), - new Ghost(14, 12, '#00ffff'), - new Ghost(15, 12, '#ffa500') -]; +let ghosts = []; + +function createGhosts() { + ghosts = []; + const ghostColors = ['#ff0000', '#ff00ff', '#00ffff', '#ffa500', '#00ff00', '#ffff00', '#ff00ff', '#00ffff']; + const ghostTypes = [GHOST_NORMAL, GHOST_HUNTER, GHOST_PATROL, GHOST_FAST, GHOST_INVISIBLE]; + + // Nombre de fantômes selon le niveau + let numGhosts = 4; + if (level >= 3 && level < 5) numGhosts = 5; + else if (level >= 5 && level < 10) numGhosts = 6; + else if (level >= 10) numGhosts = 7; + + const positions = [ + {x: 14, y: 11}, {x: 15, y: 11}, {x: 14, y: 12}, {x: 15, y: 12}, + {x: 13, y: 11}, {x: 16, y: 11}, {x: 13, y: 12}, {x: 16, y: 12} + ]; + + for (let i = 0; i < numGhosts; i++) { + const pos = positions[i % positions.length]; + let type = GHOST_NORMAL; + + // Types selon le niveau + if (level >= 2 && i === 1) type = GHOST_HUNTER; + if (level >= 3 && i === 2) type = GHOST_PATROL; + if (level >= 4 && i === 3) type = GHOST_FAST; + if (level >= 5 && i >= 4) { + const rand = Math.random(); + if (rand < 0.3) type = GHOST_INVISIBLE; + else if (rand < 0.6) type = GHOST_FAST; + else type = GHOST_HUNTER; + } + + ghosts.push(new Ghost(pos.x, pos.y, ghostColors[i % ghostColors.length], type)); + } + + for (let ghost of ghosts) { + ghost.updateSpeed(); + } +} let bonuses = []; @@ -669,6 +993,27 @@ function drawMaze() { ctx.beginPath(); ctx.arc(cellX + CELL_SIZE / 2, cellY + CELL_SIZE / 2, 2, 0, Math.PI * 2); ctx.fill(); + } else if (maze[y][x] === ZONE_TELEPORT) { + // Zone de téléportation + ctx.fillStyle = 'rgba(255, 0, 255, 0.3)'; + ctx.fillRect(cellX, cellY, CELL_SIZE, CELL_SIZE); + ctx.strokeStyle = '#ff00ff'; + ctx.lineWidth = 2; + ctx.strokeRect(cellX, cellY, CELL_SIZE, CELL_SIZE); + } else if (maze[y][x] === ZONE_BONUS) { + // Zone bonus + ctx.fillStyle = 'rgba(255, 215, 0, 0.3)'; + ctx.fillRect(cellX, cellY, CELL_SIZE, CELL_SIZE); + ctx.strokeStyle = '#ffd700'; + ctx.lineWidth = 2; + ctx.strokeRect(cellX, cellY, CELL_SIZE, CELL_SIZE); + } else if (maze[y][x] === ZONE_DANGER) { + // Zone danger + ctx.fillStyle = 'rgba(255, 0, 0, 0.3)'; + ctx.fillRect(cellX, cellY, CELL_SIZE, CELL_SIZE); + ctx.strokeStyle = '#ff0000'; + ctx.lineWidth = 2; + ctx.strokeRect(cellX, cellY, CELL_SIZE, CELL_SIZE); } } } @@ -702,17 +1047,32 @@ function checkCollisions() { ghost.pixelY = ghost.y * CELL_SIZE + CELL_SIZE / 2; ghost.direction = Math.floor(Math.random() * 4); } else { - // Perdre une vie - lives--; - updateLivesDisplay(); - - if (lives <= 0) { - gameRunning = false; - statusElement.textContent = 'Game Over !'; - showGameOver(); - saveScore(); + // Vérifier le bouclier + if (shieldActive) { + // Bouclier actif : repousser le fantôme sans perdre de vie + shieldActive = false; + shieldTimer = 0; + if (shieldIndicator) shieldIndicator.style.display = 'none'; + // Repousser le fantôme + const dx = ghost.x - pacman.x; + const dy = ghost.y - pacman.y; + ghost.x = Math.max(0, Math.min(COLS - 1, ghost.x + Math.sign(dx) * 2)); + ghost.y = Math.max(0, Math.min(ROWS - 1, ghost.y + Math.sign(dy) * 2)); + ghost.pixelX = ghost.x * CELL_SIZE + CELL_SIZE / 2; + ghost.pixelY = ghost.y * CELL_SIZE + CELL_SIZE / 2; } else { - restartCurrentLevel(); + // Perdre une vie + lives--; + updateLivesDisplay(); + + if (lives <= 0) { + gameRunning = false; + statusElement.textContent = 'Game Over !'; + showGameOver(); + saveScore(); + } else { + restartCurrentLevel(); + } } } } @@ -744,21 +1104,88 @@ function gameLoop() { return; } + // Gestion des power-ups + if (speedBoostActive) { + speedBoostTimer--; + if (speedBoostTimer <= 0) { + speedBoostActive = false; + pacman.speed = pacman.baseSpeed; + if (speedBoostIndicator) speedBoostIndicator.style.display = 'none'; + } else if (speedBoostIndicator) { + const seconds = Math.ceil(speedBoostTimer / 60); + speedBoostIndicator.textContent = `⚡ Vitesse (${seconds}s)`; + } + } + + if (shieldActive) { + shieldTimer--; + if (shieldTimer <= 0) { + shieldActive = false; + if (shieldIndicator) shieldIndicator.style.display = 'none'; + } else if (shieldIndicator) { + const seconds = Math.ceil(shieldTimer / 60); + shieldIndicator.textContent = `🛡 Bouclier (${seconds}s)`; + } + } + + if (scoreMultiplierActive) { + scoreMultiplierTimer--; + if (scoreMultiplierTimer <= 0) { + scoreMultiplierActive = false; + scoreMultiplierValue = 1; + if (multiplierIndicator) multiplierIndicator.style.display = 'none'; + } else if (multiplierIndicator && multiplierValueElement) { + const seconds = Math.ceil(scoreMultiplierTimer / 60); + multiplierIndicator.textContent = `✨ x${scoreMultiplierValue} (${seconds}s)`; + } + } + + // Mode Frenzy + if (!frenzyModeActive) { + frenzyCooldown++; + if (frenzyCooldown >= FRENZY_INTERVAL) { + frenzyModeActive = true; + frenzyTimer = FRENZY_DURATION; + frenzyCooldown = 0; + // Rendre tous les fantômes vulnérables + for (let ghost of ghosts) { + ghost.isVulnerable = true; + ghost.vulnerableTimer = FRENZY_DURATION; + } + if (frenzyIndicator) frenzyIndicator.style.display = 'block'; + if (pursuitIndicator) pursuitIndicator.style.display = 'block'; + } + } else { + frenzyTimer--; + if (frenzyTimer <= 0) { + frenzyModeActive = false; + for (let ghost of ghosts) { + ghost.isVulnerable = false; + ghost.vulnerableTimer = 0; + } + if (frenzyIndicator) frenzyIndicator.style.display = 'none'; + if (pursuitIndicator) pursuitIndicator.style.display = 'none'; + } + } + + // Combo timeout + if (comboCount > 0 && Date.now() - lastDotTime > COMBO_TIMEOUT) { + comboCount = 0; + comboMultiplier = 1; + if (comboDisplay) comboDisplay.style.display = 'none'; + } + pacman.update(); pacman.draw(); // Mettre à jour l'indicateur de poursuite - if (pursuitIndicator && cherryEatenTimer > 0) { - const seconds = Math.ceil(cherryEatenTimer / 60); + if (pursuitIndicator && (cherryEatenTimer > 0 || frenzyModeActive)) { + const timer = frenzyModeActive ? frenzyTimer : cherryEatenTimer; + const seconds = Math.ceil(timer / 60); if (pursuitTimerElement) { pursuitTimerElement.textContent = seconds; } - // Vérifier si tous les fantômes sont encore vulnérables - const allVulnerable = ghosts.every(g => g.isVulnerable); - if (!allVulnerable && pursuitIndicator.style.display !== 'none') { - pursuitIndicator.style.display = 'none'; - } - } else if (pursuitIndicator) { + } else if (pursuitIndicator && !frenzyModeActive) { pursuitIndicator.style.display = 'none'; } @@ -813,13 +1240,9 @@ function restartCurrentLevel() { pacman = new Pacman(); pacman.speed = pacman.baseSpeed * (1 + (level - 1) * 0.05); - ghosts[0] = new Ghost(14, 11, '#ff0000'); - ghosts[1] = new Ghost(15, 11, '#ff00ff'); - ghosts[2] = new Ghost(14, 12, '#00ffff'); - ghosts[3] = new Ghost(15, 12, '#ffa500'); + createGhosts(); for (let ghost of ghosts) { - ghost.updateSpeed(); ghost.isVulnerable = false; ghost.vulnerableTimer = 0; } @@ -909,13 +1332,9 @@ function nextLevel() { console.log('Réinitialisation de Pacman et des fantômes'); pacman = new Pacman(); pacman.speed = pacman.baseSpeed * (1 + (level - 1) * 0.05); - ghosts[0] = new Ghost(14, 11, '#ff0000'); - ghosts[1] = new Ghost(15, 11, '#ff00ff'); - ghosts[2] = new Ghost(14, 12, '#00ffff'); - ghosts[3] = new Ghost(15, 12, '#ffa500'); + createGhosts(); for (let ghost of ghosts) { - ghost.updateSpeed(); ghost.isVulnerable = false; ghost.vulnerableTimer = 0; } @@ -1131,9 +1550,46 @@ function placeBonuses() { {x: 14, y: 14, type: BONUS_LUDO}, {x: 15, y: 14, type: BONUS_LUDO} ]; + + // Ajouter des power-ups selon le niveau + if (level >= 2) { + bonusPositions.push({x: 5, y: 5, type: BONUS_SPEED}); + bonusPositions.push({x: 25, y: 5, type: BONUS_SHIELD}); + } + if (level >= 3) { + bonusPositions.push({x: 5, y: 25, type: BONUS_BOMB}); + bonusPositions.push({x: 25, y: 25, type: BONUS_MULTIPLIER}); + } + + // Zones spéciales selon le niveau + if (level >= 4) { + // Zones de téléportation + const teleportZones = [ + {x: 7, y: 7}, {x: 23, y: 7}, {x: 7, y: 23}, {x: 23, y: 23} + ]; + for (let zone of teleportZones) { + if (maze[zone.y] && maze[zone.y][zone.x] === EMPTY) { + maze[zone.y][zone.x] = ZONE_TELEPORT; + specialZones.push({x: zone.x, y: zone.y, type: ZONE_TELEPORT}); + } + } + } + + if (level >= 5) { + // Zones bonus + const bonusZones = [ + {x: 10, y: 10}, {x: 20, y: 10}, {x: 10, y: 20}, {x: 20, y: 20} + ]; + for (let zone of bonusZones) { + if (maze[zone.y] && maze[zone.y][zone.x] === EMPTY) { + maze[zone.y][zone.x] = ZONE_BONUS; + specialZones.push({x: zone.x, y: zone.y, type: ZONE_BONUS}); + } + } + } for (let pos of bonusPositions) { - if (maze[pos.y][pos.x] === EMPTY || maze[pos.y][pos.x] === DOT) { + if (maze[pos.y] && (maze[pos.y][pos.x] === EMPTY || maze[pos.y][pos.x] === DOT)) { maze[pos.y][pos.x] = pos.type; bonuses.push(new Bonus(pos.x, pos.y, pos.type)); } @@ -1152,6 +1608,26 @@ function initGame() { lives = 3; cherriesEaten = 0; isPaused = false; + + // Réinitialiser les systèmes + comboCount = 0; + comboMultiplier = 1; + lastDotTime = 0; + speedBoostActive = false; + speedBoostTimer = 0; + shieldActive = false; + shieldTimer = 0; + scoreMultiplierActive = false; + scoreMultiplierTimer = 0; + scoreMultiplierValue = 1; + frenzyModeActive = false; + frenzyTimer = 0; + frenzyCooldown = 0; + specialZones = []; + temporaryWalls = []; + slowZones = []; + traps = []; + scoreElement.textContent = score; levelElement.textContent = level; updateLivesDisplay(); @@ -1163,16 +1639,17 @@ function initGame() { if (pursuitIndicator) { pursuitIndicator.style.display = 'none'; } + if (comboDisplay) comboDisplay.style.display = 'none'; + if (frenzyIndicator) frenzyIndicator.style.display = 'none'; + if (speedBoostIndicator) speedBoostIndicator.style.display = 'none'; + if (shieldIndicator) shieldIndicator.style.display = 'none'; + if (multiplierIndicator) multiplierIndicator.style.display = 'none'; pacman = new Pacman(); pacman.speed = pacman.baseSpeed * (1 + (level - 1) * 0.05); - ghosts[0] = new Ghost(14, 11, '#ff0000'); - ghosts[1] = new Ghost(15, 11, '#ff00ff'); - ghosts[2] = new Ghost(14, 12, '#00ffff'); - ghosts[3] = new Ghost(15, 12, '#ffa500'); - + createGhosts(); + for (let ghost of ghosts) { - ghost.updateSpeed(); ghost.isVulnerable = false; ghost.vulnerableTimer = 0; } @@ -1605,11 +2082,34 @@ scoresBtn.addEventListener('click', () => { leaderboardModal.style.display = 'flex'; }); +// Ouvrir la modal des règles +if (rulesBtn) { + rulesBtn.addEventListener('click', () => { + rulesModal.style.display = 'flex'; + }); +} + // Fermer le modal de classement closeModalBtn.addEventListener('click', () => { leaderboardModal.style.display = 'none'; }); +// Fermer la modal des règles +if (closeRulesBtn) { + closeRulesBtn.addEventListener('click', () => { + rulesModal.style.display = 'none'; + }); +} + +// Fermer en cliquant en dehors de la modal des règles +if (rulesModal) { + rulesModal.addEventListener('click', (e) => { + if (e.target === rulesModal) { + rulesModal.style.display = 'none'; + } + }); +} + // Fermer le classement dans le jeu if (closeLeaderboardBtn) { closeLeaderboardBtn.addEventListener('click', () => { diff --git a/index.html b/index.html index da4cefc..488a6e9 100644 --- a/index.html +++ b/index.html @@ -24,6 +24,9 @@ + @@ -46,8 +49,17 @@