Files
Site_web/exercices/memory-pictogrammes/assets/js/game-state.js

360 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ============================================
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;