798 lines
27 KiB
JavaScript
798 lines
27 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,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 = '<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();
|
|
|