/* ============================================ GAME STATE & SCORING ENGINE ============================================ */ /** * Moteur de scoring avancé * Calcule les points avec bonus temps et efficacité */ class ScoringEngine { constructor() { this.basePoints = 1000; this.difficultyMultiplier = 1; this.timeBonus = 0; this.efficiencyBonus = 0; } /** * Calculer le score final * Formule : basePoints × difficultyMultiplier + timeBonus + efficiencyBonus */ calculateFinalScore(moves, timeSeconds, pairesTotal, difficulty) { const mode = DIFFICULTY_MODES[difficulty]; this.difficultyMultiplier = mode.scoreMultiplier; // Bonus temps : points restants const timeRemaining = Math.max(0, mode.timeLimit - timeSeconds); this.timeBonus = Math.round((timeRemaining / mode.timeLimit) * 500); // Bonus efficacité : pénalité pour erreurs // Jeu parfait = pairesTotal mouvements (2 cartes par paire) const perfectMoves = pairesTotal; const efficiency = Math.max(0, 1 - ((moves - perfectMoves) / (perfectMoves * 2))); this.efficiencyBonus = Math.round(efficiency * 500); // Score final const baseScore = this.basePoints * this.difficultyMultiplier; const finalScore = Math.round(baseScore + this.timeBonus + this.efficiencyBonus); return { baseScore: baseScore, timeBonus: this.timeBonus, efficiencyBonus: this.efficiencyBonus, totalScore: finalScore, duration: timeSeconds, breakdown: { base: baseScore, temps: this.timeBonus, efficacite: this.efficiencyBonus, total: finalScore } }; } /** * Détecter les achievements */ detectAchievements(moves, timeSeconds, pairesTotal, difficulty) { const achievements = []; // 🏅 Jeu parfait (0 erreurs) if (moves === pairesTotal) { achievements.push({ id: 'perfect_game', name: '🏅 Jeu Parfait', icon: '🏅', description: 'Trouvé toutes les paires sans erreur!' }); } // ⚡ Vitesse extrême if (timeSeconds < DIFFICULTY_MODES[difficulty].timeLimit * 0.3) { achievements.push({ id: 'speedrun', name: '⚡ Vitesse Éclair', icon: '⚡', description: `Complété en ${timeSeconds}s!` }); } // 🔥 Difficile réussi if (difficulty === 'hard') { achievements.push({ id: 'hard_mode', name: '🔥 Maître du Mode Difficile', icon: '🔥', description: 'Défi chronométré réussi!' }); } // 🎯 Score parfait const perfectScore = 1000 * DIFFICULTY_MODES[difficulty].scoreMultiplier; if ((this.basePoints * this.difficultyMultiplier + this.timeBonus + this.efficiencyBonus) >= perfectScore + 900) { achievements.push({ id: 'perfect_score', name: '🎯 Score Parfait', icon: '🎯', description: 'Score quasi-maximal!' }); } // 🏃 Rapidité respectable if (timeSeconds < 60 && pairesTotal === 9) { achievements.push({ id: 'fast_completion', name: '🏃 Très Rapide', icon: '🏃', description: 'Moins d\'une minute!' }); } return achievements; } } /** * Manager pour le localStorage et persistance */ class StorageManager { static setItem(key, value, isSession = false) { try { const storage = isSession ? sessionStorage : localStorage; storage.setItem(key, JSON.stringify(value)); return true; } catch (e) { console.error('Storage error:', e); return false; } } static getItem(key, isSession = false) { try { const storage = isSession ? sessionStorage : localStorage; const item = storage.getItem(key); return item ? JSON.parse(item) : null; } catch (e) { console.error('Storage error:', e); return null; } } static removeItem(key, isSession = false) { try { const storage = isSession ? sessionStorage : localStorage; storage.removeItem(key); return true; } catch (e) { console.error('Storage error:', e); return false; } } static clear(isSession = false) { try { const storage = isSession ? sessionStorage : localStorage; storage.clear(); return true; } catch (e) { console.error('Storage error:', e); return false; } } } /** * Session state - données temporaires du jeu actuel */ class GameSessionState { constructor() { this.sessionId = generateUUID(); this.userId = getUserId(); this.playerName = getPlayerName(); this.startTime = null; this.endTime = null; this.difficulty = 'normal'; this.moves = 0; this.score = 0; this.matched = 0; this.isPaused = false; this.gameActive = false; } /** * Démarrer une nouvelle session */ startSession(difficulty) { this.sessionId = generateUUID(); this.startTime = Date.now(); this.difficulty = difficulty; this.moves = 0; this.score = 0; this.matched = 0; this.gameActive = true; this.isPaused = false; } /** * Terminer la session */ endSession() { this.endTime = Date.now(); this.gameActive = false; } /** * Obtenir la durée en secondes */ getDuration() { const end = this.endTime || Date.now(); return Math.floor((end - this.startTime) / 1000); } /** * Sérialiser pour transmission */ toJSON() { return { sessionId: this.sessionId, userId: this.userId, playerName: this.playerName, startTime: this.startTime, endTime: this.endTime, difficulty: this.difficulty, moves: this.moves, score: this.score, matched: this.matched, duration: this.getDuration() }; } } /** * Gestionnaire des notifications */ class NotificationManager { static show(message, type = 'info', duration = 3000) { // Créer la notification const notification = document.createElement('div'); notification.className = `notification notification-${type}`; notification.textContent = message; notification.style.cssText = ` position: fixed; bottom: 20px; right: 20px; background: ${this.getTypeColor(type)}; color: white; padding: 16px 24px; border-radius: 8px; font-family: 'Syne', sans-serif; font-size: 14px; font-weight: 600; z-index: 3000; animation: slideUp 0.3s ease-out; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); `; document.body.appendChild(notification); // Auto-remove après durée setTimeout(() => { notification.style.animation = 'fadeOut 0.3s ease-out'; setTimeout(() => notification.remove(), 300); }, duration); } static success(message) { this.show(message, 'success'); } static error(message) { this.show(message, 'error'); } static warning(message) { this.show(message, 'warning'); } static info(message) { this.show(message, 'info'); } static getTypeColor(type) { const colors = { success: '#4CAF50', error: '#F44336', warning: '#FF9800', info: '#2196F3' }; return colors[type] || colors.info; } } /** * Gestionnaire des préférences utilisateur */ class PreferencesManager { static KEY_PREFERENCES = 'nextgn_preferences'; static defaults = { soundEnabled: true, animationsEnabled: true, darkMode: false, showStats: true, notifications: true }; static get() { const saved = localStorage.getItem(this.KEY_PREFERENCES); return saved ? JSON.parse(saved) : this.defaults; } static set(preferences) { localStorage.setItem(this.KEY_PREFERENCES, JSON.stringify(preferences)); this.applyPreferences(preferences); } static update(key, value) { const prefs = this.get(); prefs[key] = value; this.set(prefs); } static applyPreferences(prefs) { // Appliquer le dark mode if (prefs.darkMode) { document.body.style.colorScheme = 'dark'; } else { document.body.style.colorScheme = 'light'; } // Désactiver les animations si demandé if (!prefs.animationsEnabled) { document.documentElement.style.setProperty('--animation-duration', '0ms'); } } static toggle(key) { const prefs = this.get(); prefs[key] = !prefs[key]; this.set(prefs); return prefs[key]; } } // ============================================ // INITIALISATION DES SINGLETONS // ============================================ // Créer une instance de session au chargement const gameSession = new GameSessionState(); // Appliquer les préférences utilisateur PreferencesManager.applyPreferences(PreferencesManager.get()); // Exporter pour utilisation globale window.ScoringEngine = ScoringEngine; window.StorageManager = StorageManager; window.GameSessionState = GameSessionState; window.NotificationManager = NotificationManager; window.PreferencesManager = PreferencesManager;