Déplacement de fichiers et cohérence des header d'exercices

This commit is contained in:
2026-06-17 12:50:22 +02:00
parent 4e18746b3d
commit 5f35ffa7f1
52 changed files with 537 additions and 7642 deletions

View 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;