/* ============================================ UTILS & CONFIGURATION ============================================ */ // ============================================ // PICTOGRAMMES DE DANGER (GHS) // ============================================ const PICTOGRAMMES = [ { id: 1, nom: "Gaz comprimé", filename: "1-gaz-comprime.png", texte: "Je suis sous pression", danger: "Danger physique", couleur: "teal" }, { id: 2, nom: "Exclamation", filename: "2-exclamation.png", texte: "Je nuis gravement à la santé", danger: "Danger pour la santé", couleur: "teal" }, { id: 3, nom: "Explosion", filename: "3-explosion.png", texte: "J'explose", danger: "Danger physique", couleur: "teal" }, { id: 4, nom: "Crâne", filename: "4-crane.png", texte: "Je tue", danger: "Danger physique", couleur: "teal" }, { id: 5, nom: "Flamme", filename: "5-flamme.png", texte: "Je flambe", danger: "Danger physique", couleur: "teal" }, { id: 6, nom: "Comburant", filename: "6-comburant.png", texte: "Je fais flamber", danger: "Danger physique", couleur: "teal" }, { id: 7, nom: "Environnement", filename: "7-environnement.png", texte: "Je pollue", danger: "Danger pour l'environnement", couleur: "teal" }, { id: 8, nom: "Corrosion", filename: "8-corrosion.png", texte: "Je ronge", danger: "Danger physique", couleur: "teal" }, { id: 9, nom: "Santé/Environnement", filename: "9-sante-environnement.png", 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(); } });