Corrige les pictogrammes cassés (mauvais noms de fichiers dans utils.js) et le conflit de classe CSS .progress-bar avec le header qui cassait la barre de progression du jeu. Réorganise l'interface dans un panneau de jeu unifié, ajoute des icônes aux statistiques et au sélecteur de difficulté, et rend le dos des cartes plus ludique.
405 lines
10 KiB
JavaScript
405 lines
10 KiB
JavaScript
/* ============================================
|
|
UTILS & CONFIGURATION
|
|
============================================ */
|
|
|
|
// ============================================
|
|
// PICTOGRAMMES DE DANGER (GHS)
|
|
// ============================================
|
|
const PICTOGRAMMES = [
|
|
{
|
|
id: 1,
|
|
nom: "Gaz comprimé",
|
|
filename: "sous_pression.jpg",
|
|
texte: "Je suis sous pression",
|
|
danger: "Danger physique",
|
|
couleur: "teal"
|
|
},
|
|
{
|
|
id: 2,
|
|
nom: "Exclamation",
|
|
filename: "nocif_irritant_ozone.jpg",
|
|
texte: "Je nuis gravement à la santé",
|
|
danger: "Danger pour la santé",
|
|
couleur: "teal"
|
|
},
|
|
{
|
|
id: 3,
|
|
nom: "Explosion",
|
|
filename: "explosif.jpg",
|
|
texte: "J'explose",
|
|
danger: "Danger physique",
|
|
couleur: "teal"
|
|
},
|
|
{
|
|
id: 4,
|
|
nom: "Crâne",
|
|
filename: "toxicite_aigue.jpg",
|
|
texte: "Je tue",
|
|
danger: "Danger physique",
|
|
couleur: "teal"
|
|
},
|
|
{
|
|
id: 5,
|
|
nom: "Flamme",
|
|
filename: "inflammable.jpg",
|
|
texte: "Je flambe",
|
|
danger: "Danger physique",
|
|
couleur: "teal"
|
|
},
|
|
{
|
|
id: 6,
|
|
nom: "Comburant",
|
|
filename: "comburant.jpg",
|
|
texte: "Je fais flamber",
|
|
danger: "Danger physique",
|
|
couleur: "teal"
|
|
},
|
|
{
|
|
id: 7,
|
|
nom: "Environnement",
|
|
filename: "pollution.jpg",
|
|
texte: "Je pollue",
|
|
danger: "Danger pour l'environnement",
|
|
couleur: "teal"
|
|
},
|
|
{
|
|
id: 8,
|
|
nom: "Corrosion",
|
|
filename: "corrosif.jpg",
|
|
texte: "Je ronge",
|
|
danger: "Danger physique",
|
|
couleur: "teal"
|
|
},
|
|
{
|
|
id: 9,
|
|
nom: "Santé/Environnement",
|
|
filename: "cmr.jpg",
|
|
texte: "J'altère la santé ou la couche d'ozone",
|
|
danger: "Danger pour la santé/Environnement",
|
|
couleur: "teal"
|
|
}
|
|
];
|
|
|
|
// ============================================
|
|
// MODES DE DIFFICULTÉ
|
|
// ============================================
|
|
const DIFFICULTY_MODES = {
|
|
easy: {
|
|
name: "Facile",
|
|
paires: 4,
|
|
timeLimit: 180,
|
|
scoreMultiplier: 1,
|
|
description: "4 paires pour débuter",
|
|
color: "#4CAF50"
|
|
},
|
|
|
|
normal: {
|
|
name: "Normal",
|
|
paires: 9,
|
|
timeLimit: 300,
|
|
scoreMultiplier: 2,
|
|
description: "Tous les 9 pictogrammes",
|
|
color: "#1D9E75"
|
|
},
|
|
|
|
hard: {
|
|
name: "Difficile",
|
|
paires: 9,
|
|
timeLimit: 120,
|
|
scoreMultiplier: 4,
|
|
description: "Défi chronométré (2 min)",
|
|
color: "#D85A30"
|
|
}
|
|
};
|
|
|
|
// ============================================
|
|
// ACHIEVEMENTS
|
|
// ============================================
|
|
const ACHIEVEMENTS = {
|
|
perfectGame: {
|
|
id: 'perfect_game',
|
|
name: '🏅 Jeu Parfait',
|
|
icon: '🏅',
|
|
description: 'Trouvé toutes les paires sans erreur!',
|
|
condition: (moves, pairesTotal) => moves === pairesTotal
|
|
},
|
|
|
|
speedrun: {
|
|
id: 'speedrun',
|
|
name: '⚡ Vitesse Éclair',
|
|
icon: '⚡',
|
|
description: 'Complété en moins de 60 secondes!',
|
|
condition: (moves, time, pairesTotal, difficulty) => {
|
|
const threshold = DIFFICULTY_MODES[difficulty].timeLimit * 0.5;
|
|
return time < threshold;
|
|
}
|
|
},
|
|
|
|
fastHard: {
|
|
id: 'fast_hard',
|
|
name: '🔥 Mode Difficile Maître',
|
|
icon: '🔥',
|
|
description: 'Complété le mode difficile!',
|
|
condition: (moves, time, pairesTotal, difficulty) => difficulty === 'hard'
|
|
},
|
|
|
|
twoMinutes: {
|
|
id: 'two_minutes',
|
|
name: '⏱ Contre le Chrono',
|
|
icon: '⏱',
|
|
description: 'Terminé avant les 2 minutes!',
|
|
condition: (moves, time, pairesTotal, difficulty) => time < 120
|
|
},
|
|
|
|
noMistakes: {
|
|
id: 'no_mistakes',
|
|
name: '🎯 Tir Précis',
|
|
icon: '🎯',
|
|
description: '0 erreur dans le mode Normal!',
|
|
condition: (moves, time, pairesTotal, difficulty) => {
|
|
return difficulty === 'normal' && moves === pairesTotal;
|
|
}
|
|
}
|
|
};
|
|
|
|
// ============================================
|
|
// UTILITY FUNCTIONS
|
|
// ============================================
|
|
|
|
/**
|
|
* Générer un UUID
|
|
*/
|
|
function generateUUID() {
|
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
|
const r = Math.random() * 16 | 0;
|
|
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
|
return v.toString(16);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Formater le temps en mm:ss
|
|
*/
|
|
function formatTime(seconds) {
|
|
const minutes = Math.floor(seconds / 60);
|
|
const secs = seconds % 60;
|
|
return `${String(minutes).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
|
|
}
|
|
|
|
/**
|
|
* Formater un nombre avec séparateurs
|
|
*/
|
|
function formatNumber(num) {
|
|
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
}
|
|
|
|
/**
|
|
* Ajouter une classe temporaire
|
|
*/
|
|
function addTemporaryClass(element, className, duration = 300) {
|
|
element.classList.add(className);
|
|
setTimeout(() => {
|
|
element.classList.remove(className);
|
|
}, duration);
|
|
}
|
|
|
|
/**
|
|
* Obtenir l'ID utilisateur (avec localStorage)
|
|
*/
|
|
function getUserId() {
|
|
let userId = localStorage.getItem('nextgn_userId');
|
|
if (!userId) {
|
|
userId = generateUUID();
|
|
localStorage.setItem('nextgn_userId', userId);
|
|
}
|
|
return userId;
|
|
}
|
|
|
|
/**
|
|
* Obtenir le nom du joueur
|
|
*/
|
|
function getPlayerName() {
|
|
let playerName = localStorage.getItem('nextgn_playerName');
|
|
if (!playerName) {
|
|
playerName = 'Joueur ' + Math.floor(Math.random() * 9000 + 1000);
|
|
localStorage.setItem('nextgn_playerName', playerName);
|
|
}
|
|
return playerName;
|
|
}
|
|
|
|
/**
|
|
* Demander le nom du joueur (au premier lancement)
|
|
*/
|
|
function promptPlayerName() {
|
|
const stored = localStorage.getItem('nextgn_playerName');
|
|
if (stored) return stored;
|
|
|
|
const name = prompt('Quel est votre nom?', 'Joueur ' + Math.floor(Math.random() * 9000 + 1000));
|
|
if (name) {
|
|
localStorage.setItem('nextgn_playerName', name);
|
|
return name;
|
|
}
|
|
return 'Anonyme';
|
|
}
|
|
|
|
/**
|
|
* Obtenir la couleur d'une difficulté
|
|
*/
|
|
function getDifficultyColor(difficulty) {
|
|
return DIFFICULTY_MODES[difficulty]?.color || '#1D9E75';
|
|
}
|
|
|
|
/**
|
|
* Obtenir la couleur de badge difficulté
|
|
*/
|
|
function getDifficultyClass(difficulty) {
|
|
switch(difficulty) {
|
|
case 'easy': return 'easy';
|
|
case 'normal': return 'normal';
|
|
case 'hard': return 'hard';
|
|
default: return 'normal';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Comparer deux scores
|
|
*/
|
|
function compareScores(a, b) {
|
|
if (b.score !== a.score) {
|
|
return b.score - a.score;
|
|
}
|
|
return a.timeSeconds - b.timeSeconds;
|
|
}
|
|
|
|
/**
|
|
* Trier les scores par difficulty
|
|
*/
|
|
function sortByDifficulty(scores, difficulty) {
|
|
if (difficulty === 'all') {
|
|
return scores;
|
|
}
|
|
return scores.filter(s => s.difficulty === difficulty);
|
|
}
|
|
|
|
/**
|
|
* Obtenir le rang d'un score
|
|
*/
|
|
function getRank(index) {
|
|
if (index === 0) return '🥇';
|
|
if (index === 1) return '🥈';
|
|
if (index === 2) return '🥉';
|
|
return `${index + 1}`;
|
|
}
|
|
|
|
/**
|
|
* Vérifier si le score est dans le top 10
|
|
*/
|
|
function isTopScore(score, difficulty = 'all') {
|
|
const scores = LeaderboardManager.getLeaderboard(difficulty, 10);
|
|
return scores.some(s => s.score === score.score && s.userId === score.userId);
|
|
}
|
|
|
|
/**
|
|
* Formater la date d'un score
|
|
*/
|
|
function formatDate(isoDate) {
|
|
const date = new Date(isoDate);
|
|
const today = new Date();
|
|
const yesterday = new Date(today);
|
|
yesterday.setDate(yesterday.getDate() - 1);
|
|
|
|
if (date.toDateString() === today.toDateString()) {
|
|
return 'Aujourd\'hui';
|
|
} else if (date.toDateString() === yesterday.toDateString()) {
|
|
return 'Hier';
|
|
} else {
|
|
return date.toLocaleDateString('fr-FR', { day: 'numeric', month: 'short' });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Obtenir les statistiques d'un joueur
|
|
*/
|
|
function getPlayerStatistics(userId) {
|
|
const scores = LeaderboardManager.getAllScores().filter(s => s.userId === userId);
|
|
|
|
if (scores.length === 0) {
|
|
return {
|
|
totalGames: 0,
|
|
bestScore: 0,
|
|
averageScore: 0,
|
|
averageTime: 0,
|
|
bestTime: Infinity,
|
|
winRate: 0,
|
|
perfectGames: 0
|
|
};
|
|
}
|
|
|
|
const bestScore = Math.max(...scores.map(s => s.score), 0);
|
|
const totalScore = scores.reduce((a, b) => a + b.score, 0);
|
|
const totalTime = scores.reduce((a, b) => a + b.timeSeconds, 0);
|
|
const bestTime = Math.min(...scores.map(s => s.timeSeconds), Infinity);
|
|
const perfectGames = scores.filter(s => s.isPerfect).length;
|
|
const winRate = (perfectGames / scores.length) * 100;
|
|
|
|
return {
|
|
totalGames: scores.length,
|
|
bestScore: bestScore,
|
|
averageScore: Math.round(totalScore / scores.length),
|
|
averageTime: Math.round(totalTime / scores.length),
|
|
bestTime: bestTime === Infinity ? 0 : bestTime,
|
|
winRate: Math.round(winRate),
|
|
perfectGames: perfectGames
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Déboguer : afficher tous les scores en console
|
|
*/
|
|
function debugScores() {
|
|
console.table(LeaderboardManager.getAllScores());
|
|
}
|
|
|
|
/**
|
|
* Déboguer : nettoyer tous les scores
|
|
*/
|
|
function clearAllScores() {
|
|
if (confirm('Êtes-vous sûr de vouloir supprimer tous les scores?')) {
|
|
localStorage.removeItem('nextgn_leaderboard');
|
|
console.log('✓ Tous les scores ont été supprimés');
|
|
location.reload();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Mode développement
|
|
*/
|
|
const DEBUG = {
|
|
enabled: localStorage.getItem('nextgn_debug') === 'true',
|
|
|
|
toggle() {
|
|
this.enabled = !this.enabled;
|
|
localStorage.setItem('nextgn_debug', this.enabled);
|
|
console.log(`Debug mode: ${this.enabled ? 'ON' : 'OFF'}`);
|
|
},
|
|
|
|
log(...args) {
|
|
if (this.enabled) {
|
|
console.log('[DEBUG]', ...args);
|
|
}
|
|
},
|
|
|
|
table(data) {
|
|
if (this.enabled) {
|
|
console.table(data);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Activer le debug avec Ctrl+Shift+D
|
|
document.addEventListener('keydown', (e) => {
|
|
if (e.ctrlKey && e.shiftKey && e.key === 'D') {
|
|
DEBUG.toggle();
|
|
}
|
|
});
|