From 937fc44907287cf5c42852396bc54e84cdd25858 Mon Sep 17 00:00:00 2001 From: syoul Date: Fri, 28 Nov 2025 17:44:08 +0100 Subject: [PATCH] premier --- game.js | 797 +++++++++++++++++++++++++++++++++++++++++++++++++++++ index.html | 39 +++ style.css | 242 ++++++++++++++++ 3 files changed, 1078 insertions(+) create mode 100644 game.js create mode 100644 index.html create mode 100644 style.css diff --git a/game.js b/game.js new file mode 100644 index 0000000..bb7a69f --- /dev/null +++ b/game.js @@ -0,0 +1,797 @@ +const canvas = document.getElementById('gameCanvas'); +const ctx = canvas.getContext('2d'); +const scoreElement = document.getElementById('score'); +const levelElement = document.getElementById('level'); +const statusElement = document.getElementById('status'); +const restartBtn = document.getElementById('restartBtn'); +const usernameInput = document.getElementById('username'); +const leaderboardElement = document.getElementById('leaderboard'); + +const CELL_SIZE = 20; +const COLS = 30; +const ROWS = 30; + +const WALL = 1; +const DOT = 2; +const EMPTY = 0; +const TUNNEL = 3; +const BONUS_CHERRY = 4; +const BONUS_LUDO = 5; + +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], + [1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1], + [1,2,1,1,1,1,2,1,1,1,1,1,1,2,1,1,2,1,1,1,1,1,1,2,1,1,1,1,2,1], + [1,2,1,1,1,1,2,1,1,1,1,1,1,2,1,1,2,1,1,1,1,1,1,2,1,1,1,1,2,1], + [1,2,1,1,1,1,2,1,1,1,1,1,1,2,1,1,2,1,1,1,1,1,1,2,1,1,1,1,2,1], + [1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1], + [1,2,1,1,1,1,2,1,1,2,1,1,1,1,1,1,1,1,1,1,2,1,1,2,1,1,1,1,2,1], + [1,2,1,1,1,1,2,1,1,2,1,1,1,1,1,1,1,1,1,1,2,1,1,2,1,1,1,1,2,1], + [1,2,2,2,2,2,2,1,1,2,2,2,2,1,1,1,1,2,2,2,2,1,1,2,2,2,2,2,2,1], + [1,1,1,1,1,1,2,1,1,1,1,1,0,1,1,1,1,0,1,1,1,1,1,2,1,1,1,1,1,1], + [0,0,0,0,0,1,2,1,1,1,1,1,0,1,1,1,1,0,1,1,1,1,1,2,1,0,0,0,0,0], + [0,0,0,0,0,1,2,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,2,1,0,0,0,0,0], + [0,0,0,0,0,1,2,1,1,0,1,1,1,3,3,3,3,1,1,1,0,1,1,2,1,0,0,0,0,0], + [1,1,1,1,1,1,2,1,1,0,1,0,0,0,0,0,0,0,0,1,0,1,1,2,1,1,1,1,1,1], + [0,0,0,0,0,0,2,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,2,0,0,0,0,0,0], + [1,1,1,1,1,1,2,1,1,0,1,0,0,0,0,0,0,0,0,1,0,1,1,2,1,1,1,1,1,1], + [0,0,0,0,0,1,2,1,1,0,1,1,1,1,1,1,1,1,1,1,0,1,1,2,1,0,0,0,0,0], + [0,0,0,0,0,1,2,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,2,1,0,0,0,0,0], + [0,0,0,0,0,1,2,1,1,0,1,1,1,1,1,1,1,1,1,1,0,1,1,2,1,0,0,0,0,0], + [1,1,1,1,1,1,2,1,1,0,1,1,1,1,1,1,1,1,1,1,0,1,1,2,1,1,1,1,1,1], + [1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1], + [1,2,1,1,1,1,2,1,1,1,1,1,1,2,1,1,2,1,1,1,1,1,1,2,1,1,1,1,2,1], + [1,2,1,1,1,1,2,1,1,1,1,1,1,2,1,1,2,1,1,1,1,1,1,2,1,1,1,1,2,1], + [1,2,2,2,1,1,2,2,2,2,2,2,2,2,0,0,2,2,2,2,2,2,2,2,1,1,2,2,2,1], + [1,1,1,2,1,1,2,1,1,2,1,1,1,1,1,1,1,1,1,1,2,1,1,2,1,1,2,1,1,1], + [1,1,1,2,1,1,2,1,1,2,1,1,1,1,1,1,1,1,1,1,2,1,1,2,1,1,2,1,1,1], + [1,2,2,2,2,2,2,1,1,2,2,2,2,1,1,1,1,2,2,2,2,1,1,2,2,2,2,2,2,1], + [1,2,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,2,1], + [1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,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,1] +]; + +const originalMaze2 = [ + [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], + [1,2,2,2,2,2,2,2,2,2,1,1,2,2,2,2,2,2,1,1,2,2,2,2,2,2,2,2,2,1], + [1,2,1,1,1,1,2,1,1,2,1,1,2,1,1,1,1,2,1,1,2,1,1,2,1,1,1,1,2,1], + [1,2,2,2,1,1,2,1,1,2,1,1,2,1,1,1,1,2,1,1,2,1,1,2,1,1,2,2,2,1], + [1,1,1,2,1,1,2,2,2,2,1,1,2,2,2,2,2,2,1,1,2,2,2,2,1,1,2,1,1,1], + [1,2,2,2,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,2,2,2,1], + [1,2,1,1,1,1,2,2,2,2,2,2,2,2,1,1,2,2,2,2,2,2,2,2,1,1,1,1,2,1], + [1,2,2,2,2,2,2,1,1,1,1,1,1,2,1,1,2,1,1,1,1,1,1,2,2,2,2,2,2,1], + [1,1,1,1,1,1,2,1,1,2,2,2,2,2,1,1,2,2,2,2,2,1,1,2,1,1,1,1,1,1], + [0,0,0,0,0,1,2,1,1,2,1,1,1,1,1,1,1,1,1,1,2,1,1,2,1,0,0,0,0,0], + [0,0,0,0,0,1,2,1,1,2,1,0,0,0,0,0,0,0,0,1,2,1,1,2,1,0,0,0,0,0], + [0,0,0,0,0,1,2,1,1,2,1,0,1,1,3,3,1,1,0,1,2,1,1,2,1,0,0,0,0,0], + [1,1,1,1,1,1,2,1,1,2,1,0,1,0,0,0,0,1,0,1,2,1,1,2,1,1,1,1,1,1], + [0,0,0,0,0,0,2,0,0,2,0,0,1,0,0,0,0,1,0,0,2,0,0,2,0,0,0,0,0,0], + [1,1,1,1,1,1,2,1,1,2,1,0,1,0,0,0,0,1,0,1,2,1,1,2,1,1,1,1,1,1], + [0,0,0,0,0,1,2,1,1,2,1,0,1,1,1,1,1,1,0,1,2,1,1,2,1,0,0,0,0,0], + [0,0,0,0,0,1,2,1,1,2,1,0,0,0,0,0,0,0,0,1,2,1,1,2,1,0,0,0,0,0], + [0,0,0,0,0,1,2,1,1,2,1,1,1,1,1,1,1,1,1,1,2,1,1,2,1,0,0,0,0,0], + [1,1,1,1,1,1,2,1,1,2,2,2,2,2,1,1,2,2,2,2,2,1,1,2,1,1,1,1,1,1], + [1,2,2,2,2,2,2,2,2,2,1,1,1,2,1,1,2,1,1,1,2,2,2,2,2,2,2,2,2,1], + [1,2,1,1,1,1,2,1,1,1,1,1,1,2,1,1,2,1,1,1,1,1,1,2,1,1,1,1,2,1], + [1,2,2,2,1,1,2,2,2,2,2,2,2,2,0,0,2,2,2,2,2,2,2,2,1,1,2,2,2,1], + [1,1,1,2,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,2,1,1,1], + [1,2,2,2,2,2,2,1,1,2,2,2,2,2,2,2,2,2,2,2,2,1,1,2,2,2,2,2,2,1], + [1,2,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,2,1], + [1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1], + [1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1], + [1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1], + [1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,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,1] +]; + +const originalMaze3 = [ + [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], + [1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1], + [1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1], + [1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1], + [1,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,1], + [1,2,1,1,1,1,2,1,1,1,1,1,2,1,1,1,1,2,1,1,1,1,1,2,1,1,1,1,2,1], + [1,2,2,2,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,2,2,2,1], + [1,1,1,2,1,1,2,1,1,2,1,1,1,1,1,1,1,1,1,1,2,1,1,2,1,1,2,1,1,1], + [1,2,2,2,2,2,2,1,1,2,1,1,1,1,1,1,1,1,1,1,2,1,1,2,2,2,2,2,2,1], + [1,2,1,1,1,1,1,1,1,2,1,1,0,0,0,0,0,0,1,1,2,1,1,1,1,1,1,1,2,1], + [1,2,1,1,1,1,1,1,1,2,1,1,0,1,1,1,1,0,1,1,2,1,1,1,1,1,1,1,2,1], + [1,2,2,2,2,2,2,2,2,2,1,1,0,1,3,3,1,0,1,1,2,2,2,2,2,2,2,2,2,1], + [1,1,1,1,1,1,1,1,1,2,1,1,0,1,1,1,1,0,1,1,2,1,1,1,1,1,1,1,1,1], + [0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0], + [1,1,1,1,1,1,1,1,1,2,1,1,0,1,1,1,1,0,1,1,2,1,1,1,1,1,1,1,1,1], + [1,2,2,2,2,2,2,2,2,2,1,1,0,1,3,3,1,0,1,1,2,2,2,2,2,2,2,2,2,1], + [1,2,1,1,1,1,1,1,1,2,1,1,0,1,1,1,1,0,1,1,2,1,1,1,1,1,1,1,2,1], + [1,2,1,1,1,1,1,1,1,2,1,1,0,0,0,0,0,0,1,1,2,1,1,1,1,1,1,1,2,1], + [1,2,2,2,2,2,2,1,1,2,1,1,1,1,1,1,1,1,1,1,2,1,1,2,2,2,2,2,2,1], + [1,1,1,2,1,1,2,1,1,2,1,1,1,1,1,1,1,1,1,1,2,1,1,2,1,1,2,1,1,1], + [1,2,2,2,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,2,2,2,1], + [1,2,1,1,1,1,2,1,1,1,1,1,2,1,1,1,1,2,1,1,1,1,1,2,1,1,1,1,2,1], + [1,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,1], + [1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1], + [1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1], + [1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1], + [1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1], + [1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1], + [1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,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,1] +]; + +const mazeVariants = [originalMaze1, originalMaze2, originalMaze3]; +let originalMaze = originalMaze1; +let currentMazeIndex = 0; + +let maze = originalMaze.map(row => [...row]); + +let score = 0; +let level = 1; +let gameRunning = true; +let totalDots = 0; +const TEST_MODE = true; +let cherriesEaten = 0; +let isChangingLevel = false; + +class Pacman { + constructor() { + this.x = 14; + this.y = 23; + this.direction = 0; + this.nextDirection = 0; + this.mouthAngle = 0; + this.mouthOpen = true; + this.speed = 0.15; + this.pixelX = this.x * CELL_SIZE + CELL_SIZE / 2; + this.pixelY = this.y * CELL_SIZE + CELL_SIZE / 2; + } + + update() { + if (!gameRunning) return; + + this.mouthAngle += 0.2; + if (this.mouthAngle > Math.PI * 2) { + this.mouthAngle = 0; + this.mouthOpen = !this.mouthOpen; + } + + const gridX = Math.floor(this.pixelX / CELL_SIZE); + const gridY = Math.floor(this.pixelY / CELL_SIZE); + + if (this.canMove(this.nextDirection)) { + this.direction = this.nextDirection; + } + + if (this.canMove(this.direction)) { + const dx = [0, 1, 0, -1]; + const dy = [-1, 0, 1, 0]; + + this.pixelX += dx[this.direction] * this.speed * CELL_SIZE; + this.pixelY += dy[this.direction] * this.speed * CELL_SIZE; + + if (this.pixelX < 0) { + this.pixelX = COLS * CELL_SIZE; + } else if (this.pixelX > COLS * CELL_SIZE) { + this.pixelX = 0; + } + } else { + this.pixelX = gridX * CELL_SIZE + CELL_SIZE / 2; + this.pixelY = gridY * CELL_SIZE + CELL_SIZE / 2; + } + + this.x = Math.floor(this.pixelX / CELL_SIZE); + this.y = Math.floor(this.pixelY / CELL_SIZE); + + this.collectDot(); + } + + canMove(direction) { + const dx = [0, 1, 0, -1]; + const dy = [-1, 0, 1, 0]; + + const nextX = Math.floor((this.pixelX + dx[direction] * CELL_SIZE * 0.5) / CELL_SIZE); + const nextY = Math.floor((this.pixelY + dy[direction] * CELL_SIZE * 0.5) / CELL_SIZE); + + if (nextX < 0 || nextX >= COLS || nextY < 0 || nextY >= ROWS) { + return true; + } + + return maze[nextY][nextX] !== WALL; + } + + collectDot() { + if (maze[this.y][this.x] === DOT) { + maze[this.y][this.x] = EMPTY; + score += 10; + scoreElement.textContent = score; + totalDots--; + + if (totalDots === 0 && !isChangingLevel) { + console.log('Toutes les pastilles collectées, passage au niveau suivant'); + nextLevel(); + } + } else if (maze[this.y][this.x] === BONUS_CHERRY) { + if (isChangingLevel) { + console.log('Changement de niveau en cours, cerise ignorée'); + return; + } + + console.log('Cerise collectée, cherriesEaten:', cherriesEaten); + maze[this.y][this.x] = EMPTY; + score += 100; + scoreElement.textContent = score; + bonuses = bonuses.filter(b => !(b.x === this.x && b.y === this.y && b.type === BONUS_CHERRY)); + cherriesEaten++; + + console.log('Après incrémentation, cherriesEaten:', cherriesEaten, 'TEST_MODE:', TEST_MODE, 'isChangingLevel:', isChangingLevel); + + if (TEST_MODE && cherriesEaten >= 2 && !isChangingLevel) { + console.log('2 cerises mangées, appel de nextLevel()'); + cherriesEaten = 0; + nextLevel(); + } + } else if (maze[this.y][this.x] === BONUS_LUDO) { + maze[this.y][this.x] = EMPTY; + score += 200; + scoreElement.textContent = score; + bonuses = bonuses.filter(b => !(b.x === this.x && b.y === this.y && b.type === BONUS_LUDO)); + } + } + + draw() { + ctx.save(); + ctx.translate(this.pixelX, this.pixelY); + + const rotation = [Math.PI * 1.5, 0, Math.PI * 0.5, Math.PI]; + ctx.rotate(rotation[this.direction]); + + ctx.fillStyle = '#ffd700'; + ctx.beginPath(); + + if (this.mouthOpen) { + ctx.arc(0, 0, CELL_SIZE * 0.4, 0.2, Math.PI * 2 - 0.2); + } else { + ctx.arc(0, 0, CELL_SIZE * 0.4, 0, Math.PI * 2); + } + + ctx.lineTo(0, 0); + ctx.fill(); + ctx.restore(); + } +} + +class Ghost { + constructor(x, y, color) { + this.x = x; + this.y = y; + this.color = color; + this.direction = Math.floor(Math.random() * 4); + this.baseSpeed = 0.1; + this.speed = this.baseSpeed; + this.pixelX = this.x * CELL_SIZE + CELL_SIZE / 2; + this.pixelY = this.y * CELL_SIZE + CELL_SIZE / 2; + this.moveCounter = 0; + } + + updateSpeed() { + this.speed = this.baseSpeed * (1 + (level - 1) * 0.15); + } + + update() { + if (!gameRunning) return; + + this.moveCounter++; + + if (this.moveCounter > 30 || !this.canMove(this.direction)) { + const possibleDirections = []; + for (let i = 0; i < 4; i++) { + if (this.canMove(i)) { + possibleDirections.push(i); + } + } + + if (possibleDirections.length > 0) { + this.direction = possibleDirections[Math.floor(Math.random() * possibleDirections.length)]; + } + this.moveCounter = 0; + } + + const dx = [0, 1, 0, -1]; + const dy = [-1, 0, 1, 0]; + + this.pixelX += dx[this.direction] * this.speed * CELL_SIZE; + this.pixelY += dy[this.direction] * this.speed * CELL_SIZE; + + if (this.pixelX < 0) { + this.pixelX = COLS * CELL_SIZE; + } else if (this.pixelX > COLS * CELL_SIZE) { + this.pixelX = 0; + } + + this.x = Math.floor(this.pixelX / CELL_SIZE); + this.y = Math.floor(this.pixelY / CELL_SIZE); + } + + canMove(direction) { + const dx = [0, 1, 0, -1]; + const dy = [-1, 0, 1, 0]; + + const nextX = Math.floor((this.pixelX + dx[direction] * CELL_SIZE * 0.5) / CELL_SIZE); + const nextY = Math.floor((this.pixelY + dy[direction] * CELL_SIZE * 0.5) / CELL_SIZE); + + if (nextX < 0 || nextX >= COLS || nextY < 0 || nextY >= ROWS) { + return true; + } + + return maze[nextY][nextX] !== WALL; + } + + draw() { + ctx.save(); + ctx.translate(this.pixelX, this.pixelY); + ctx.fillStyle = this.color; + + ctx.beginPath(); + ctx.arc(0, -CELL_SIZE * 0.15, CELL_SIZE * 0.3, Math.PI, 0, false); + ctx.rect(-CELL_SIZE * 0.3, -CELL_SIZE * 0.15, CELL_SIZE * 0.6, CELL_SIZE * 0.45); + ctx.fill(); + + ctx.beginPath(); + ctx.rect(-CELL_SIZE * 0.3, CELL_SIZE * 0.3, CELL_SIZE * 0.2, CELL_SIZE * 0.2); + ctx.rect(-CELL_SIZE * 0.1, CELL_SIZE * 0.3, CELL_SIZE * 0.2, CELL_SIZE * 0.2); + ctx.rect(CELL_SIZE * 0.1, CELL_SIZE * 0.3, CELL_SIZE * 0.2, CELL_SIZE * 0.2); + ctx.fill(); + + ctx.fillStyle = '#fff'; + ctx.beginPath(); + ctx.arc(-CELL_SIZE * 0.15, -CELL_SIZE * 0.1, CELL_SIZE * 0.08, 0, Math.PI * 2); + ctx.arc(CELL_SIZE * 0.15, -CELL_SIZE * 0.1, CELL_SIZE * 0.08, 0, Math.PI * 2); + ctx.fill(); + + ctx.fillStyle = '#000'; + ctx.beginPath(); + ctx.arc(-CELL_SIZE * 0.15, -CELL_SIZE * 0.1, CELL_SIZE * 0.04, 0, Math.PI * 2); + ctx.arc(CELL_SIZE * 0.15, -CELL_SIZE * 0.1, CELL_SIZE * 0.04, 0, Math.PI * 2); + ctx.fill(); + + ctx.restore(); + } +} + +class Bonus { + constructor(x, y, type) { + this.x = x; + this.y = y; + this.type = type; + this.animation = 0; + } + + update() { + this.animation += 0.1; + if (this.animation > Math.PI * 2) { + this.animation = 0; + } + } + + draw() { + const cellX = this.x * CELL_SIZE + CELL_SIZE / 2; + const cellY = this.y * CELL_SIZE + CELL_SIZE / 2; + const scale = 1 + Math.sin(this.animation) * 0.2; + + ctx.save(); + ctx.translate(cellX, cellY); + ctx.scale(scale, scale); + + if (this.type === BONUS_CHERRY) { + ctx.fillStyle = '#ff0000'; + ctx.beginPath(); + ctx.arc(0, 0, CELL_SIZE * 0.25, 0, Math.PI * 2); + ctx.fill(); + + ctx.fillStyle = '#00ff00'; + ctx.beginPath(); + ctx.arc(-CELL_SIZE * 0.15, -CELL_SIZE * 0.2, CELL_SIZE * 0.1, 0, Math.PI * 2); + ctx.fill(); + + ctx.strokeStyle = '#00aa00'; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.moveTo(-CELL_SIZE * 0.15, -CELL_SIZE * 0.3); + ctx.lineTo(-CELL_SIZE * 0.25, -CELL_SIZE * 0.4); + ctx.stroke(); + } else if (this.type === BONUS_LUDO) { + ctx.fillStyle = '#ffd700'; + ctx.font = `bold ${CELL_SIZE * 0.4}px Arial`; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillText('L', 0, 0); + } + + ctx.restore(); + } +} + +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 bonuses = []; + +function countDots() { + totalDots = 0; + if (!maze || maze.length === 0) { + console.error('countDots() - maze est vide ou undefined'); + return; + } + for (let y = 0; y < ROWS; y++) { + if (!maze[y]) { + console.error(`countDots() - maze[${y}] est undefined`); + continue; + } + for (let x = 0; x < COLS; x++) { + if (maze[y][x] === DOT) { + totalDots++; + } + } + } +} + +function drawMaze() { + ctx.fillStyle = '#000'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + for (let y = 0; y < ROWS; y++) { + for (let x = 0; x < COLS; x++) { + const cellX = x * CELL_SIZE; + const cellY = y * CELL_SIZE; + + if (maze[y][x] === WALL) { + ctx.fillStyle = '#0000ff'; + ctx.fillRect(cellX, cellY, CELL_SIZE, CELL_SIZE); + ctx.strokeStyle = '#000080'; + ctx.strokeRect(cellX, cellY, CELL_SIZE, CELL_SIZE); + } else if (maze[y][x] === DOT) { + ctx.fillStyle = '#fff'; + ctx.beginPath(); + ctx.arc(cellX + CELL_SIZE / 2, cellY + CELL_SIZE / 2, 2, 0, Math.PI * 2); + ctx.fill(); + } + } + } + + for (let bonus of bonuses) { + bonus.update(); + bonus.draw(); + } +} + +function checkCollisions() { + if (!gameRunning) return; + + for (let ghost of ghosts) { + const distance = Math.sqrt( + Math.pow(pacman.pixelX - ghost.pixelX, 2) + + Math.pow(pacman.pixelY - ghost.pixelY, 2) + ); + + if (distance < CELL_SIZE * 0.6) { + gameRunning = false; + statusElement.textContent = 'Game Over !'; + restartBtn.style.display = 'block'; + saveScore(); + } + } +} + +function gameLoop() { + if (isChangingLevel || !gameRunning) { + if (isChangingLevel) { + console.log('gameLoop() - Changement de niveau en cours, arrêt'); + } + return; + } + + drawMaze(); + pacman.update(); + pacman.draw(); + + for (let ghost of ghosts) { + ghost.update(); + ghost.draw(); + } + + checkCollisions(); + + if (gameRunning && !isChangingLevel) { + requestAnimationFrame(gameLoop); + } else { + console.log('gameLoop() - Arrêt de la boucle, gameRunning:', gameRunning, 'isChangingLevel:', isChangingLevel); + } +} + +function nextLevel() { + console.log('=== nextLevel() appelée ==='); + console.log('gameRunning:', gameRunning, 'isChangingLevel:', isChangingLevel); + + if (!gameRunning || isChangingLevel) { + console.log('nextLevel() annulée - gameRunning:', gameRunning, 'isChangingLevel:', isChangingLevel); + return; + } + + console.log('Début du changement de niveau'); + isChangingLevel = true; + const wasRunning = gameRunning; + gameRunning = false; + console.log('gameRunning mis à false, wasRunning:', wasRunning); + + requestAnimationFrame(() => { + console.log('Dans requestAnimationFrame, changement du niveau'); + level++; + console.log('Nouveau niveau:', level); + levelElement.textContent = level; + cherriesEaten = 0; + + const mazeIndex = (level - 1) % mazeVariants.length; + console.log('Index du labyrinthe:', mazeIndex); + currentMazeIndex = mazeIndex; + const newMaze = mazeVariants[mazeIndex]; + + console.log('Création du nouveau labyrinthe'); + console.log('newMaze.length:', newMaze.length, 'ROWS:', ROWS); + console.log('newMaze[0]?.length:', newMaze[0]?.length, 'COLS:', COLS); + + if (!newMaze || newMaze.length !== ROWS) { + console.error('Erreur: Le nouveau labyrinthe a une taille incorrecte:', newMaze?.length); + isChangingLevel = false; + gameRunning = wasRunning; + return; + } + + maze = []; + for (let i = 0; i < newMaze.length; i++) { + if (!newMaze[i] || newMaze[i].length !== COLS) { + console.error(`Erreur: Ligne ${i} du labyrinthe a une taille incorrecte:`, newMaze[i]?.length); + } + maze[i] = [...newMaze[i]]; + } + + console.log('Labyrinthe copié, maze.length:', maze.length); + + randomizeMaze(); + + countDots(); + console.log('Total dots:', totalDots); + + bonuses = []; + + console.log('Réinitialisation de Pacman et des fantômes'); + pacman = new Pacman(); + 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'); + + for (let ghost of ghosts) { + ghost.updateSpeed(); + } + + placeBonuses(); + console.log('Bonus placés, nombre:', bonuses.length); + + statusElement.textContent = `Niveau ${level} - Labyrinthe ${mazeIndex + 1} !`; + statusElement.style.color = '#00ff00'; + + console.log('Redessin du labyrinthe'); + drawMaze(); + pacman.draw(); + for (let ghost of ghosts) { + ghost.draw(); + } + + console.log('Attente de 2 secondes avant redémarrage'); + setTimeout(() => { + console.log('Redémarrage de la boucle de jeu'); + isChangingLevel = false; + gameRunning = wasRunning; + console.log('isChangingLevel:', isChangingLevel, 'gameRunning:', gameRunning); + if (gameRunning) { + statusElement.textContent = 'En jeu'; + statusElement.style.color = '#ffd700'; + console.log('Appel de gameLoop() pour redémarrer'); + gameLoop(); + } + }, 2000); + }); +} + +function randomizeMaze() { + const modificationRate = 0.15; + const changes = Math.floor(ROWS * COLS * modificationRate); + + console.log('Modification aléatoire du labyrinthe,', changes, 'changements'); + + for (let i = 0; i < changes; i++) { + const x = Math.floor(Math.random() * COLS); + const y = Math.floor(Math.random() * ROWS); + + if (x === 0 || x === COLS - 1 || y === 0 || y === ROWS - 1) { + continue; + } + + const currentCell = maze[y][x]; + + if (currentCell === WALL) { + if (Math.random() < 0.7) { + maze[y][x] = DOT; + } else { + maze[y][x] = EMPTY; + } + } else if (currentCell === EMPTY) { + if (Math.random() < 0.6) { + maze[y][x] = DOT; + } else if (Math.random() < 0.3) { + maze[y][x] = WALL; + } + } else if (currentCell === DOT) { + if (Math.random() < 0.3) { + maze[y][x] = EMPTY; + } else if (Math.random() < 0.1) { + maze[y][x] = WALL; + } + } + } + + for (let y = 1; y < ROWS - 1; y++) { + for (let x = 1; x < COLS - 1; x++) { + if (maze[y][x] === WALL) { + const neighbors = [ + maze[y-1][x], maze[y+1][x], maze[y][x-1], maze[y][x+1] + ]; + const wallCount = neighbors.filter(c => c === WALL).length; + + if (wallCount === 0 && Math.random() < 0.4) { + maze[y][x] = DOT; + } else if (wallCount === 4 && Math.random() < 0.3) { + maze[y][x] = EMPTY; + } + } + } + } + + console.log('Labyrinthe modifié aléatoirement'); +} + +function placeBonuses() { + bonuses = []; + const bonusPositions = [ + {x: 1, y: 1, type: BONUS_CHERRY}, + {x: 28, y: 1, type: BONUS_CHERRY}, + {x: 1, y: 28, type: BONUS_CHERRY}, + {x: 28, y: 28, type: BONUS_CHERRY}, + {x: 14, y: 14, type: BONUS_LUDO}, + {x: 15, y: 14, type: BONUS_LUDO} + ]; + + for (let pos of bonusPositions) { + if (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)); + } + } +} + +function initGame() { + currentMazeIndex = 0; + originalMaze = mazeVariants[0]; + maze = originalMaze.map(row => [...row]); + countDots(); + score = 0; + level = 1; + cherriesEaten = 0; + scoreElement.textContent = score; + levelElement.textContent = level; + gameRunning = true; + statusElement.textContent = 'En jeu'; + statusElement.style.color = '#ffd700'; + restartBtn.style.display = 'none'; + + pacman = new Pacman(); + 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'); + + for (let ghost of ghosts) { + ghost.updateSpeed(); + } + + placeBonuses(); + countDots(); + gameLoop(); +} + +document.addEventListener('keydown', (e) => { + if (!gameRunning) return; + + switch(e.key) { + case 'ArrowUp': + pacman.nextDirection = 0; + e.preventDefault(); + break; + case 'ArrowRight': + pacman.nextDirection = 1; + e.preventDefault(); + break; + case 'ArrowDown': + pacman.nextDirection = 2; + e.preventDefault(); + break; + case 'ArrowLeft': + pacman.nextDirection = 3; + e.preventDefault(); + break; + } +}); + +function getScores() { + const scoresJson = localStorage.getItem('pacmanScores'); + return scoresJson ? JSON.parse(scoresJson) : []; +} + +function saveScore() { + const username = usernameInput.value.trim() || 'Anonyme'; + if (score > 0) { + const scores = getScores(); + scores.push({ + username: username, + score: score, + date: new Date().toISOString() + }); + scores.sort((a, b) => b.score - a.score); + const topScores = scores.slice(0, 10); + localStorage.setItem('pacmanScores', JSON.stringify(topScores)); + updateLeaderboard(); + } +} + +function updateLeaderboard() { + const scores = getScores(); + leaderboardElement.innerHTML = ''; + + if (scores.length === 0) { + leaderboardElement.innerHTML = '
Aucun score enregistré
'; + return; + } + + scores.forEach((entry, index) => { + const item = document.createElement('div'); + item.className = 'leaderboard-item' + (index < 3 ? ' top' : ''); + + const rank = document.createElement('div'); + rank.className = 'leaderboard-rank'; + rank.textContent = (index + 1) + '.'; + + const name = document.createElement('div'); + name.className = 'leaderboard-name'; + name.textContent = entry.username; + + const scoreDiv = document.createElement('div'); + scoreDiv.className = 'leaderboard-score'; + scoreDiv.textContent = entry.score; + + item.appendChild(rank); + item.appendChild(name); + item.appendChild(scoreDiv); + leaderboardElement.appendChild(item); + }); +} + +restartBtn.addEventListener('click', () => { + initGame(); +}); + +updateLeaderboard(); +initGame(); + diff --git a/index.html b/index.html new file mode 100644 index 0000000..bc0a463 --- /dev/null +++ b/index.html @@ -0,0 +1,39 @@ + + + + + + Jeu Pacman + + + +
+
+

PACMAN

+
+ + +
+
+
Score: 0
+
Niveau: 1
+
Prêt à jouer
+
+ +
+

Utilisez les flèches directionnelles pour déplacer Pacman

+ +
+
+
+

Classement

+
+
+
+ + + + + diff --git a/style.css b/style.css new file mode 100644 index 0000000..8609a3a --- /dev/null +++ b/style.css @@ -0,0 +1,242 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Arial', sans-serif; + background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%); + color: #fff; + min-height: 100vh; + padding: 20px; +} + +.main-wrapper { + display: flex; + justify-content: center; + align-items: flex-start; + gap: 30px; + max-width: 1400px; + margin: 0 auto; +} + +.container { + text-align: center; + background: rgba(0, 0, 0, 0.3); + padding: 30px; + border-radius: 15px; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); + flex-shrink: 0; +} + +h1 { + font-size: 3em; + margin-bottom: 20px; + text-shadow: 3px 3px 6px rgba(0, 0, 0, 0.5); + color: #ffd700; +} + +.game-info { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + padding: 10px 20px; + background: rgba(0, 0, 0, 0.2); + border-radius: 10px; + gap: 15px; + flex-wrap: wrap; +} + +.score { + font-size: 1.5em; + font-weight: bold; +} + +.level { + font-size: 1.5em; + font-weight: bold; + color: #ffd700; +} + +#status { + font-size: 1.2em; + color: #ffd700; +} + +#gameCanvas { + border: 3px solid #ffd700; + border-radius: 10px; + background: #000; + display: block; + margin: 0 auto; + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); +} + +.user-input-section { + margin-bottom: 15px; + display: flex; + align-items: center; + justify-content: center; + gap: 10px; +} + +.user-input-section label { + font-size: 1.1em; + font-weight: bold; +} + +#username { + padding: 8px 15px; + font-size: 1em; + border: 2px solid #ffd700; + border-radius: 8px; + background: rgba(0, 0, 0, 0.3); + color: #fff; + outline: none; + max-width: 200px; +} + +#username:focus { + border-color: #ffed4e; + box-shadow: 0 0 10px rgba(255, 215, 0, 0.5); +} + +.instructions { + margin-top: 20px; + font-size: 1.1em; +} + +.leaderboard-container { + background: rgba(0, 0, 0, 0.3); + padding: 25px; + border-radius: 15px; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); + min-width: 300px; + max-height: 700px; +} + +.leaderboard-container h2 { + color: #ffd700; + text-align: center; + margin-bottom: 20px; + font-size: 2em; + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5); +} + +#leaderboard { + max-height: 600px; + overflow-y: auto; +} + +.leaderboard-item { + background: rgba(255, 255, 255, 0.1); + padding: 12px; + margin-bottom: 10px; + border-radius: 8px; + display: flex; + justify-content: space-between; + align-items: center; + border-left: 4px solid #ffd700; +} + +.leaderboard-item.top { + background: rgba(255, 215, 0, 0.2); + border-left-color: #ffed4e; + font-weight: bold; +} + +.leaderboard-rank { + font-size: 1.3em; + font-weight: bold; + color: #ffd700; + min-width: 30px; +} + +.leaderboard-name { + flex: 1; + text-align: left; + margin-left: 15px; + font-size: 1.1em; +} + +.leaderboard-score { + font-size: 1.2em; + font-weight: bold; + color: #ffd700; + min-width: 80px; + text-align: right; +} + +.empty-leaderboard { + text-align: center; + color: #aaa; + padding: 20px; + font-style: italic; +} + +#restartBtn { + margin-top: 15px; + padding: 12px 30px; + font-size: 1.2em; + background: #ffd700; + color: #000; + border: none; + border-radius: 8px; + cursor: pointer; + font-weight: bold; + transition: background 0.3s; +} + +#restartBtn:hover { + background: #ffed4e; +} + +#restartBtn:active { + transform: scale(0.95); +} + +footer { + text-align: center; + margin-top: 30px; + padding: 20px; + color: #ffd700; + font-size: 1.1em; + font-weight: bold; + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5); +} + +@media (max-width: 1200px) { + .main-wrapper { + flex-direction: column; + align-items: center; + } + + .leaderboard-container { + width: 100%; + max-width: 600px; + } +} + +@media (max-width: 700px) { + #gameCanvas { + width: 100%; + height: auto; + } + + h1 { + font-size: 2em; + } + + .game-info { + flex-direction: column; + gap: 10px; + } + + .user-input-section { + flex-direction: column; + gap: 8px; + } +} +