Déplacement de fichiers et cohérence des header d'exercices
This commit is contained in:
359
exercices/memory-pictogrammes/assets/js/game-state.js
Normal file
359
exercices/memory-pictogrammes/assets/js/game-state.js
Normal file
@@ -0,0 +1,359 @@
|
||||
/* ============================================
|
||||
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;
|
||||
Reference in New Issue
Block a user