Compare commits
8 Commits
34fd7d1f4e
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 844c64c7b6 | |||
| 5091fc3d6e | |||
| 58ed4774ba | |||
| e65d165acd | |||
| fb95e88eed | |||
| 286cb2222e | |||
| bedc9876ab | |||
| 366aa894a2 |
697
game.js
697
game.js
@ -7,11 +7,15 @@ const statusElement = document.getElementById('status');
|
|||||||
const restartBtn = document.getElementById('restartBtn');
|
const restartBtn = document.getElementById('restartBtn');
|
||||||
const usernameInput = document.getElementById('username');
|
const usernameInput = document.getElementById('username');
|
||||||
const leaderboardElement = document.getElementById('leaderboard');
|
const leaderboardElement = document.getElementById('leaderboard');
|
||||||
|
const gameLeaderboardContent = document.getElementById('gameLeaderboardContent');
|
||||||
const gameOverlay = document.getElementById('gameOverlay');
|
const gameOverlay = document.getElementById('gameOverlay');
|
||||||
const finalScoreElement = document.getElementById('finalScore');
|
const finalScoreElement = document.getElementById('finalScore');
|
||||||
const finalLevelElement = document.getElementById('finalLevel');
|
const finalLevelElement = document.getElementById('finalLevel');
|
||||||
const overlayRestartBtn = document.getElementById('overlayRestartBtn');
|
const overlayRestartBtn = document.getElementById('overlayRestartBtn');
|
||||||
const confirmUsernameBtn = document.getElementById('confirmUsernameBtn');
|
const confirmUsernameBtn = document.getElementById('confirmUsernameBtn');
|
||||||
|
const gameOverUsernameInput = document.getElementById('gameOverUsername');
|
||||||
|
const saveScoreBtn = document.getElementById('saveScoreBtn');
|
||||||
|
const scoreSavedMsg = document.getElementById('scoreSavedMsg');
|
||||||
|
|
||||||
// === GESTION DU MENU ===
|
// === GESTION DU MENU ===
|
||||||
const mainMenu = document.getElementById('mainMenu');
|
const mainMenu = document.getElementById('mainMenu');
|
||||||
@ -27,10 +31,29 @@ const closeRulesBtn = document.getElementById('closeRulesBtn');
|
|||||||
const menuLeaderboard = document.getElementById('menuLeaderboard');
|
const menuLeaderboard = document.getElementById('menuLeaderboard');
|
||||||
const leaderboardContainer = document.getElementById('leaderboardContainer');
|
const leaderboardContainer = document.getElementById('leaderboardContainer');
|
||||||
const closeLeaderboardBtn = document.getElementById('closeLeaderboardBtn');
|
const closeLeaderboardBtn = document.getElementById('closeLeaderboardBtn');
|
||||||
|
const fullscreenBtn = document.getElementById('fullscreenBtn');
|
||||||
|
const customizeBtn = document.getElementById('customizeBtn');
|
||||||
|
const customizeModal = document.getElementById('customizeModal');
|
||||||
|
const closeCustomizeBtn = document.getElementById('closeCustomizeBtn');
|
||||||
|
const saveCustomizeBtn = document.getElementById('saveCustomizeBtn');
|
||||||
|
const playerPreviewCanvas = document.getElementById('playerPreviewCanvas');
|
||||||
|
const colorOptions = document.querySelectorAll('.color-option');
|
||||||
|
const shapeOptions = document.querySelectorAll('.shape-option');
|
||||||
|
|
||||||
|
// Fonction pour vérifier si on est en mode plein écran
|
||||||
|
function isFullscreen() {
|
||||||
|
return !!(document.fullscreenElement || document.webkitFullscreenElement ||
|
||||||
|
document.mozFullScreenElement || document.msFullscreenElement);
|
||||||
|
}
|
||||||
|
|
||||||
let usernameConfirmed = false;
|
let usernameConfirmed = false;
|
||||||
|
let gameOverUsername = null;
|
||||||
|
|
||||||
const CELL_SIZE = 20;
|
// Personnalisation du joueur
|
||||||
|
let playerColor = localStorage.getItem('playerColor') || 'rainbow';
|
||||||
|
let playerShape = localStorage.getItem('playerShape') || 'round';
|
||||||
|
|
||||||
|
const CELL_SIZE = 25;
|
||||||
const COLS = 30;
|
const COLS = 30;
|
||||||
const ROWS = 30;
|
const ROWS = 30;
|
||||||
|
|
||||||
@ -232,7 +255,7 @@ let traps = [];
|
|||||||
let specialZones = [];
|
let specialZones = [];
|
||||||
|
|
||||||
class Pacman {
|
class Pacman {
|
||||||
constructor() {
|
constructor(color = null, shape = null) {
|
||||||
this.x = 14;
|
this.x = 14;
|
||||||
this.y = 23;
|
this.y = 23;
|
||||||
this.direction = 0;
|
this.direction = 0;
|
||||||
@ -244,6 +267,8 @@ class Pacman {
|
|||||||
this.pixelX = this.x * CELL_SIZE + CELL_SIZE / 2;
|
this.pixelX = this.x * CELL_SIZE + CELL_SIZE / 2;
|
||||||
this.pixelY = this.y * CELL_SIZE + CELL_SIZE / 2;
|
this.pixelY = this.y * CELL_SIZE + CELL_SIZE / 2;
|
||||||
this.colorAnimation = 0;
|
this.colorAnimation = 0;
|
||||||
|
this.color = color || playerColor;
|
||||||
|
this.shape = shape || playerShape;
|
||||||
}
|
}
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
@ -353,14 +378,14 @@ class Pacman {
|
|||||||
cherryEatenTimer = Math.max(150, 300 - (level - 1) * 20);
|
cherryEatenTimer = Math.max(150, 300 - (level - 1) * 20);
|
||||||
|
|
||||||
// Rendre tous les fantômes vulnérables
|
// Rendre tous les fantômes vulnérables
|
||||||
const vulnerableTime = Math.max(180, 360 - (level - 1) * 30);
|
const vulnerableTime = Math.max(60, 180 - (level - 1) * 15);
|
||||||
for (let ghost of ghosts) {
|
for (let ghost of ghosts) {
|
||||||
ghost.isVulnerable = true;
|
ghost.isVulnerable = true;
|
||||||
ghost.vulnerableTimer = vulnerableTime;
|
ghost.vulnerableTimer = vulnerableTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Afficher l'indicateur de poursuite
|
// Afficher l'indicateur de poursuite (sauf en mode plein écran)
|
||||||
if (pursuitIndicator) {
|
if (pursuitIndicator && !isFullscreen()) {
|
||||||
pursuitIndicator.style.display = 'block';
|
pursuitIndicator.style.display = 'block';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -448,19 +473,93 @@ class Pacman {
|
|||||||
const rotation = [Math.PI * 1.5, 0, Math.PI * 0.5, Math.PI];
|
const rotation = [Math.PI * 1.5, 0, Math.PI * 0.5, Math.PI];
|
||||||
ctx.rotate(rotation[this.direction]);
|
ctx.rotate(rotation[this.direction]);
|
||||||
|
|
||||||
const hue = (this.colorAnimation * 180 / Math.PI) % 360;
|
// Utiliser la couleur personnalisée
|
||||||
ctx.fillStyle = `hsl(${hue}, 100%, 50%)`;
|
let baseFillColor;
|
||||||
|
if (this.color === 'rainbow') {
|
||||||
|
const hue = (this.colorAnimation * 180 / Math.PI) % 360;
|
||||||
|
baseFillColor = `hsl(${hue}, 100%, 50%)`;
|
||||||
|
ctx.fillStyle = baseFillColor;
|
||||||
|
} else {
|
||||||
|
const colorMap = {
|
||||||
|
'yellow': '#ffd700',
|
||||||
|
'red': '#ff4444',
|
||||||
|
'blue': '#4444ff',
|
||||||
|
'green': '#44ff44',
|
||||||
|
'purple': '#aa44ff',
|
||||||
|
'orange': '#ff8844',
|
||||||
|
'pink': '#ff44aa'
|
||||||
|
};
|
||||||
|
baseFillColor = colorMap[this.color] || '#ffd700';
|
||||||
|
ctx.fillStyle = baseFillColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size = CELL_SIZE * 0.4;
|
||||||
|
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
|
|
||||||
if (this.mouthOpen) {
|
if (this.shape === 'triangle') {
|
||||||
ctx.arc(0, 0, CELL_SIZE * 0.4, 0.2, Math.PI * 2 - 0.2);
|
const triangleSize = size * 1.5; // Plus grand pour une meilleure visibilité
|
||||||
} else {
|
|
||||||
ctx.arc(0, 0, CELL_SIZE * 0.4, 0, Math.PI * 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.lineTo(0, 0);
|
// Dessiner le triangle avec contour et yeux
|
||||||
ctx.fill();
|
ctx.beginPath();
|
||||||
|
if (this.mouthOpen) {
|
||||||
|
// Triangle avec bouche ouverte - ouverture plus grande et visible
|
||||||
|
ctx.moveTo(0, -triangleSize * 0.9);
|
||||||
|
ctx.lineTo(-triangleSize * 0.85, triangleSize * 0.7);
|
||||||
|
ctx.lineTo(-triangleSize * 0.4, triangleSize * 0.4);
|
||||||
|
ctx.lineTo(0, triangleSize * 0.2);
|
||||||
|
ctx.lineTo(triangleSize * 0.4, triangleSize * 0.4);
|
||||||
|
ctx.lineTo(triangleSize * 0.85, triangleSize * 0.7);
|
||||||
|
ctx.closePath();
|
||||||
|
} else {
|
||||||
|
// Triangle équilatéral complet
|
||||||
|
ctx.moveTo(0, -triangleSize);
|
||||||
|
ctx.lineTo(-triangleSize * 0.866, triangleSize * 0.5); // cos(30°) ≈ 0.866
|
||||||
|
ctx.lineTo(triangleSize * 0.866, triangleSize * 0.5);
|
||||||
|
ctx.closePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remplir le triangle
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
// Contour noir épais pour plus de définition
|
||||||
|
ctx.strokeStyle = '#000000';
|
||||||
|
ctx.lineWidth = 2.5;
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// Dessiner les yeux
|
||||||
|
ctx.fillStyle = '#ffffff';
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(-triangleSize * 0.2, -triangleSize * 0.15, triangleSize * 0.12, 0, Math.PI * 2);
|
||||||
|
ctx.arc(triangleSize * 0.2, -triangleSize * 0.15, triangleSize * 0.12, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
// Pupilles
|
||||||
|
ctx.fillStyle = '#000000';
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(-triangleSize * 0.2, -triangleSize * 0.15, triangleSize * 0.06, 0, Math.PI * 2);
|
||||||
|
ctx.arc(triangleSize * 0.2, -triangleSize * 0.15, triangleSize * 0.06, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
// Reflets dans les yeux pour plus de vie
|
||||||
|
ctx.fillStyle = '#ffffff';
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(-triangleSize * 0.22, -triangleSize * 0.17, triangleSize * 0.03, 0, Math.PI * 2);
|
||||||
|
ctx.arc(triangleSize * 0.18, -triangleSize * 0.17, triangleSize * 0.03, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
// Restaurer la couleur originale
|
||||||
|
ctx.fillStyle = baseFillColor;
|
||||||
|
} else {
|
||||||
|
// Forme ronde (par défaut)
|
||||||
|
if (this.mouthOpen) {
|
||||||
|
ctx.arc(0, 0, size, 0.2, Math.PI * 2 - 0.2);
|
||||||
|
} else {
|
||||||
|
ctx.arc(0, 0, size, 0, Math.PI * 2);
|
||||||
|
}
|
||||||
|
ctx.lineTo(0, 0);
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
@ -490,7 +589,7 @@ class Ghost {
|
|||||||
|
|
||||||
// Ajustements selon le type
|
// Ajustements selon le type
|
||||||
if (type === GHOST_FAST) {
|
if (type === GHOST_FAST) {
|
||||||
this.baseSpeed = 0.25;
|
this.baseSpeed = 0.16;
|
||||||
} else if (type === GHOST_INVISIBLE && level >= 5) {
|
} else if (type === GHOST_INVISIBLE && level >= 5) {
|
||||||
this.isInvisible = true;
|
this.isInvisible = true;
|
||||||
this.invisibleTimer = 300;
|
this.invisibleTimer = 300;
|
||||||
@ -498,9 +597,11 @@ class Ghost {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateSpeed() {
|
updateSpeed() {
|
||||||
let speedMultiplier = 1 + (level - 1) * 0.2;
|
// Vitesse fixe comme au niveau 3 (x1.4) pour tous les niveaux
|
||||||
|
let speedMultiplier = 1.4;
|
||||||
|
|
||||||
if (this.type === GHOST_FAST) {
|
if (this.type === GHOST_FAST) {
|
||||||
speedMultiplier *= 1.5;
|
speedMultiplier *= 1.1;
|
||||||
}
|
}
|
||||||
this.speed = this.baseSpeed * speedMultiplier;
|
this.speed = this.baseSpeed * speedMultiplier;
|
||||||
}
|
}
|
||||||
@ -535,7 +636,7 @@ class Ghost {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.moveInterval = Math.max(8, 20 - (level - 1) * 2);
|
this.moveInterval = Math.max(5, 15 - (level - 1) * 1.5);
|
||||||
|
|
||||||
this.moveCounter++;
|
this.moveCounter++;
|
||||||
|
|
||||||
@ -552,15 +653,20 @@ class Ghost {
|
|||||||
// Fuir le joueur quand vulnérable
|
// Fuir le joueur quand vulnérable
|
||||||
this.direction = this.getDirectionAwayFromPacman(possibleDirections);
|
this.direction = this.getDirectionAwayFromPacman(possibleDirections);
|
||||||
} else {
|
} else {
|
||||||
// Comportement selon le type
|
// Tous les fantômes chassent le joueur (sauf patrouilleurs qui patrouillent si très loin)
|
||||||
if (this.type === GHOST_HUNTER) {
|
if (this.type === GHOST_PATROL) {
|
||||||
this.direction = this.getDirectionToPacman(possibleDirections);
|
const distance = Math.sqrt(
|
||||||
} else if (this.type === GHOST_PATROL) {
|
Math.pow(pacman.x - this.x, 2) +
|
||||||
this.direction = this.getPatrolDirection(possibleDirections);
|
Math.pow(pacman.y - this.y, 2)
|
||||||
} else if (this.type === GHOST_FAST || this.type === GHOST_INVISIBLE) {
|
);
|
||||||
this.direction = this.getDirectionToPacman(possibleDirections);
|
// Si proche, chasser, sinon patrouiller (seulement si très loin)
|
||||||
|
if (distance < 12) {
|
||||||
|
this.direction = this.getDirectionToPacman(possibleDirections);
|
||||||
|
} else {
|
||||||
|
this.direction = this.getPatrolDirection(possibleDirections);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Normal : toujours poursuivre
|
// Tous les autres chassent directement
|
||||||
this.direction = this.getDirectionToPacman(possibleDirections);
|
this.direction = this.getDirectionToPacman(possibleDirections);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -592,10 +698,13 @@ class Ghost {
|
|||||||
let targetX = pacman.x;
|
let targetX = pacman.x;
|
||||||
let targetY = pacman.y;
|
let targetY = pacman.y;
|
||||||
|
|
||||||
|
// Prédiction améliorée pour tous les niveaux
|
||||||
|
const predictionSteps = level >= 3 ? 4 : 3;
|
||||||
|
|
||||||
// Si Pacman bouge, prédire où il sera
|
// Si Pacman bouge, prédire où il sera
|
||||||
if (pacman.direction !== undefined) {
|
if (pacman.direction !== undefined) {
|
||||||
const futureX = pacman.x + dx[pacman.direction] * 2;
|
const futureX = pacman.x + dx[pacman.direction] * predictionSteps;
|
||||||
const futureY = pacman.y + dy[pacman.direction] * 2;
|
const futureY = pacman.y + dy[pacman.direction] * predictionSteps;
|
||||||
if (futureX >= 0 && futureX < COLS && futureY >= 0 && futureY < ROWS) {
|
if (futureX >= 0 && futureX < COLS && futureY >= 0 && futureY < ROWS) {
|
||||||
targetX = futureX;
|
targetX = futureX;
|
||||||
targetY = futureY;
|
targetY = futureY;
|
||||||
@ -609,10 +718,8 @@ class Ghost {
|
|||||||
const oppositeDirection = (this.direction + 2) % 4;
|
const oppositeDirection = (this.direction + 2) % 4;
|
||||||
|
|
||||||
for (let dir of possibleDirections) {
|
for (let dir of possibleDirections) {
|
||||||
// Éviter la direction opposée sauf si c'est la seule option
|
// Permettre le retour en arrière si c'est la meilleure direction
|
||||||
if (possibleDirections.length > 1 && dir === oppositeDirection) {
|
// (pas de restriction pour une poursuite plus agressive)
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextX = this.x + dx[dir];
|
const nextX = this.x + dx[dir];
|
||||||
const nextY = this.y + dy[dir];
|
const nextY = this.y + dy[dir];
|
||||||
@ -1221,7 +1328,7 @@ function checkCollisions() {
|
|||||||
gameRunning = false;
|
gameRunning = false;
|
||||||
statusElement.textContent = 'Game Over !';
|
statusElement.textContent = 'Game Over !';
|
||||||
showGameOver();
|
showGameOver();
|
||||||
saveScore();
|
// Le score sera sauvegardé quand l'utilisateur clique sur "Sauvegarder"
|
||||||
} else {
|
} else {
|
||||||
restartCurrentLevel();
|
restartCurrentLevel();
|
||||||
}
|
}
|
||||||
@ -1305,7 +1412,7 @@ function gameLoop() {
|
|||||||
ghost.vulnerableTimer = FRENZY_DURATION;
|
ghost.vulnerableTimer = FRENZY_DURATION;
|
||||||
}
|
}
|
||||||
if (frenzyIndicator) frenzyIndicator.style.display = 'block';
|
if (frenzyIndicator) frenzyIndicator.style.display = 'block';
|
||||||
if (pursuitIndicator) pursuitIndicator.style.display = 'block';
|
if (pursuitIndicator && !isFullscreen()) pursuitIndicator.style.display = 'block';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
frenzyTimer--;
|
frenzyTimer--;
|
||||||
@ -1330,13 +1437,16 @@ function gameLoop() {
|
|||||||
pacman.update();
|
pacman.update();
|
||||||
pacman.draw();
|
pacman.draw();
|
||||||
|
|
||||||
// Mettre à jour l'indicateur de poursuite
|
// Mettre à jour l'indicateur de poursuite (masquer en mode plein écran)
|
||||||
if (pursuitIndicator && (cherryEatenTimer > 0 || frenzyModeActive)) {
|
if (isFullscreen() && pursuitIndicator) {
|
||||||
|
pursuitIndicator.style.display = 'none';
|
||||||
|
} else if (pursuitIndicator && (cherryEatenTimer > 0 || frenzyModeActive) && !isFullscreen()) {
|
||||||
const timer = frenzyModeActive ? frenzyTimer : cherryEatenTimer;
|
const timer = frenzyModeActive ? frenzyTimer : cherryEatenTimer;
|
||||||
const seconds = Math.ceil(timer / 60);
|
const seconds = Math.ceil(timer / 60);
|
||||||
if (pursuitTimerElement) {
|
if (pursuitTimerElement) {
|
||||||
pursuitTimerElement.textContent = seconds;
|
pursuitTimerElement.textContent = seconds;
|
||||||
}
|
}
|
||||||
|
pursuitIndicator.style.display = 'block';
|
||||||
} else if (pursuitIndicator && !frenzyModeActive) {
|
} else if (pursuitIndicator && !frenzyModeActive) {
|
||||||
pursuitIndicator.style.display = 'none';
|
pursuitIndicator.style.display = 'none';
|
||||||
}
|
}
|
||||||
@ -1748,6 +1858,77 @@ function placeBonuses() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// === REDIMENSIONNEMENT ADAPTATIF DU CANVAS ===
|
||||||
|
function resizeCanvas() {
|
||||||
|
if (!gameWrapper || gameWrapper.style.display === 'none') {
|
||||||
|
return; // Ne pas redimensionner si le jeu n'est pas visible
|
||||||
|
}
|
||||||
|
|
||||||
|
const container = document.querySelector('.container');
|
||||||
|
const gameHeader = document.querySelector('.game-header');
|
||||||
|
const gameInfo = document.querySelector('.game-info');
|
||||||
|
const userInput = document.querySelector('.user-input-section');
|
||||||
|
const powerUps = document.querySelector('.power-ups-display');
|
||||||
|
const frenzyIndicator = document.querySelector('.frenzy-indicator');
|
||||||
|
const pursuitIndicator = document.querySelector('.pursuit-indicator');
|
||||||
|
const instructions = document.querySelector('.instructions');
|
||||||
|
const leaderboard = document.querySelector('.game-leaderboard');
|
||||||
|
|
||||||
|
if (!container || !canvas) return;
|
||||||
|
|
||||||
|
// Calculer l'espace disponible en hauteur
|
||||||
|
const windowHeight = window.innerHeight;
|
||||||
|
const windowWidth = window.innerWidth;
|
||||||
|
|
||||||
|
// Hauteur des éléments autour du canvas
|
||||||
|
let usedHeight = 0;
|
||||||
|
if (gameHeader) usedHeight += gameHeader.offsetHeight + 20;
|
||||||
|
if (userInput && userInput.offsetHeight > 0) usedHeight += userInput.offsetHeight + 10;
|
||||||
|
if (gameInfo) usedHeight += gameInfo.offsetHeight + 20;
|
||||||
|
if (powerUps && powerUps.offsetHeight > 0) usedHeight += powerUps.offsetHeight + 10;
|
||||||
|
if (frenzyIndicator && frenzyIndicator.offsetHeight > 0 && frenzyIndicator.style.display !== 'none') {
|
||||||
|
usedHeight += frenzyIndicator.offsetHeight + 10;
|
||||||
|
}
|
||||||
|
if (pursuitIndicator && pursuitIndicator.offsetHeight > 0 && pursuitIndicator.style.display !== 'none') {
|
||||||
|
usedHeight += pursuitIndicator.offsetHeight + 10;
|
||||||
|
}
|
||||||
|
if (instructions) usedHeight += instructions.offsetHeight + 20;
|
||||||
|
|
||||||
|
// Padding du container (30px top + 30px bottom)
|
||||||
|
usedHeight += 60;
|
||||||
|
|
||||||
|
// Marges et espacement
|
||||||
|
usedHeight += 40;
|
||||||
|
|
||||||
|
// Largeur disponible (en tenant compte du leaderboard à droite si visible)
|
||||||
|
let usedWidth = 0;
|
||||||
|
const containerPadding = 60; // 30px left + 30px right
|
||||||
|
const gaps = 60; // Espacement entre éléments
|
||||||
|
|
||||||
|
if (leaderboard && window.innerWidth > 1200) {
|
||||||
|
const leaderboardRect = leaderboard.getBoundingClientRect();
|
||||||
|
if (leaderboardRect.width > 0) {
|
||||||
|
usedWidth += leaderboardRect.width + 30; // + gap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
usedWidth += containerPadding + gaps;
|
||||||
|
|
||||||
|
const availableHeight = windowHeight - usedHeight;
|
||||||
|
const availableWidth = windowWidth - usedWidth;
|
||||||
|
|
||||||
|
// La taille du canvas sera le minimum entre hauteur et largeur disponible
|
||||||
|
// Utilise 95% de l'espace disponible avec une taille minimale de 400px
|
||||||
|
const maxSize = Math.min(availableHeight, availableWidth);
|
||||||
|
const minSize = 400;
|
||||||
|
const canvasDisplaySize = Math.max(minSize, Math.min(maxSize * 0.95, 1200));
|
||||||
|
|
||||||
|
// Appliquer la taille au canvas via CSS
|
||||||
|
canvas.style.width = canvasDisplaySize + 'px';
|
||||||
|
canvas.style.height = canvasDisplaySize + 'px';
|
||||||
|
canvas.style.maxWidth = '100%';
|
||||||
|
canvas.style.maxHeight = 'calc(100vh - ' + usedHeight + 'px)';
|
||||||
|
}
|
||||||
|
|
||||||
function initGame() {
|
function initGame() {
|
||||||
resetUsernameInput(); // Réinitialiser le champ nom
|
resetUsernameInput(); // Réinitialiser le champ nom
|
||||||
|
|
||||||
@ -1812,6 +1993,11 @@ function initGame() {
|
|||||||
|
|
||||||
placeBonuses();
|
placeBonuses();
|
||||||
countDots();
|
countDots();
|
||||||
|
updateGameLeaderboard();
|
||||||
|
|
||||||
|
// Redimensionner le canvas après l'initialisation
|
||||||
|
setTimeout(resizeCanvas, 100);
|
||||||
|
|
||||||
gameLoop();
|
gameLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1859,9 +2045,15 @@ function getScores() {
|
|||||||
return scoresJson ? JSON.parse(scoresJson) : [];
|
return scoresJson ? JSON.parse(scoresJson) : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveScore() {
|
function saveScore(usernameForScore = null) {
|
||||||
let username;
|
let username;
|
||||||
if (!usernameConfirmed) {
|
|
||||||
|
// Priorité au nom passé en paramètre, puis au nom du Game Over, puis au nom confirmé, sinon "Anonyme"
|
||||||
|
if (usernameForScore) {
|
||||||
|
username = usernameForScore.trim() || 'Anonyme';
|
||||||
|
} else if (gameOverUsername) {
|
||||||
|
username = gameOverUsername.trim() || 'Anonyme';
|
||||||
|
} else if (!usernameConfirmed) {
|
||||||
username = 'Anonyme';
|
username = 'Anonyme';
|
||||||
} else {
|
} else {
|
||||||
username = usernameInput.value.trim() || 'Anonyme';
|
username = usernameInput.value.trim() || 'Anonyme';
|
||||||
@ -1879,6 +2071,7 @@ function saveScore() {
|
|||||||
const topScores = scores.slice(0, 10);
|
const topScores = scores.slice(0, 10);
|
||||||
localStorage.setItem('pacmanScores', JSON.stringify(topScores));
|
localStorage.setItem('pacmanScores', JSON.stringify(topScores));
|
||||||
updateLeaderboard();
|
updateLeaderboard();
|
||||||
|
updateGameLeaderboard();
|
||||||
updateBestScore(); // Mettre à jour le meilleur score dans le menu
|
updateBestScore(); // Mettre à jour le meilleur score dans le menu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1920,6 +2113,48 @@ function updateLeaderboard() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateGameLeaderboard() {
|
||||||
|
if (!gameLeaderboardContent) return;
|
||||||
|
|
||||||
|
const scores = getScores();
|
||||||
|
gameLeaderboardContent.innerHTML = '';
|
||||||
|
|
||||||
|
if (scores.length === 0) {
|
||||||
|
gameLeaderboardContent.innerHTML = '<div class="empty-leaderboard" style="text-align: center; color: #888; padding: 20px; font-size: 0.7em;">Aucun score</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Afficher seulement les 10 premiers
|
||||||
|
const topScores = scores.slice(0, 10);
|
||||||
|
|
||||||
|
topScores.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 || 'Anonyme';
|
||||||
|
|
||||||
|
const levelDiv = document.createElement('div');
|
||||||
|
levelDiv.className = 'leaderboard-level';
|
||||||
|
levelDiv.textContent = 'N' + (entry.level || 1);
|
||||||
|
|
||||||
|
const scoreDiv = document.createElement('div');
|
||||||
|
scoreDiv.className = 'leaderboard-score';
|
||||||
|
scoreDiv.textContent = entry.score;
|
||||||
|
|
||||||
|
item.appendChild(rank);
|
||||||
|
item.appendChild(name);
|
||||||
|
item.appendChild(levelDiv);
|
||||||
|
item.appendChild(scoreDiv);
|
||||||
|
gameLeaderboardContent.appendChild(item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
restartBtn.addEventListener('click', () => {
|
restartBtn.addEventListener('click', () => {
|
||||||
initGame();
|
initGame();
|
||||||
});
|
});
|
||||||
@ -1989,6 +2224,21 @@ usernameInput.addEventListener('keypress', (e) => {
|
|||||||
function showGameOver() {
|
function showGameOver() {
|
||||||
finalScoreElement.textContent = score;
|
finalScoreElement.textContent = score;
|
||||||
finalLevelElement.textContent = level;
|
finalLevelElement.textContent = level;
|
||||||
|
|
||||||
|
// Réinitialiser le champ de nom et cacher le message
|
||||||
|
if (gameOverUsernameInput) {
|
||||||
|
gameOverUsernameInput.value = usernameInput.value.trim() || '';
|
||||||
|
gameOverUsernameInput.disabled = false;
|
||||||
|
gameOverUsernameInput.focus();
|
||||||
|
}
|
||||||
|
if (saveScoreBtn) {
|
||||||
|
saveScoreBtn.disabled = false;
|
||||||
|
}
|
||||||
|
if (scoreSavedMsg) {
|
||||||
|
scoreSavedMsg.style.display = 'none';
|
||||||
|
}
|
||||||
|
gameOverUsername = null; // Réinitialiser
|
||||||
|
|
||||||
gameOverlay.classList.add('active');
|
gameOverlay.classList.add('active');
|
||||||
restartBtn.style.display = 'block';
|
restartBtn.style.display = 'block';
|
||||||
}
|
}
|
||||||
@ -1997,7 +2247,39 @@ function hideGameOver() {
|
|||||||
gameOverlay.classList.remove('active');
|
gameOverlay.classList.remove('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sauvegarder le score avec le nom du Game Over
|
||||||
|
if (saveScoreBtn && gameOverUsernameInput) {
|
||||||
|
saveScoreBtn.addEventListener('click', () => {
|
||||||
|
const username = gameOverUsernameInput.value.trim();
|
||||||
|
if (username.length === 0) {
|
||||||
|
alert('Veuillez entrer un nom d\'utilisateur !');
|
||||||
|
gameOverUsernameInput.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
gameOverUsername = username;
|
||||||
|
saveScore(username);
|
||||||
|
|
||||||
|
// Afficher le message de confirmation
|
||||||
|
if (scoreSavedMsg) {
|
||||||
|
scoreSavedMsg.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Désactiver le champ et le bouton
|
||||||
|
gameOverUsernameInput.disabled = true;
|
||||||
|
saveScoreBtn.disabled = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Permettre de sauvegarder avec la touche Entrée
|
||||||
|
gameOverUsernameInput.addEventListener('keypress', (e) => {
|
||||||
|
if (e.key === 'Enter' && !saveScoreBtn.disabled) {
|
||||||
|
saveScoreBtn.click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
overlayRestartBtn.addEventListener('click', () => {
|
overlayRestartBtn.addEventListener('click', () => {
|
||||||
|
hideGameOver();
|
||||||
initGame();
|
initGame();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -2214,7 +2496,11 @@ playBtn.addEventListener('click', () => {
|
|||||||
resetUsernameInput();
|
resetUsernameInput();
|
||||||
// Toujours initialiser le jeu quand on clique sur JOUER
|
// Toujours initialiser le jeu quand on clique sur JOUER
|
||||||
gameRunning = false; // S'assurer que le jeu est arrêté
|
gameRunning = false; // S'assurer que le jeu est arrêté
|
||||||
initGame();
|
// Attendre un peu pour que le DOM soit rendu avant de redimensionner
|
||||||
|
setTimeout(() => {
|
||||||
|
initGame();
|
||||||
|
resizeCanvas();
|
||||||
|
}, 50);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Retour au menu depuis le jeu
|
// Retour au menu depuis le jeu
|
||||||
@ -2262,6 +2548,262 @@ if (rulesModal) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// === GESTION DU MODAL DE PERSONNALISATION ===
|
||||||
|
let selectedColor = playerColor;
|
||||||
|
let selectedShape = playerShape;
|
||||||
|
|
||||||
|
// Ouvrir le modal de personnalisation
|
||||||
|
if (customizeBtn) {
|
||||||
|
customizeBtn.addEventListener('click', () => {
|
||||||
|
if (customizeModal) {
|
||||||
|
customizeModal.style.display = 'flex';
|
||||||
|
selectedColor = playerColor;
|
||||||
|
selectedShape = playerShape;
|
||||||
|
updateColorSelection();
|
||||||
|
updateShapeSelection();
|
||||||
|
updatePlayerPreview();
|
||||||
|
startPreviewAnimation();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fermer le modal de personnalisation
|
||||||
|
if (closeCustomizeBtn) {
|
||||||
|
closeCustomizeBtn.addEventListener('click', () => {
|
||||||
|
if (customizeModal) {
|
||||||
|
customizeModal.style.display = 'none';
|
||||||
|
stopPreviewAnimation();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fermer en cliquant en dehors du modal
|
||||||
|
if (customizeModal) {
|
||||||
|
customizeModal.addEventListener('click', (e) => {
|
||||||
|
if (e.target === customizeModal) {
|
||||||
|
customizeModal.style.display = 'none';
|
||||||
|
stopPreviewAnimation();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gérer la sélection de couleur
|
||||||
|
if (colorOptions) {
|
||||||
|
colorOptions.forEach(option => {
|
||||||
|
option.addEventListener('click', () => {
|
||||||
|
selectedColor = option.getAttribute('data-color');
|
||||||
|
updateColorSelection();
|
||||||
|
updatePlayerPreview();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gérer la sélection de forme
|
||||||
|
if (shapeOptions) {
|
||||||
|
shapeOptions.forEach(option => {
|
||||||
|
option.addEventListener('click', () => {
|
||||||
|
selectedShape = option.getAttribute('data-shape');
|
||||||
|
updateShapeSelection();
|
||||||
|
updatePlayerPreview();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mettre à jour la sélection visuelle
|
||||||
|
function updateColorSelection() {
|
||||||
|
if (colorOptions) {
|
||||||
|
colorOptions.forEach(option => {
|
||||||
|
if (option.getAttribute('data-color') === selectedColor) {
|
||||||
|
option.classList.add('active');
|
||||||
|
} else {
|
||||||
|
option.classList.remove('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateShapeSelection() {
|
||||||
|
if (shapeOptions) {
|
||||||
|
shapeOptions.forEach(option => {
|
||||||
|
if (option.getAttribute('data-shape') === selectedShape) {
|
||||||
|
option.classList.add('active');
|
||||||
|
} else {
|
||||||
|
option.classList.remove('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prévisualisation du joueur
|
||||||
|
let previewAnimationId = null;
|
||||||
|
function startPreviewAnimation() {
|
||||||
|
if (!playerPreviewCanvas) return;
|
||||||
|
|
||||||
|
let angle = 0;
|
||||||
|
let mouthOpen = true;
|
||||||
|
|
||||||
|
function animate() {
|
||||||
|
if (!playerPreviewCanvas) return;
|
||||||
|
const ctx = playerPreviewCanvas.getContext('2d');
|
||||||
|
ctx.clearRect(0, 0, playerPreviewCanvas.width, playerPreviewCanvas.height);
|
||||||
|
|
||||||
|
// Fond
|
||||||
|
ctx.fillStyle = '#000';
|
||||||
|
ctx.fillRect(0, 0, playerPreviewCanvas.width, playerPreviewCanvas.height);
|
||||||
|
|
||||||
|
// Dessiner le joueur au centre
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(playerPreviewCanvas.width / 2, playerPreviewCanvas.height / 2);
|
||||||
|
|
||||||
|
// Couleur
|
||||||
|
if (selectedColor === 'rainbow') {
|
||||||
|
angle += 0.05;
|
||||||
|
const hue = (angle * 180 / Math.PI) % 360;
|
||||||
|
ctx.fillStyle = `hsl(${hue}, 100%, 50%)`;
|
||||||
|
} else {
|
||||||
|
const colorMap = {
|
||||||
|
'yellow': '#ffd700',
|
||||||
|
'red': '#ff4444',
|
||||||
|
'blue': '#4444ff',
|
||||||
|
'green': '#44ff44',
|
||||||
|
'purple': '#aa44ff',
|
||||||
|
'orange': '#ff8844',
|
||||||
|
'pink': '#ff44aa'
|
||||||
|
};
|
||||||
|
ctx.fillStyle = colorMap[selectedColor] || '#ffd700';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animation de la bouche
|
||||||
|
if (Math.floor(angle * 5) % 2 === 0) {
|
||||||
|
mouthOpen = !mouthOpen;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size = 40;
|
||||||
|
ctx.beginPath();
|
||||||
|
|
||||||
|
if (selectedShape === 'triangle') {
|
||||||
|
const triangleSize = size * 1.5; // Plus grand pour une meilleure visibilité
|
||||||
|
|
||||||
|
// Dessiner le triangle avec contour et yeux
|
||||||
|
ctx.beginPath();
|
||||||
|
if (mouthOpen) {
|
||||||
|
// Triangle avec bouche ouverte
|
||||||
|
ctx.moveTo(0, -triangleSize * 0.9);
|
||||||
|
ctx.lineTo(-triangleSize * 0.85, triangleSize * 0.7);
|
||||||
|
ctx.lineTo(-triangleSize * 0.4, triangleSize * 0.4);
|
||||||
|
ctx.lineTo(0, triangleSize * 0.2);
|
||||||
|
ctx.lineTo(triangleSize * 0.4, triangleSize * 0.4);
|
||||||
|
ctx.lineTo(triangleSize * 0.85, triangleSize * 0.7);
|
||||||
|
ctx.closePath();
|
||||||
|
} else {
|
||||||
|
// Triangle équilatéral complet
|
||||||
|
ctx.moveTo(0, -triangleSize);
|
||||||
|
ctx.lineTo(-triangleSize * 0.866, triangleSize * 0.5);
|
||||||
|
ctx.lineTo(triangleSize * 0.866, triangleSize * 0.5);
|
||||||
|
ctx.closePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remplir le triangle
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
// Contour noir épais
|
||||||
|
ctx.strokeStyle = '#000000';
|
||||||
|
ctx.lineWidth = 2.5;
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// Dessiner les yeux
|
||||||
|
ctx.fillStyle = '#ffffff';
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(-triangleSize * 0.2, -triangleSize * 0.15, triangleSize * 0.12, 0, Math.PI * 2);
|
||||||
|
ctx.arc(triangleSize * 0.2, -triangleSize * 0.15, triangleSize * 0.12, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
// Pupilles
|
||||||
|
ctx.fillStyle = '#000000';
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(-triangleSize * 0.2, -triangleSize * 0.15, triangleSize * 0.06, 0, Math.PI * 2);
|
||||||
|
ctx.arc(triangleSize * 0.2, -triangleSize * 0.15, triangleSize * 0.06, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
// Reflets dans les yeux
|
||||||
|
ctx.fillStyle = '#ffffff';
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(-triangleSize * 0.22, -triangleSize * 0.17, triangleSize * 0.03, 0, Math.PI * 2);
|
||||||
|
ctx.arc(triangleSize * 0.18, -triangleSize * 0.17, triangleSize * 0.03, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
// Restaurer la couleur originale
|
||||||
|
if (selectedColor === 'rainbow') {
|
||||||
|
const hue = (angle * 180 / Math.PI) % 360;
|
||||||
|
ctx.fillStyle = `hsl(${hue}, 100%, 50%)`;
|
||||||
|
} else {
|
||||||
|
const colorMap = {
|
||||||
|
'yellow': '#ffd700',
|
||||||
|
'red': '#ff4444',
|
||||||
|
'blue': '#4444ff',
|
||||||
|
'green': '#44ff44',
|
||||||
|
'purple': '#aa44ff',
|
||||||
|
'orange': '#ff8844',
|
||||||
|
'pink': '#ff44aa'
|
||||||
|
};
|
||||||
|
ctx.fillStyle = colorMap[selectedColor] || '#ffd700';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Dessiner un cercle
|
||||||
|
if (mouthOpen) {
|
||||||
|
ctx.arc(0, 0, size, 0.2, Math.PI * 2 - 0.2);
|
||||||
|
} else {
|
||||||
|
ctx.arc(0, 0, size, 0, Math.PI * 2);
|
||||||
|
}
|
||||||
|
ctx.lineTo(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
ctx.restore();
|
||||||
|
|
||||||
|
previewAnimationId = requestAnimationFrame(animate);
|
||||||
|
}
|
||||||
|
|
||||||
|
animate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopPreviewAnimation() {
|
||||||
|
if (previewAnimationId) {
|
||||||
|
cancelAnimationFrame(previewAnimationId);
|
||||||
|
previewAnimationId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePlayerPreview() {
|
||||||
|
// L'animation se mettra à jour automatiquement car elle utilise selectedColor
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sauvegarder la personnalisation
|
||||||
|
if (saveCustomizeBtn) {
|
||||||
|
saveCustomizeBtn.addEventListener('click', () => {
|
||||||
|
playerColor = selectedColor;
|
||||||
|
playerShape = selectedShape;
|
||||||
|
localStorage.setItem('playerColor', playerColor);
|
||||||
|
localStorage.setItem('playerShape', playerShape);
|
||||||
|
|
||||||
|
// Mettre à jour le joueur actuel si le jeu est en cours
|
||||||
|
if (pacman) {
|
||||||
|
pacman.color = playerColor;
|
||||||
|
pacman.shape = playerShape;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fermer le modal
|
||||||
|
if (customizeModal) {
|
||||||
|
customizeModal.style.display = 'none';
|
||||||
|
stopPreviewAnimation();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message de confirmation
|
||||||
|
alert('Personnalisation sauvegardée !');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Fermer le classement dans le jeu
|
// Fermer le classement dans le jeu
|
||||||
if (closeLeaderboardBtn) {
|
if (closeLeaderboardBtn) {
|
||||||
closeLeaderboardBtn.addEventListener('click', () => {
|
closeLeaderboardBtn.addEventListener('click', () => {
|
||||||
@ -2332,3 +2874,80 @@ function updateBestScore() {
|
|||||||
updateMenuLeaderboard();
|
updateMenuLeaderboard();
|
||||||
updateBestScore();
|
updateBestScore();
|
||||||
|
|
||||||
|
// Appeler au chargement, au redimensionnement et quand le jeu devient visible
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
if (gameWrapper && gameWrapper.style.display !== 'none') {
|
||||||
|
resizeCanvas();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Redimensionner quand le jeu est affiché
|
||||||
|
if (gameWrapper) {
|
||||||
|
const observer = new MutationObserver(() => {
|
||||||
|
if (gameWrapper && gameWrapper.style.display !== 'none') {
|
||||||
|
// Attendre un peu pour que le DOM soit complètement rendu
|
||||||
|
setTimeout(resizeCanvas, 100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
observer.observe(gameWrapper, { attributes: true, attributeFilter: ['style'] });
|
||||||
|
}
|
||||||
|
|
||||||
|
// === GESTION DU PLEIN ÉCRAN ===
|
||||||
|
function toggleFullscreen() {
|
||||||
|
const gameWrapper = document.getElementById('gameWrapper');
|
||||||
|
|
||||||
|
if (!document.fullscreenElement && !document.webkitFullscreenElement &&
|
||||||
|
!document.mozFullScreenElement && !document.msFullscreenElement) {
|
||||||
|
// Entrer en plein écran
|
||||||
|
if (gameWrapper.requestFullscreen) {
|
||||||
|
gameWrapper.requestFullscreen();
|
||||||
|
} else if (gameWrapper.webkitRequestFullscreen) {
|
||||||
|
gameWrapper.webkitRequestFullscreen();
|
||||||
|
} else if (gameWrapper.mozRequestFullScreen) {
|
||||||
|
gameWrapper.mozRequestFullScreen();
|
||||||
|
} else if (gameWrapper.msRequestFullscreen) {
|
||||||
|
gameWrapper.msRequestFullscreen();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Quitter le plein écran
|
||||||
|
if (document.exitFullscreen) {
|
||||||
|
document.exitFullscreen();
|
||||||
|
} else if (document.webkitExitFullscreen) {
|
||||||
|
document.webkitExitFullscreen();
|
||||||
|
} else if (document.mozCancelFullScreen) {
|
||||||
|
document.mozCancelFullScreen();
|
||||||
|
} else if (document.msExitFullscreen) {
|
||||||
|
document.msExitFullscreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFullscreenButton() {
|
||||||
|
const isFullscreenMode = document.fullscreenElement || document.webkitFullscreenElement ||
|
||||||
|
document.mozFullScreenElement || document.msFullscreenElement;
|
||||||
|
|
||||||
|
if (fullscreenBtn) {
|
||||||
|
fullscreenBtn.textContent = isFullscreenMode ? '✕ Sortir' : '⛶ Plein écran';
|
||||||
|
fullscreenBtn.title = isFullscreenMode ? 'Quitter le plein écran' : 'Plein écran';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cacher l'indicateur de poursuite en mode plein écran
|
||||||
|
if (isFullscreenMode && pursuitIndicator) {
|
||||||
|
pursuitIndicator.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redimensionner le canvas après changement de mode plein écran
|
||||||
|
setTimeout(resizeCanvas, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ajouter l'event listener au bouton
|
||||||
|
if (fullscreenBtn) {
|
||||||
|
fullscreenBtn.addEventListener('click', toggleFullscreen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Écouter les changements de mode plein écran
|
||||||
|
document.addEventListener('fullscreenchange', updateFullscreenButton);
|
||||||
|
document.addEventListener('webkitfullscreenchange', updateFullscreenButton);
|
||||||
|
document.addEventListener('mozfullscreenchange', updateFullscreenButton);
|
||||||
|
document.addEventListener('MSFullscreenChange', updateFullscreenButton);
|
||||||
|
|
||||||
|
|||||||
139
index.html
139
index.html
@ -21,6 +21,9 @@
|
|||||||
<button class="menu-btn" id="playBtn">
|
<button class="menu-btn" id="playBtn">
|
||||||
<span class="btn-icon">▶</span> JOUER
|
<span class="btn-icon">▶</span> JOUER
|
||||||
</button>
|
</button>
|
||||||
|
<button class="menu-btn" id="customizeBtn">
|
||||||
|
<span class="btn-icon">🎨</span> PERSONNALISER
|
||||||
|
</button>
|
||||||
<button class="menu-btn" id="scoresBtn">
|
<button class="menu-btn" id="scoresBtn">
|
||||||
<span class="btn-icon">🏆</span> CLASSEMENT
|
<span class="btn-icon">🏆</span> CLASSEMENT
|
||||||
</button>
|
</button>
|
||||||
@ -38,20 +41,21 @@
|
|||||||
<div class="game-header">
|
<div class="game-header">
|
||||||
<button class="back-btn" id="backToMenuBtn">← Menu</button>
|
<button class="back-btn" id="backToMenuBtn">← Menu</button>
|
||||||
<h2>OULVIC</h2>
|
<h2>OULVIC</h2>
|
||||||
|
<button class="fullscreen-btn" id="fullscreenBtn" title="Plein écran">⛶ Plein écran</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="user-input-section">
|
<div class="user-input-section">
|
||||||
<label for="username">Nom d'utilisateur:</label>
|
<label for="username">Nom d'utilisateur:</label>
|
||||||
<input type="text" id="username" placeholder="Entrez votre nom" maxlength="15">
|
<input type="text" id="username" placeholder="Entrez votre nom" maxlength="15">
|
||||||
<button id="confirmUsernameBtn" class="confirm-btn">✓ Confirmer</button>
|
<button id="confirmUsernameBtn" class="confirm-btn">✓ Confirmer</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="game-info">
|
<div class="game-info">
|
||||||
<div class="score">Score: <span id="score">0</span></div>
|
<div class="score">Score: <span id="score">0</span></div>
|
||||||
<div class="level">Niveau: <span id="level">1</span></div>
|
<div class="level">Niveau: <span id="level">1</span></div>
|
||||||
<div class="lives">Vies: <span id="lives"><span class="heart">♥</span><span class="heart">♥</span><span class="heart">♥</span></span></div>
|
<div class="lives">Vies: <span id="lives"><span class="heart">♥</span><span class="heart">♥</span><span class="heart">♥</span></span></div>
|
||||||
<div class="dots-remaining">Pastilles: <span id="dotsRemaining">0</span></div>
|
<div class="dots-remaining">Pastilles: <span id="dotsRemaining">0</span></div>
|
||||||
<div class="combo-display" id="comboDisplay" style="display: none;">Combo x<span id="comboValue">1</span></div>
|
<div class="combo-display" id="comboDisplay" style="display: none;">Combo x<span id="comboValue">1</span></div>
|
||||||
<div class="status" id="status">Prêt à jouer</div>
|
<div class="status" id="status">Prêt à jouer</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="power-ups-display" id="powerUpsDisplay">
|
<div class="power-ups-display" id="powerUpsDisplay">
|
||||||
<div class="power-up-item" id="speedBoostIndicator" style="display: none;">⚡ Vitesse</div>
|
<div class="power-up-item" id="speedBoostIndicator" style="display: none;">⚡ Vitesse</div>
|
||||||
<div class="power-up-item" id="shieldIndicator" style="display: none;">🛡 Bouclier</div>
|
<div class="power-up-item" id="shieldIndicator" style="display: none;">🛡 Bouclier</div>
|
||||||
@ -63,7 +67,7 @@
|
|||||||
<div class="pursuit-indicator" id="pursuitIndicator" style="display: none;">
|
<div class="pursuit-indicator" id="pursuitIndicator" style="display: none;">
|
||||||
<span class="pursuit-text">👻 Poursuite active: <span id="pursuitTimer">0</span>s</span>
|
<span class="pursuit-text">👻 Poursuite active: <span id="pursuitTimer">0</span>s</span>
|
||||||
</div>
|
</div>
|
||||||
<canvas id="gameCanvas" width="600" height="600"></canvas>
|
<canvas id="gameCanvas" width="750" height="750"></canvas>
|
||||||
|
|
||||||
<!-- Contrôles tactiles mobile -->
|
<!-- Contrôles tactiles mobile -->
|
||||||
<div class="mobile-controls" id="mobileControls">
|
<div class="mobile-controls" id="mobileControls">
|
||||||
@ -75,8 +79,8 @@
|
|||||||
<button class="ctrl-btn" data-dir="2">▼</button>
|
<button class="ctrl-btn" data-dir="2">▼</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="instructions">
|
<div class="instructions">
|
||||||
<p>Utilisez les flèches directionnelles pour déplacer Oulvic</p>
|
<p>Utilisez les flèches directionnelles pour déplacer Oulvic</p>
|
||||||
<button id="restartBtn" style="display: none;">REJOUER</button>
|
<button id="restartBtn" style="display: none;">REJOUER</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -86,23 +90,37 @@
|
|||||||
<h2 class="game-over-title">GAME OVER</h2>
|
<h2 class="game-over-title">GAME OVER</h2>
|
||||||
<p class="final-score">Score Final: <span id="finalScore">0</span></p>
|
<p class="final-score">Score Final: <span id="finalScore">0</span></p>
|
||||||
<p class="final-level">Niveau Atteint: <span id="finalLevel">1</span></p>
|
<p class="final-level">Niveau Atteint: <span id="finalLevel">1</span></p>
|
||||||
|
<div class="game-over-username-section">
|
||||||
|
<label for="gameOverUsername">Nom pour le classement:</label>
|
||||||
|
<input type="text" id="gameOverUsername" placeholder="Entrez votre nom" maxlength="15">
|
||||||
|
<button id="saveScoreBtn" class="save-score-btn">💾 Sauvegarder</button>
|
||||||
|
</div>
|
||||||
|
<p class="score-saved-msg" id="scoreSavedMsg" style="display: none; color: #00ff00; font-size: 0.7em; margin-top: 10px;">Score sauvegardé !</p>
|
||||||
<button id="overlayRestartBtn">REJOUER</button>
|
<button id="overlayRestartBtn">REJOUER</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- CLASSEMENT À DROITE (hors du container) -->
|
||||||
|
<div class="game-leaderboard" id="gameLeaderboard">
|
||||||
|
<div class="game-leaderboard-header">
|
||||||
|
<h3>🏆 Classement</h3>
|
||||||
|
</div>
|
||||||
|
<div id="gameLeaderboardContent" class="game-leaderboard-content"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- CLASSEMENT (caché par défaut) -->
|
<!-- CLASSEMENT (caché par défaut) -->
|
||||||
<div class="leaderboard-container" id="leaderboardContainer" style="display: none;">
|
<div class="leaderboard-container" id="leaderboardContainer" style="display: none;">
|
||||||
<div class="leaderboard-header">
|
<div class="leaderboard-header">
|
||||||
<h2>Classement</h2>
|
<h2>Classement</h2>
|
||||||
<button class="close-leaderboard-btn" id="closeLeaderboardBtn">✕</button>
|
<button class="close-leaderboard-btn" id="closeLeaderboardBtn">✕</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="leaderboard"></div>
|
<div id="leaderboard"></div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<footer>
|
</div>
|
||||||
<p>By Ludo and Syoul</p>
|
<footer>
|
||||||
</footer>
|
<p>By Ludo and Syoul</p>
|
||||||
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- MODAL CLASSEMENT (pour le menu) -->
|
<!-- MODAL CLASSEMENT (pour le menu) -->
|
||||||
@ -170,10 +188,20 @@
|
|||||||
<div class="rule-section">
|
<div class="rule-section">
|
||||||
<h3>👻 Fantômes</h3>
|
<h3>👻 Fantômes</h3>
|
||||||
<p>Évitez les fantômes ! Si vous les touchez, vous perdez une vie. Vous avez 3 vies au départ.</p>
|
<p>Évitez les fantômes ! Si vous les touchez, vous perdez une vie. Vous avez 3 vies au départ.</p>
|
||||||
|
<p><strong>Types de fantômes :</strong></p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>Niveau 1-2</strong> : 4 fantômes normaux</li>
|
<li><strong>👤 Fantôme Normal</strong> : Poursuit directement le joueur (Niveau 1+)</li>
|
||||||
<li><strong>Niveau 3-4</strong> : 5 fantômes (dont des chasseurs et patrouilleurs)</li>
|
<li><strong>🎯 Chasseur</strong> : Poursuit agressivement avec prédiction de mouvement (Niveau 2+)</li>
|
||||||
<li><strong>Niveau 5+</strong> : 6-7 fantômes (rapides, invisibles, etc.)</li>
|
<li><strong>🛡 Patrouilleur</strong> : Bloque les passages stratégiques et patrouille (Niveau 3+)</li>
|
||||||
|
<li><strong>⚡ Rapide</strong> : 1.5x plus rapide que les autres fantômes (Niveau 4+)</li>
|
||||||
|
<li><strong>👻 Invisible</strong> : Devient invisible 50% du temps, difficile à voir (Niveau 5+)</li>
|
||||||
|
</ul>
|
||||||
|
<p><strong>Nombre de fantômes par niveau :</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Niveau 1-2</strong> : 4 fantômes</li>
|
||||||
|
<li><strong>Niveau 3-4</strong> : 5 fantômes</li>
|
||||||
|
<li><strong>Niveau 5-9</strong> : 6 fantômes</li>
|
||||||
|
<li><strong>Niveau 10+</strong> : 7 fantômes</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -203,6 +231,73 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- MODAL PERSONNALISATION -->
|
||||||
|
<div class="customize-modal" id="customizeModal" style="display: none;">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2>🎨 PERSONNALISER VOTRE JOUEUR</h2>
|
||||||
|
<button class="close-modal-btn" id="closeCustomizeBtn">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="customize-content">
|
||||||
|
<div class="customize-section">
|
||||||
|
<h3>Couleur du joueur</h3>
|
||||||
|
<div class="color-options">
|
||||||
|
<div class="color-option" data-color="yellow" data-name="Jaune">
|
||||||
|
<div class="color-preview" style="background: #ffd700;"></div>
|
||||||
|
<span>Jaune (Classique)</span>
|
||||||
|
</div>
|
||||||
|
<div class="color-option" data-color="red" data-name="Rouge">
|
||||||
|
<div class="color-preview" style="background: #ff4444;"></div>
|
||||||
|
<span>Rouge</span>
|
||||||
|
</div>
|
||||||
|
<div class="color-option" data-color="blue" data-name="Bleu">
|
||||||
|
<div class="color-preview" style="background: #4444ff;"></div>
|
||||||
|
<span>Bleu</span>
|
||||||
|
</div>
|
||||||
|
<div class="color-option" data-color="green" data-name="Vert">
|
||||||
|
<div class="color-preview" style="background: #44ff44;"></div>
|
||||||
|
<span>Vert</span>
|
||||||
|
</div>
|
||||||
|
<div class="color-option" data-color="purple" data-name="Violet">
|
||||||
|
<div class="color-preview" style="background: #aa44ff;"></div>
|
||||||
|
<span>Violet</span>
|
||||||
|
</div>
|
||||||
|
<div class="color-option" data-color="orange" data-name="Orange">
|
||||||
|
<div class="color-preview" style="background: #ff8844;"></div>
|
||||||
|
<span>Orange</span>
|
||||||
|
</div>
|
||||||
|
<div class="color-option active" data-color="rainbow" data-name="Arc-en-ciel">
|
||||||
|
<div class="color-preview rainbow-preview"></div>
|
||||||
|
<span>Arc-en-ciel (Animé)</span>
|
||||||
|
</div>
|
||||||
|
<div class="color-option" data-color="pink" data-name="Rose">
|
||||||
|
<div class="color-preview" style="background: #ff44aa;"></div>
|
||||||
|
<span>Rose</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="customize-section">
|
||||||
|
<h3>Forme du joueur</h3>
|
||||||
|
<div class="shape-options">
|
||||||
|
<div class="shape-option active" data-shape="round" data-name="Rond">
|
||||||
|
<div class="shape-preview round-preview"></div>
|
||||||
|
<span>Rond (Classique)</span>
|
||||||
|
</div>
|
||||||
|
<div class="shape-option" data-shape="triangle" data-name="Triangle">
|
||||||
|
<div class="shape-preview triangle-preview"></div>
|
||||||
|
<span>Triangle</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="customize-preview">
|
||||||
|
<h3>Aperçu</h3>
|
||||||
|
<canvas id="playerPreviewCanvas" width="200" height="200"></canvas>
|
||||||
|
</div>
|
||||||
|
<button class="save-customize-btn" id="saveCustomizeBtn">💾 Sauvegarder</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="game.js"></script>
|
<script src="game.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
391
style.css
391
style.css
@ -19,6 +19,7 @@ body {
|
|||||||
padding: 20px;
|
padding: 20px;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='36' height='32' viewBox='0 0 36 32'%3E%3Cdefs%3E%3Cfilter id='glow'%3E%3CfeGaussianBlur stdDeviation='1.5' result='coloredBlur'/%3E%3CfeMerge%3E%3CfeMergeNode in='coloredBlur'/%3E%3CfeMergeNode in='SourceGraphic'/%3E%3C/feMerge%3E%3C/filter%3E%3ClinearGradient id='ghostGrad' x1='0%25' y1='0%25' x2='0%25' y2='100%25'%3E%3Cstop offset='0%25' style='stop-color:%23ff4444;stop-opacity:1' /%3E%3Cstop offset='100%25' style='stop-color:%23cc0000;stop-opacity:1' /%3E%3C/linearGradient%3E%3C/defs%3E%3Cpath d='M18 4 Q22 2 26 4 Q30 6 30 10 L30 20 Q30 24 28 26 Q26 28 24 28 Q22 30 20 28 Q18 30 16 28 Q14 30 12 28 Q10 30 8 28 Q6 30 4 28 Q2 26 2 22 L2 10 Q2 6 6 4 Q10 2 14 4 Q16 2 18 4 Z' fill='url(%23ghostGrad)' filter='url(%23glow)' stroke='%23ffffff' stroke-width='0.3'/%3E%3Cpath d='M8 22 Q6 24 8 26 Q9 24 8 22 M14 22 Q12 24 14 26 Q15 24 14 22 M20 22 Q18 24 20 26 Q21 24 20 22 M26 22 Q24 24 26 26 Q27 24 26 22' fill='url(%23ghostGrad)' filter='url(%23glow)'/%3E%3Ccircle cx='12' cy='10' r='3' fill='%23ffffff'/%3E%3Ccircle cx='24' cy='10' r='3' fill='%23ffffff'/%3E%3Ccircle cx='12' cy='10' r='1.8' fill='%23000000'/%3E%3Ccircle cx='24' cy='10' r='1.8' fill='%23000000'/%3E%3Cellipse cx='12.5' cy='9.5' rx='0.6' ry='0.8' fill='%23ffffff'/%3E%3Cellipse cx='24.5' cy='9.5' rx='0.6' ry='0.8' fill='%23ffffff'/%3E%3C/svg%3E") 18 16, auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
body::before {
|
body::before {
|
||||||
@ -143,6 +144,7 @@ body::after {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='36' height='32' viewBox='0 0 36 32'%3E%3Cdefs%3E%3Cfilter id='glow'%3E%3CfeGaussianBlur stdDeviation='1.5' result='coloredBlur'/%3E%3CfeMerge%3E%3CfeMergeNode in='coloredBlur'/%3E%3CfeMergeNode in='SourceGraphic'/%3E%3C/feMerge%3E%3C/filter%3E%3ClinearGradient id='ghostGrad' x1='0%25' y1='0%25' x2='0%25' y2='100%25'%3E%3Cstop offset='0%25' style='stop-color:%23ff4444;stop-opacity:1' /%3E%3Cstop offset='100%25' style='stop-color:%23cc0000;stop-opacity:1' /%3E%3C/linearGradient%3E%3C/defs%3E%3Cpath d='M18 4 Q22 2 26 4 Q30 6 30 10 L30 20 Q30 24 28 26 Q26 28 24 28 Q22 30 20 28 Q18 30 16 28 Q14 30 12 28 Q10 30 8 28 Q6 30 4 28 Q2 26 2 22 L2 10 Q2 6 6 4 Q10 2 14 4 Q16 2 18 4 Z' fill='url(%23ghostGrad)' filter='url(%23glow)' stroke='%23ffffff' stroke-width='0.3'/%3E%3Cpath d='M8 22 Q6 24 8 26 Q9 24 8 22 M14 22 Q12 24 14 26 Q15 24 14 22 M20 22 Q18 24 20 26 Q21 24 20 22 M26 22 Q24 24 26 26 Q27 24 26 22' fill='url(%23ghostGrad)' filter='url(%23glow)'/%3E%3Ccircle cx='12' cy='10' r='3' fill='%23ffffff'/%3E%3Ccircle cx='24' cy='10' r='3' fill='%23ffffff'/%3E%3Ccircle cx='12' cy='10' r='1.8' fill='%23000000'/%3E%3Ccircle cx='24' cy='10' r='1.8' fill='%23000000'/%3E%3Cellipse cx='12.5' cy='9.5' rx='0.6' ry='0.8' fill='%23ffffff'/%3E%3Cellipse cx='24.5' cy='9.5' rx='0.6' ry='0.8' fill='%23ffffff'/%3E%3C/svg%3E") 18 16, auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* === CANVAS ARRIERE-PLAN MENU === */
|
/* === CANVAS ARRIERE-PLAN MENU === */
|
||||||
@ -321,6 +323,26 @@ body::after {
|
|||||||
transform: translateX(-3px);
|
transform: translateX(-3px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fullscreen-btn {
|
||||||
|
padding: 10px 20px;
|
||||||
|
font-size: 0.7em;
|
||||||
|
font-family: 'Press Start 2P', cursive;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
color: #ffd700;
|
||||||
|
border: 2px solid #ffd700;
|
||||||
|
border-radius: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
line-height: 1.2;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-btn:hover {
|
||||||
|
background: rgba(255, 215, 0, 0.2);
|
||||||
|
transform: scale(1.1);
|
||||||
|
box-shadow: 0 0 10px rgba(255, 215, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
/* === MODAL CLASSEMENT === */
|
/* === MODAL CLASSEMENT === */
|
||||||
.leaderboard-modal {
|
.leaderboard-modal {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@ -512,7 +534,7 @@ body::after {
|
|||||||
0 10px 30px rgba(0, 0, 0, 0.8),
|
0 10px 30px rgba(0, 0, 0, 0.8),
|
||||||
0 0 50px rgba(255, 0, 0, 0.2),
|
0 0 50px rgba(255, 0, 0, 0.2),
|
||||||
inset 0 0 30px rgba(255, 215, 0, 0.05);
|
inset 0 0 30px rgba(255, 215, 0, 0.05);
|
||||||
flex-shrink: 0;
|
flex: 0 0 auto;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
border: 3px solid rgba(255, 215, 0, 0.4);
|
border: 3px solid rgba(255, 215, 0, 0.4);
|
||||||
@ -693,6 +715,14 @@ h1 {
|
|||||||
0 0 40px rgba(255, 215, 0, 0.3),
|
0 0 40px rgba(255, 215, 0, 0.3),
|
||||||
inset 0 0 60px rgba(0, 0, 255, 0.1);
|
inset 0 0 60px rgba(0, 0, 255, 0.1);
|
||||||
animation: canvasGlow 3s ease-in-out infinite alternate;
|
animation: canvasGlow 3s ease-in-out infinite alternate;
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 90vh;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
image-rendering: -moz-crisp-edges;
|
||||||
|
image-rendering: -webkit-crisp-edges;
|
||||||
|
image-rendering: pixelated;
|
||||||
|
image-rendering: crisp-edges;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* === INPUT UTILISATEUR === */
|
/* === INPUT UTILISATEUR === */
|
||||||
@ -911,6 +941,83 @@ h1 {
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* === CLASSEMENT DANS LE JEU (à droite) === */
|
||||||
|
.game-leaderboard {
|
||||||
|
background: rgba(0, 0, 0, 0.85);
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 15px;
|
||||||
|
border: 2px solid rgba(255, 215, 0, 0.4);
|
||||||
|
box-shadow:
|
||||||
|
0 5px 20px rgba(0, 0, 0, 0.6),
|
||||||
|
0 0 30px rgba(255, 0, 0, 0.1);
|
||||||
|
width: 250px;
|
||||||
|
max-height: 600px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-leaderboard-header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 2px solid rgba(255, 215, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-leaderboard-header h3 {
|
||||||
|
color: #ffd700;
|
||||||
|
font-size: 0.9em;
|
||||||
|
margin: 0;
|
||||||
|
text-shadow: 0 0 10px rgba(255, 215, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-leaderboard-content {
|
||||||
|
max-height: 550px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-leaderboard-content::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-leaderboard-content::-webkit-scrollbar-track {
|
||||||
|
background: rgba(255, 215, 0, 0.1);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-leaderboard-content::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(255, 215, 0, 0.5);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-leaderboard-content::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(255, 215, 0, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-leaderboard .leaderboard-item {
|
||||||
|
padding: 8px 10px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-size: 0.7em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-leaderboard .leaderboard-rank {
|
||||||
|
min-width: 40px;
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-leaderboard .leaderboard-name {
|
||||||
|
font-size: 0.65em;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-leaderboard .leaderboard-level {
|
||||||
|
min-width: 50px;
|
||||||
|
font-size: 0.65em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-leaderboard .leaderboard-score {
|
||||||
|
min-width: 60px;
|
||||||
|
font-size: 0.7em;
|
||||||
|
}
|
||||||
|
|
||||||
.empty-leaderboard {
|
.empty-leaderboard {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
@ -1016,6 +1123,71 @@ h1 {
|
|||||||
transform: translateY(0) scale(0.98);
|
transform: translateY(0) scale(0.98);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.game-over-username-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-over-username-section label {
|
||||||
|
font-size: 0.7em;
|
||||||
|
color: #ffd700;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gameOverUsername {
|
||||||
|
padding: 10px 15px;
|
||||||
|
font-size: 0.8em;
|
||||||
|
font-family: 'Press Start 2P', cursive;
|
||||||
|
border: 2px solid #ffd700;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
color: #fff;
|
||||||
|
outline: none;
|
||||||
|
max-width: 250px;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gameOverUsername:focus {
|
||||||
|
border-color: #ffed4e;
|
||||||
|
box-shadow: 0 0 15px rgba(255, 215, 0, 0.6);
|
||||||
|
background: rgba(0, 0, 0, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-score-btn {
|
||||||
|
padding: 10px 20px;
|
||||||
|
font-size: 0.7em;
|
||||||
|
font-family: 'Press Start 2P', cursive;
|
||||||
|
background: linear-gradient(180deg, #00ff00 0%, #00cc00 100%);
|
||||||
|
color: #000;
|
||||||
|
border: 2px solid #00ff00;
|
||||||
|
border-radius: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
box-shadow: 0 4px 10px rgba(0, 255, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-score-btn:hover {
|
||||||
|
background: linear-gradient(180deg, #00ff88 0%, #00ff00 100%);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 15px rgba(0, 255, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-score-btn:active {
|
||||||
|
transform: translateY(0) scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-score-btn:disabled {
|
||||||
|
background: rgba(100, 100, 100, 0.5);
|
||||||
|
border-color: #666;
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
/* === CONTROLES TACTILES MOBILE === */
|
/* === CONTROLES TACTILES MOBILE === */
|
||||||
.mobile-controls {
|
.mobile-controls {
|
||||||
display: none;
|
display: none;
|
||||||
@ -1069,6 +1241,12 @@ footer {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.game-leaderboard {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.leaderboard-container {
|
.leaderboard-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
@ -1155,3 +1333,214 @@ footer {
|
|||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* === MODAL PERSONNALISATION === */
|
||||||
|
.customize-modal {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0, 0, 0, 0.9);
|
||||||
|
z-index: 1000;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
animation: fadeIn 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customize-modal.active {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customize-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 30px;
|
||||||
|
max-width: 800px;
|
||||||
|
width: 90%;
|
||||||
|
max-height: 90vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customize-section {
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 15px;
|
||||||
|
border: 2px solid #ffd700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customize-section h3 {
|
||||||
|
color: #ffd700;
|
||||||
|
font-size: 1em;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-options {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||||
|
gap: 15px;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-option {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 15px;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
border: 2px solid rgba(255, 215, 0, 0.3);
|
||||||
|
border-radius: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-option:hover {
|
||||||
|
background: rgba(255, 215, 0, 0.1);
|
||||||
|
border-color: #ffd700;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-option.active {
|
||||||
|
background: rgba(255, 215, 0, 0.2);
|
||||||
|
border-color: #ffd700;
|
||||||
|
box-shadow: 0 0 15px rgba(255, 215, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-preview {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 3px solid #fff;
|
||||||
|
box-shadow: 0 0 10px rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rainbow-preview {
|
||||||
|
background: linear-gradient(45deg, #ff0000, #ff8800, #ffff00, #00ff00, #0088ff, #0000ff, #8800ff);
|
||||||
|
animation: rainbowRotate 3s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rainbowRotate {
|
||||||
|
0% { filter: hue-rotate(0deg); }
|
||||||
|
100% { filter: hue-rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-option span {
|
||||||
|
font-size: 0.7em;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customize-preview {
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 15px;
|
||||||
|
border: 2px solid #ffd700;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customize-preview h3 {
|
||||||
|
color: #ffd700;
|
||||||
|
font-size: 1em;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#playerPreviewCanvas {
|
||||||
|
border: 3px solid #ffd700;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #000;
|
||||||
|
box-shadow: 0 0 20px rgba(255, 215, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-customize-btn {
|
||||||
|
padding: 15px 30px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
font-family: 'Press Start 2P', cursive;
|
||||||
|
background: linear-gradient(180deg, #00ff00 0%, #00cc00 100%);
|
||||||
|
color: #000;
|
||||||
|
border: 2px solid #00ff00;
|
||||||
|
border-radius: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
box-shadow: 0 4px 10px rgba(0, 255, 0, 0.3);
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-customize-btn:hover {
|
||||||
|
background: linear-gradient(180deg, #00ff88 0%, #00ff00 100%);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 15px rgba(0, 255, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-customize-btn:active {
|
||||||
|
transform: translateY(0) scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
.shape-options {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||||
|
gap: 15px;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shape-option {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 15px;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
border: 2px solid rgba(255, 215, 0, 0.3);
|
||||||
|
border-radius: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shape-option:hover {
|
||||||
|
background: rgba(255, 215, 0, 0.1);
|
||||||
|
border-color: #ffd700;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.shape-option.active {
|
||||||
|
background: rgba(255, 215, 0, 0.2);
|
||||||
|
border-color: #ffd700;
|
||||||
|
box-shadow: 0 0 15px rgba(255, 215, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.shape-preview {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.round-preview {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #ffd700;
|
||||||
|
border: 3px solid #fff;
|
||||||
|
box-shadow: 0 0 10px rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.triangle-preview {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-left: 25px solid transparent;
|
||||||
|
border-right: 25px solid transparent;
|
||||||
|
border-bottom: 45px solid #ffd700;
|
||||||
|
border-top: none;
|
||||||
|
filter: drop-shadow(0 0 5px rgba(255, 255, 255, 0.3));
|
||||||
|
}
|
||||||
|
|
||||||
|
.shape-option span {
|
||||||
|
font-size: 0.7em;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user