Files
Site_web/exercices/memory-pictogrammes/assets/js/utils.js
gauthier.chombart 2a85ced1e6 Passe le classement Memory en ligne (ASP.NET + SQL Server)
Le classement du jeu Memory était stocké en localStorage (visible d'un seul
joueur, sur un seul poste). Il est désormais partagé en ligne via une API
ASP.NET/SQL Server adaptée à l'hébergement bsite.net.

- API scores.ashx (C#, compilée au vol par IIS) : lecture GET et
  enregistrement POST, création automatique de la table, ne conserve que le
  top 10 de chaque mode (facile / normal / difficile).
- leaderboard.js : le classement passe par fetch vers l'API (états
  chargement/erreur/vide, surbrillance de la ligne du joueur). Les
  statistiques personnelles restent stockées localement.
- memory-game.js : rafraîchit le classement après l'enregistrement (async).
- Style .is-me pour la ligne du joueur courant.
- web.config.example fourni ; le web.config réel (mot de passe) est ignoré.
2026-07-15 22:35:24 +02:00

373 lines
9.1 KiB
JavaScript

/* ============================================
UTILS & CONFIGURATION
============================================ */
// ============================================
// PICTOGRAMMES DE DANGER (GHS)
// ============================================
const PICTOGRAMMES = [
{
id: 1,
nom: "Gaz comprimé",
filename: "sous_pression.jpg",
texte: "Je suis sous pression",
danger: "Danger physique",
couleur: "teal"
},
{
id: 2,
nom: "Exclamation",
filename: "nocif_irritant_ozone.jpg",
texte: "J'altère la santé ou la couche d'ozone",
danger: "Danger pour la santé/Environnement",
couleur: "teal"
},
{
id: 3,
nom: "Explosion",
filename: "explosif.jpg",
texte: "J'explose",
danger: "Danger physique",
couleur: "teal"
},
{
id: 4,
nom: "Crâne",
filename: "toxicite_aigue.jpg",
texte: "Je tue",
danger: "Danger physique",
couleur: "teal"
},
{
id: 5,
nom: "Flamme",
filename: "inflammable.jpg",
texte: "Je flambe",
danger: "Danger physique",
couleur: "teal"
},
{
id: 6,
nom: "Comburant",
filename: "comburant.jpg",
texte: "Je fais flamber",
danger: "Danger physique",
couleur: "teal"
},
{
id: 7,
nom: "Environnement",
filename: "pollution.jpg",
texte: "Je pollue",
danger: "Danger pour l'environnement",
couleur: "teal"
},
{
id: 8,
nom: "Corrosion",
filename: "corrosif.jpg",
texte: "Je ronge",
danger: "Danger physique",
couleur: "teal"
},
{
id: 9,
nom: "Santé (CMR)",
filename: "cmr.jpg",
texte: "Je nuis gravement à la santé",
danger: "Danger pour la santé",
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 (classement en ligne, asynchrone)
*/
async function isTopScore(score, difficulty = 'all') {
const scores = await 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 locales du joueur
*/
function getPlayerStatistics() {
return LeaderboardManager.getPlayerStats();
}
/**
* Déboguer : afficher l'historique local du joueur en console
*/
function debugScores() {
console.table(LeaderboardManager._getLocalHistory());
}
/**
* Déboguer : effacer les statistiques locales du joueur
* (le classement en ligne partagé n'est pas affecté)
*/
function clearAllScores() {
LeaderboardManager.clearLocalHistory();
}
/**
* 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();
}
});