Files
pacmanludo/game.js

1037 lines
35 KiB
JavaScript

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,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 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,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 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;
let cherryEatenRecently = false;
let cherryEatenTimer = 0;
class Pacman {
constructor() {
this.x = 14;
this.y = 23;
this.direction = 0;
this.nextDirection = 0;
this.mouthAngle = 0;
this.mouthOpen = true;
this.baseSpeed = 0.15;
this.speed = this.baseSpeed;
this.pixelX = this.x * CELL_SIZE + CELL_SIZE / 2;
this.pixelY = this.y * CELL_SIZE + CELL_SIZE / 2;
this.colorAnimation = 0;
}
update() {
if (!gameRunning) return;
this.mouthAngle += 0.2;
if (this.mouthAngle > Math.PI * 2) {
this.mouthAngle = 0;
this.mouthOpen = !this.mouthOpen;
}
this.colorAnimation += 0.3;
if (this.colorAnimation > Math.PI * 2) {
this.colorAnimation = 0;
}
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++;
cherryEatenRecently = true;
cherryEatenTimer = Math.max(150, 300 - (level - 1) * 20);
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]);
const hue = (this.colorAnimation * 180 / Math.PI) % 360;
ctx.fillStyle = `hsl(${hue}, 100%, 50%)`;
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;
this.moveInterval = 30;
}
updateSpeed() {
this.speed = this.baseSpeed * (1 + (level - 1) * 0.2);
}
update() {
if (!gameRunning) return;
if (cherryEatenTimer > 0) {
cherryEatenTimer--;
} else {
cherryEatenRecently = false;
}
this.moveInterval = Math.max(15, 30 - (level - 1) * 2);
this.moveCounter++;
if (this.moveCounter > this.moveInterval || !this.canMove(this.direction)) {
const possibleDirections = [];
for (let i = 0; i < 4; i++) {
if (this.canMove(i)) {
possibleDirections.push(i);
}
}
if (possibleDirections.length > 0) {
if (cherryEatenRecently) {
this.direction = this.getDirectionToPacman(possibleDirections);
} else {
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);
}
getDirectionToPacman(possibleDirections) {
const dx = [0, 1, 0, -1];
const dy = [-1, 0, 1, 0];
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 distance = Math.sqrt(
Math.pow(pacman.x - nextX, 2) +
Math.pow(pacman.y - nextY, 2)
);
if (distance < minDistance) {
minDistance = distance;
bestDirection = dir;
}
}
return bestDirection;
}
getDirectionToPacman(possibleDirections) {
const dx = [0, 1, 0, -1];
const dy = [-1, 0, 1, 0];
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 distance = Math.sqrt(
Math.pow(pacman.x - nextX, 2) +
Math.pow(pacman.y - nextY, 2)
);
if (distance < minDistance) {
minDistance = distance;
bestDirection = dir;
}
}
return bestDirection;
}
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);
const size = CELL_SIZE * 0.75;
ctx.fillStyle = this.color;
ctx.strokeStyle = '#000000';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(0, -size * 0.3, size * 0.5, Math.PI, 0, false);
ctx.fill();
ctx.stroke();
ctx.fillRect(-size * 0.5, -size * 0.3, size * 1.0, size * 0.7);
ctx.strokeRect(-size * 0.5, -size * 0.3, size * 1.0, size * 0.7);
const waveHeight = size * 0.15;
const waveWidth = size * 0.2;
ctx.beginPath();
ctx.moveTo(-size * 0.5, size * 0.4);
for (let i = 0; i < 5; i++) {
const x = -size * 0.5 + i * waveWidth;
const y = size * 0.4 + (i % 2 === 0 ? 0 : waveHeight);
ctx.lineTo(x, y);
}
ctx.lineTo(size * 0.5, size * 0.4);
ctx.lineTo(size * 0.5, size * 0.7);
ctx.lineTo(-size * 0.5, size * 0.7);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(-size * 0.2, -size * 0.1, size * 0.12, 0, Math.PI * 2);
ctx.arc(size * 0.2, -size * 0.1, size * 0.12, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#000000';
ctx.beginPath();
ctx.arc(-size * 0.2, -size * 0.1, size * 0.06, 0, Math.PI * 2);
ctx.arc(size * 0.2, -size * 0.1, size * 0.06, 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) {
const size = CELL_SIZE * 0.45;
ctx.fillStyle = '#ffd700';
ctx.strokeStyle = '#ffaa00';
ctx.lineWidth = 3;
ctx.beginPath();
const spikes = 5;
const outerRadius = size;
const innerRadius = size * 0.4;
for (let i = 0; i < spikes * 2; i++) {
const angle = (i * Math.PI) / spikes;
const radius = i % 2 === 0 ? outerRadius : innerRadius;
const x = Math.cos(angle) * radius;
const y = Math.sin(angle) * radius;
if (i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
}
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.fillStyle = '#ffff00';
ctx.beginPath();
ctx.arc(0, 0, size * 0.3, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#ffd700';
ctx.font = `bold ${size * 0.6}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);
fillEmptySpaces();
randomizeMaze();
countDots();
console.log('Total dots:', totalDots);
bonuses = [];
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');
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 fillEmptySpaces() {
console.log('Remplissage de tous les espaces vides avec des chemins');
// Première passe : remplir tous les empty
for (let y = 1; y < ROWS - 1; y++) {
for (let x = 1; x < COLS - 1; x++) {
if (maze[y][x] === EMPTY) {
maze[y][x] = DOT;
}
}
}
// Détecter et remplir les grandes zones vides
for (let iteration = 0; iteration < 6; iteration++) {
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 emptyCount = neighbors.filter(c => c === EMPTY).length;
const dotCount = neighbors.filter(c => c === DOT).length;
const wallCount = neighbors.filter(c => c === WALL).length;
// Si entouré d'espaces vides ou de beaucoup de chemins, transformer en chemin
if (emptyCount >= 1) {
maze[y][x] = DOT;
} else if (wallCount <= 1 && dotCount >= 2) {
if (Math.random() < 0.9) {
maze[y][x] = DOT;
}
} else if (wallCount === 0) {
maze[y][x] = DOT;
}
} else if (maze[y][x] === EMPTY) {
maze[y][x] = DOT;
}
}
}
// Détecter les grandes zones vides (empty) et les remplir
for (let y = 2; y < ROWS - 2; y++) {
for (let x = 2; x < COLS - 2; x++) {
if (maze[y][x] === EMPTY) {
let emptyArea = 0;
const visited = new Set();
const stack = [[x, y]];
while (stack.length > 0 && emptyArea < 20) {
const [cx, cy] = stack.pop();
const key = `${cy},${cx}`;
if (visited.has(key)) continue;
visited.add(key);
if (maze[cy][cx] === EMPTY) {
emptyArea++;
if (cy > 1) stack.push([cx, cy-1]);
if (cy < ROWS-2) stack.push([cx, cy+1]);
if (cx > 1) stack.push([cx-1, cy]);
if (cx < COLS-2) stack.push([cx+1, cy]);
}
}
if (emptyArea >= 5) {
for (const key of visited) {
const [cy, cx] = key.split(',').map(Number);
if (maze[cy][cx] === EMPTY) {
maze[cy][cx] = DOT;
}
}
}
}
}
}
}
// Créer une petite cavité au centre seulement
const centerX = Math.floor(COLS / 2);
const centerY = Math.floor(ROWS / 2);
if (maze[centerY] && maze[centerY][centerX] !== WALL) {
maze[centerY][centerX] = EMPTY;
}
console.log('Tous les grands espaces réduits, chemins denses créés');
}
function randomizeMaze() {
const modificationRate = 0.05;
const changes = Math.floor(ROWS * COLS * modificationRate);
console.log('Modification aléatoire du labyrinthe,', changes, 'changements');
const centerX = Math.floor(COLS / 2);
const centerY = Math.floor(ROWS / 2);
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 distX = Math.abs(x - centerX);
const distY = Math.abs(y - centerY);
if (distX <= 1 && distY <= 1) {
continue;
}
const currentCell = maze[y][x];
if (currentCell === WALL) {
if (Math.random() < 0.8) {
maze[y][x] = DOT;
}
} else if (currentCell === EMPTY) {
maze[y][x] = DOT;
}
}
// Remplir tous les empty restants
for (let y = 1; y < ROWS - 1; y++) {
for (let x = 1; x < COLS - 1; x++) {
const distX = Math.abs(x - centerX);
const distY = Math.abs(y - centerY);
if (distX > 1 || distY > 1) {
if (maze[y][x] === EMPTY) {
maze[y][x] = DOT;
}
}
}
}
// Transformer les murs isolés en chemins
for (let iteration = 0; iteration < 3; iteration++) {
for (let y = 1; y < ROWS - 1; y++) {
for (let x = 1; x < COLS - 1; x++) {
const distX = Math.abs(x - centerX);
const distY = Math.abs(y - centerY);
if (distX <= 1 && distY <= 1) {
continue;
}
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;
const dotCount = neighbors.filter(c => c === DOT).length;
if (wallCount <= 1 && dotCount >= 2 && Math.random() < 0.85) {
maze[y][x] = DOT;
} else if (wallCount === 0) {
maze[y][x] = DOT;
}
}
}
}
}
console.log('Labyrinthe modifié avec chemins denses, grands espaces réduits');
}
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();
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');
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 = '<div class="empty-leaderboard">Aucun score enregistré</div>';
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();