// === CHARGEUR DE JEUX DYNAMIQUE === const GameLoader = { currentGame: null, contentScanner: new ContentScanner(), jsonLoader: new JSONContentLoader(), loadedModules: { games: {}, content: {} }, async loadGame(gameType, contentType) { try { // Nettoyage du jeu précédent this.cleanup(); // Chargement parallèle du module de jeu et du contenu const [gameModule, contentModule] = await Promise.all([ this.loadGameModule(gameType), this.loadContentModule(contentType) ]); // Initialisation du jeu this.initGame(gameType, gameModule, contentModule); } catch (error) { logSh(`❌ Erreur lors du chargement du jeu: ${error.message}`, 'ERROR'); logSh(`❌ Stack trace: ${error.stack}`, 'ERROR'); throw error; } }, async loadGameModule(gameType) { // Vérifier si le module est déjà chargé if (this.loadedModules.games[gameType]) { return this.loadedModules.games[gameType]; } try { // Chargement dynamique du script await this.loadScript(`js/games/${gameType}.js`); // Récupération du module depuis l'objet global const module = window.GameModules?.[this.getModuleName(gameType)]; if (!module) { throw new Error(`Module de jeu ${gameType} non trouvé`); } // Cache du module this.loadedModules.games[gameType] = module; return module; } catch (error) { logSh(`Erreur chargement module jeu ${gameType}:`, error, 'ERROR'); throw error; } }, async loadContentModule(contentType) { // Utiliser le ContentScanner pour récupérer le contenu découvert try { // Récupérer le contenu déjà découvert par le scanner const contentInfo = await this.contentScanner.getContentById(contentType); if (!contentInfo) { throw new Error(`Contenu ${contentType} non trouvé par le scanner`); } // Charger le module JavaScript correspondant await this.loadScript(`js/content/${contentInfo.filename}`); // Récupérer le module depuis l'objet global const moduleName = this.getContentModuleName(contentType); const rawModule = window.ContentModules?.[moduleName]; if (!rawModule) { throw new Error(`Module ${moduleName} non trouvé après chargement`); } // NOUVEAU: Utiliser le JSONContentLoader pour adapter le contenu ultra-modulaire const adaptedContent = this.jsonLoader.loadContent(rawModule); logSh(`🔄 Contenu adapté pour ${contentType}: ${adaptedContent._adapted ? 'JSON ultra-modulaire' : 'format legacy'}`, 'INFO'); // Combiner les informations du scanner avec le contenu adapté const enrichedContent = { ...adaptedContent, ...contentInfo, // S'assurer que le contenu brut du module est disponible rawContent: rawModule, adaptedContent: adaptedContent }; this.loadedModules.content[contentType] = enrichedContent; return enrichedContent; } catch (error) { logSh(`Erreur chargement contenu ${contentType}:`, error, 'ERROR'); // Fallback: essayer de charger directement le fichier JSON if (contentType.includes('json') || contentType.includes('ultra') || contentType.includes('exemple')) { try { logSh(`🔄 Tentative de chargement JSON direct pour ${contentType}...`, 'INFO'); const jsonResponse = await fetch(`${contentType}.json`); if (jsonResponse.ok) { const jsonContent = await jsonResponse.json(); const adaptedContent = this.jsonLoader.adapt(jsonContent); this.loadedModules.content[contentType] = adaptedContent; return adaptedContent; } } catch (jsonError) { logSh(`⚠️ Fallback JSON échoué: ${jsonError.message}`, 'WARN'); } } throw error; } }, loadScript(src) { return new Promise((resolve, reject) => { // Vérifier si le script est déjà chargé const existingScript = document.querySelector(`script[src="${src}"]`); if (existingScript) { resolve(); return; } const script = document.createElement('script'); script.src = src; script.onload = resolve; script.onerror = () => reject(new Error(`Impossible de charger ${src}`)); document.head.appendChild(script); }); }, initGame(gameType, GameClass, contentData) { logSh(`🎮 Initializing game: ${gameType}`, 'DEBUG'); try { const gameContainer = document.getElementById('game-container'); const gameTitle = document.getElementById('game-title'); const scoreDisplay = document.getElementById('current-score'); logSh('🎮 DOM elements found, adapting content...', 'DEBUG'); // Adapter le contenu avec le JSON Loader pour compatibilité avec les jeux const adaptedContent = this.jsonLoader.loadContent(contentData); logSh('🎮 Content adapted, updating UI...', 'DEBUG'); // Mise à jour du titre const contentName = adaptedContent.name || contentType; gameTitle.textContent = this.getGameTitle(gameType, contentName); // Réinitialisation du score scoreDisplay.textContent = '0'; logSh('🎮 Creating game instance...', 'DEBUG'); // Création de l'instance de jeu avec contenu enrichi this.currentGame = new GameClass({ container: gameContainer, content: adaptedContent, contentScanner: this.contentScanner, // Passer le scanner pour accès aux métadonnées onScoreUpdate: (score) => this.updateScore(score), onGameEnd: (finalScore) => this.handleGameEnd(finalScore) }); logSh('🎮 Game instance created successfully', 'DEBUG'); // Démarrage du jeu (seulement si la méthode start existe) if (typeof this.currentGame.start === 'function') { logSh('🎮 Starting game with start() method...', 'DEBUG'); this.currentGame.start(); } else { logSh('🎮 Game auto-started in constructor (no start() method)', 'DEBUG'); } logSh('✅ Game initialization completed successfully', 'DEBUG'); } catch (error) { logSh(`❌ Error in initGame: ${error.message}`, 'ERROR'); logSh(`❌ initGame stack: ${error.stack}`, 'ERROR'); throw error; } }, updateScore(score) { const scoreDisplay = document.getElementById('current-score'); scoreDisplay.textContent = score.toString(); // Animation du score Utils.animateElement(scoreDisplay, 'pulse', 200); }, handleGameEnd(finalScore) { // Sauvegarde du score this.saveScore(finalScore); // Affichage du résultat Utils.showToast(`Jeu terminé ! Score final: ${finalScore}`, 'success'); // Afficher les options de fin de jeu this.showGameEndOptions(finalScore); }, showGameEndOptions(finalScore) { const gameContainer = document.getElementById('game-container'); // Créer l'overlay de fin de jeu const endOverlay = document.createElement('div'); endOverlay.className = 'game-end-overlay'; endOverlay.innerHTML = `

🎉 Jeu Terminé !

Score final: ${finalScore}
Meilleur score: ${this.getBestScoreForCurrentGame()}
`; gameContainer.appendChild(endOverlay); // Ajouter les event listeners endOverlay.querySelector('.restart-game-btn').addEventListener('click', () => { this.removeGameEndOverlay(); this.restartCurrentGame(); }); endOverlay.querySelector('.back-to-levels-btn').addEventListener('click', () => { const params = Utils.getUrlParams(); AppNavigation.navigateTo('levels', params.game); }); endOverlay.querySelector('.back-to-games-btn').addEventListener('click', () => { AppNavigation.navigateTo('games'); }); // Fermer avec ESC const handleEscape = (e) => { if (e.key === 'Escape') { this.removeGameEndOverlay(); AppNavigation.goBack(); document.removeEventListener('keydown', handleEscape); } }; document.addEventListener('keydown', handleEscape); }, removeGameEndOverlay() { const overlay = document.querySelector('.game-end-overlay'); if (overlay) { overlay.remove(); } }, getBestScoreForCurrentGame() { const params = Utils.getUrlParams(); return this.getBestScore(params.game, params.content); }, restartCurrentGame() { if (this.currentGame && this.currentGame.restart) { this.currentGame.restart(); document.getElementById('current-score').textContent = '0'; } }, cleanup() { if (this.currentGame && this.currentGame.destroy) { this.currentGame.destroy(); } // Supprimer l'overlay de fin de jeu s'il existe this.removeGameEndOverlay(); const gameContainer = document.getElementById('game-container'); gameContainer.innerHTML = ''; this.currentGame = null; }, saveScore(score) { const params = Utils.getUrlParams(); const scoreKey = `score_${params.game}_${params.content}`; const currentScores = Utils.storage.get(scoreKey, []); currentScores.push({ score: score, date: new Date().toISOString(), timestamp: Date.now() }); // Garder seulement les 10 meilleurs scores currentScores.sort((a, b) => b.score - a.score); const bestScores = currentScores.slice(0, 10); Utils.storage.set(scoreKey, bestScores); }, getBestScore(gameType, contentType) { const scoreKey = `score_${gameType}_${contentType}`; const scores = Utils.storage.get(scoreKey, []); return scores.length > 0 ? scores[0].score : 0; }, // Utilitaires de nommage getModuleName(gameType) { const names = { 'whack-a-mole': 'WhackAMole', 'whack-a-mole-hard': 'WhackAMoleHard', 'memory-match': 'MemoryMatch', 'quiz-game': 'QuizGame', 'fill-the-blank': 'FillTheBlank', 'adventure-reader': 'AdventureReader', 'chinese-study': 'ChineseStudy', 'story-reader': 'StoryReader', 'grammar-discovery': 'GrammarDiscovery', 'word-storm': 'WordStorm', 'word-discovery': 'WordDiscovery', 'letter-discovery': 'LetterDiscovery', 'river-run': 'RiverRun', 'wizard-spell-caster': 'WizardSpellCaster' }; return names[gameType] || gameType; }, getContentModuleName(contentType) { // Utilise la même logique que le ContentScanner const mapping = { 'sbs-level-7-8-new': 'SBSLevel78New', 'sbs-level-1': 'SBSLevel1', 'basic-chinese': 'BasicChinese', 'english-class-demo': 'EnglishClassDemo', 'chinese-long-story': 'ChineseLongStory', 'french-beginner-story': 'FrenchBeginnerStory', 'wta1b1': 'WTA1B1', 'story-prototype-optimized': 'StoryPrototypeOptimized', 'test-minimal-content': 'TestMinimalContent', 'test-rich-content': 'TestRichContent', 'test-sentence-only': 'TestSentenceOnly', 'test-minimal': 'TestMinimal', 'test-rich': 'TestRich', 'nce1-lesson63-64': 'NCE1Lesson6364', 'nce2-lesson3': 'NCE2Lesson3', 'nce2-lesson30': 'NCE2Lesson30' }; return mapping[contentType] || this.toPascalCase(contentType); }, toPascalCase(str) { return str.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1) ).join(''); }, getGameTitle(gameType, contentName) { const gameNames = { 'whack-a-mole': 'Whack-a-Mole', 'whack-a-mole-hard': 'Whack-a-Mole Hard', 'memory-match': 'Memory Match', 'quiz-game': 'Quiz Game', 'fill-the-blank': 'Fill the Blank', 'adventure-reader': 'Adventure Reader' }; const gameName = gameNames[gameType] || gameType; return `${gameName} - ${contentName}`; }, // API pour les jeux createGameAPI() { return { showFeedback: (message, type = 'info') => Utils.showToast(message, type), playSound: (soundFile) => this.playSound(soundFile), updateScore: (score) => this.updateScore(score), endGame: (score) => this.handleGameEnd(score), getBestScore: () => { const params = Utils.getUrlParams(); return this.getBestScore(params.game, params.content); } }; }, playSound(soundFile) { if (Utils.canPlayAudio()) { try { const audio = new Audio(`assets/sounds/${soundFile}`); audio.volume = 0.5; audio.play().catch(e => logSh('Cannot play sound:', e, 'WARN')); } catch (error) { logSh('Sound error:', error, 'WARN'); } } } }; // Export global window.GameLoader = GameLoader;