// === VÉRIFICATEUR DE COMPATIBILITÉ CONTENU-JEU === class ContentGameCompatibility { constructor() { this.compatibilityCache = new Map(); this.minimumScores = { 'whack-a-mole': 40, 'whack-a-mole-hard': 45, 'memory-match': 50, 'quiz-game': 30, 'fill-the-blank': 30, 'text-reader': 40, 'adventure-reader': 50, 'chinese-study': 35, 'story-builder': 35, 'story-reader': 40, 'word-storm': 15 }; } /** * Vérifie si un contenu est compatible avec un jeu * @param {Object} content - Le contenu à vérifier * @param {string} gameType - Le type de jeu * @returns {Object} - { compatible: boolean, score: number, reason: string, requirements: string[] } */ checkCompatibility(content, gameType) { // Utiliser le cache si disponible const cacheKey = `${content.id || content.name}_${gameType}`; if (this.compatibilityCache.has(cacheKey)) { return this.compatibilityCache.get(cacheKey); } let result; // Si le contenu a déjà une analyse de compatibilité (depuis ContentScanner) if (content.gameCompatibility && content.gameCompatibility[gameType]) { result = this.enrichCompatibilityInfo(content.gameCompatibility[gameType], gameType); } else { // Analyser manuellement result = this.analyzeCompatibility(content, gameType); } // Mettre en cache this.compatibilityCache.set(cacheKey, result); return result; } /** * Enrichit les informations de compatibilité existantes */ enrichCompatibilityInfo(existingCompat, gameType) { const minScore = this.minimumScores[gameType] || 30; return { compatible: existingCompat.score >= minScore, score: existingCompat.score, reason: existingCompat.reason || this.getDefaultReason(gameType), requirements: this.getGameRequirements(gameType), details: this.getDetailedAnalysis(existingCompat, gameType) }; } /** * Analyse manuelle de compatibilité si pas déjà calculée */ analyzeCompatibility(content, gameType) { const capabilities = this.analyzeContentCapabilities(content); const compatResult = this.calculateGameCompatibilityForType(capabilities, gameType); return { compatible: compatResult.compatible, score: compatResult.score, reason: compatResult.reason, requirements: this.getGameRequirements(gameType), details: this.getDetailedAnalysis(compatResult, gameType), capabilities: capabilities }; } /** * Analyse les capacités d'un contenu */ analyzeContentCapabilities(content) { return { hasVocabulary: this.hasContent(content, 'vocabulary'), hasSentences: this.hasContent(content, 'sentences'), hasGrammar: this.hasContent(content, 'grammar'), hasAudio: this.hasContent(content, 'audio'), hasDialogues: this.hasContent(content, 'dialogues'), hasExercises: this.hasExercises(content), hasFillInBlanks: this.hasContent(content, 'fillInBlanks'), hasCorrections: this.hasContent(content, 'corrections'), hasComprehension: this.hasContent(content, 'comprehension'), hasMatching: this.hasContent(content, 'matching'), // Compteurs vocabularyCount: this.countItems(content, 'vocabulary'), sentenceCount: this.countItems(content, 'sentences'), dialogueCount: this.countItems(content, 'dialogues'), grammarCount: this.countItems(content, 'grammar') }; } /** * Calcule la compatibilité pour un type de jeu spécifique */ calculateGameCompatibilityForType(capabilities, gameType) { switch (gameType) { case 'whack-a-mole': case 'whack-a-mole-hard': return this.calculateWhackAMoleCompat(capabilities, gameType === 'whack-a-mole-hard'); case 'memory-match': return this.calculateMemoryMatchCompat(capabilities); case 'quiz-game': return this.calculateQuizGameCompat(capabilities); case 'fill-the-blank': return this.calculateFillBlankCompat(capabilities); case 'text-reader': case 'story-reader': return this.calculateTextReaderCompat(capabilities); case 'adventure-reader': return this.calculateAdventureCompat(capabilities); case 'chinese-study': return this.calculateChineseStudyCompat(capabilities); case 'story-builder': return this.calculateStoryBuilderCompat(capabilities); case 'word-storm': return this.calculateWordStormCompat(capabilities); default: return { compatible: true, score: 50, reason: 'Jeu non spécifiquement analysé' }; } } // === CALCULS DE COMPATIBILITÉ SPÉCIFIQUES PAR JEU === calculateWhackAMoleCompat(capabilities, isHard = false) { let score = 0; const reasons = []; if (capabilities.hasVocabulary && capabilities.vocabularyCount >= 5) { score += 40; reasons.push(`${capabilities.vocabularyCount} mots de vocabulaire`); } else if (capabilities.vocabularyCount > 0) { score += 20; reasons.push(`${capabilities.vocabularyCount} mots (minimum recommandé: 5)`); } if (capabilities.hasSentences && capabilities.sentenceCount >= 3) { score += 30; reasons.push(`${capabilities.sentenceCount} phrases`); } else if (capabilities.sentenceCount > 0) { score += 15; reasons.push(`${capabilities.sentenceCount} phrases (minimum recommandé: 3)`); } if (capabilities.hasAudio) { score += 20; reasons.push('Fichiers audio disponibles'); } const minScore = isHard ? 45 : 40; const compatible = score >= minScore; return { compatible, score, reason: compatible ? `Compatible: ${reasons.join(', ')}` : `Incompatible (score: ${score}/${minScore}): Nécessite plus de vocabulaire ou phrases` }; } calculateMemoryMatchCompat(capabilities) { let score = 0; const reasons = []; if (capabilities.hasVocabulary && capabilities.vocabularyCount >= 4) { score += 50; reasons.push(`${capabilities.vocabularyCount} paires de vocabulaire`); } else { return { compatible: false, score: 0, reason: 'Nécessite au moins 4 mots de vocabulaire' }; } if (capabilities.hasAudio) { score += 30; reasons.push('Audio pour pronunciation'); } return { compatible: score >= 50, score, reason: `Compatible: ${reasons.join(', ')}` }; } calculateQuizGameCompat(capabilities) { let score = 0; const reasons = []; // Quiz est très flexible if (capabilities.hasVocabulary) { score += 30; reasons.push('Questions de vocabulaire'); } if (capabilities.hasGrammar) { score += 25; reasons.push('Questions de grammaire'); } if (capabilities.hasSentences) { score += 20; reasons.push('Questions sur les phrases'); } if (capabilities.hasExercises) { score += 45; reasons.push('Exercices intégrés'); } // Quiz fonctionne avec presque tout if (score === 0 && (capabilities.vocabularyCount > 0 || capabilities.sentenceCount > 0)) { score = 30; reasons.push('Contenu de base disponible'); } return { compatible: score >= 30, score, reason: `Compatible: ${reasons.join(', ')}` }; } calculateFillBlankCompat(capabilities) { let score = 0; const reasons = []; if (capabilities.hasFillInBlanks) { score += 70; reasons.push('Exercices à trous intégrés'); } else if (capabilities.hasSentences && capabilities.sentenceCount >= 3) { score += 30; reasons.push('Phrases pouvant être adaptées en exercices à trous'); } else { return { compatible: false, score: 0, reason: 'Nécessite des phrases ou exercices à trous' }; } return { compatible: score >= 30, score, reason: `Compatible: ${reasons.join(', ')}` }; } calculateTextReaderCompat(capabilities) { let score = 0; const reasons = []; if (capabilities.hasSentences && capabilities.sentenceCount >= 3) { score += 40; reasons.push(`${capabilities.sentenceCount} phrases à lire`); } if (capabilities.hasDialogues && capabilities.dialogueCount > 0) { score += 50; reasons.push(`${capabilities.dialogueCount} dialogues`); } if (capabilities.hasAudio) { score += 10; reasons.push('Audio disponible'); } return { compatible: score >= 40, score, reason: score >= 40 ? `Compatible: ${reasons.join(', ')}` : 'Nécessite des phrases ou dialogues à lire' }; } calculateAdventureCompat(capabilities) { let score = 0; const reasons = []; if (capabilities.hasDialogues && capabilities.dialogueCount > 0) { score += 60; reasons.push('Dialogues pour narration'); } if (capabilities.hasSentences && capabilities.sentenceCount >= 5) { score += 30; reasons.push('Contenu narratif suffisant'); } if (capabilities.hasVocabulary && capabilities.vocabularyCount >= 10) { score += 10; reasons.push('Vocabulaire riche'); } return { compatible: score >= 50, score, reason: score >= 50 ? `Compatible: ${reasons.join(', ')}` : 'Nécessite plus de dialogues et contenu narratif' }; } calculateChineseStudyCompat(capabilities) { let score = 0; const reasons = []; if (capabilities.hasVocabulary) { score += 35; reasons.push('Vocabulaire chinois'); } if (capabilities.hasSentences) { score += 25; reasons.push('Phrases chinoises'); } if (capabilities.hasAudio) { score += 40; reasons.push('Prononciation audio'); } return { compatible: score >= 35, score, reason: score >= 35 ? `Compatible: ${reasons.join(', ')}` : 'Optimisé pour contenu chinois' }; } calculateStoryBuilderCompat(capabilities) { let score = 0; const reasons = []; if (capabilities.hasDialogues) { score += 40; reasons.push('Dialogues pour construction'); } if (capabilities.hasSentences && capabilities.sentenceCount >= 5) { score += 35; reasons.push('Phrases pour séquences'); } if (capabilities.hasVocabulary && capabilities.vocabularyCount >= 8) { score += 25; reasons.push('Vocabulaire varié'); } return { compatible: score >= 35, score, reason: score >= 35 ? `Compatible: ${reasons.join(', ')}` : 'Nécessite contenu pour construction narrative' }; } calculateWordStormCompat(capabilities) { let score = 0; const reasons = []; // Word Storm nécessite principalement du vocabulaire if (capabilities.hasVocabulary && capabilities.vocabularyCount >= 5) { score += 60; reasons.push(`${capabilities.vocabularyCount} mots de vocabulaire`); } else if (capabilities.vocabularyCount > 0) { score += 30; reasons.push(`${capabilities.vocabularyCount} mots (peu mais suffisant)`); } // Bonus pour plus de vocabulaire if (capabilities.vocabularyCount >= 20) { score += 15; reasons.push('Vocabulaire riche'); } // Bonus si les mots ont des prononciations if (capabilities.hasPronunciation) { score += 10; reasons.push('Prononciations disponibles'); } // Word Storm peut fonctionner même avec peu de contenu if (score === 0 && (capabilities.hasSentences || capabilities.hasDialogues)) { score = 25; reasons.push('Peut extraire vocabulaire des phrases/dialogues'); } return { compatible: score >= 15, score, reason: score >= 15 ? `Compatible: ${reasons.join(', ')}` : 'Nécessite au moins quelques mots de vocabulaire' }; } // === UTILITAIRES === hasContent(content, type) { // Vérification standard const data = content[type] || content.rawContent?.[type] || content.adaptedContent?.[type]; if (data) { if (Array.isArray(data)) return data.length > 0; if (typeof data === 'object') return Object.keys(data).length > 0; return !!data; } // Support pour formats spéciaux if (type === 'sentences' && content.story?.chapters) { // Format story avec chapitres (comme Dragon's Pearl) return content.story.chapters.some(chapter => chapter.sentences && chapter.sentences.length > 0 ); } if (type === 'dialogues' && content.story?.chapters) { // Vérifier s'il y a du contenu narratif riche dans les stories return content.story.chapters.length > 1; // Multiple chapitres = contenu narratif } return false; } hasExercises(content) { return this.hasContent(content, 'exercises') || this.hasContent(content, 'fillInBlanks') || this.hasContent(content, 'corrections') || this.hasContent(content, 'comprehension') || this.hasContent(content, 'matching'); } countItems(content, type) { // Vérification standard const data = content[type] || content.rawContent?.[type] || content.adaptedContent?.[type]; if (data) { if (Array.isArray(data)) return data.length; if (typeof data === 'object') return Object.keys(data).length; } // Support pour formats spéciaux if (type === 'sentences' && content.story?.chapters) { // Compter toutes les phrases dans tous les chapitres return content.story.chapters.reduce((total, chapter) => total + (chapter.sentences ? chapter.sentences.length : 0), 0 ); } if (type === 'dialogues' && content.story?.chapters) { // Considérer chaque chapitre comme un "dialogue" narratif return content.story.chapters.length; } if (type === 'vocabulary') { // Vérifier d'abord le format standard const vocab = content.vocabulary || content.rawContent?.vocabulary || content.adaptedContent?.vocabulary; if (vocab) { if (Array.isArray(vocab)) return vocab.length; if (typeof vocab === 'object') return Object.keys(vocab).length; } } return 0; } getGameRequirements(gameType) { const requirements = { 'whack-a-mole': ['5+ mots de vocabulaire OU 3+ phrases', 'Contenu simple et répétitif'], 'whack-a-mole-hard': ['5+ mots de vocabulaire ET 3+ phrases', 'Contenu varié'], 'memory-match': ['4+ paires de vocabulaire', 'Idéalement avec images/audio'], 'quiz-game': ['Vocabulaire OU phrases OU exercices', 'Très flexible'], 'fill-the-blank': ['Phrases avec exercices à trous OU phrases simples', 'Contenu éducatif'], 'text-reader': ['3+ phrases OU dialogues', 'Contenu narratif'], 'adventure-reader': ['Dialogues + contenu narratif riche', 'Histoire cohérente'], 'chinese-study': ['Vocabulaire et phrases chinoises', 'Audio recommandé'], 'story-builder': ['Dialogues OU 5+ phrases', 'Vocabulaire varié'], 'story-reader': ['Textes à lire, dialogues recommandés', 'Contenu narratif'], 'word-storm': ['3+ mots de vocabulaire', 'Prononciations recommandées'] }; return requirements[gameType] || ['Contenu de base']; } getDefaultReason(gameType) { const reasons = { 'whack-a-mole': 'Jeu de rapidité nécessitant vocabulaire ou phrases', 'memory-match': 'Jeu de mémoire optimisé pour paires vocabulaire-traduction', 'quiz-game': 'Jeu polyvalent compatible avec la plupart des contenus', 'fill-the-blank': 'Exercices à trous nécessitant phrases structurées', 'text-reader': 'Lecture guidée nécessitant textes ou dialogues', 'adventure-reader': 'Aventure narrative nécessitant contenu riche', 'chinese-study': 'Optimisé pour apprentissage du chinois', 'story-builder': 'Construction narrative nécessitant éléments variés', 'story-reader': 'Lecture d\'histoires nécessitant contenu narratif' }; return reasons[gameType] || 'Compatibilité non évaluée spécifiquement'; } getDetailedAnalysis(compatResult, gameType) { return { minimumScore: this.minimumScores[gameType] || 30, actualScore: compatResult.score, recommendation: compatResult.score >= (this.minimumScores[gameType] || 30) ? 'Fortement recommandé' : compatResult.score >= (this.minimumScores[gameType] || 30) * 0.7 ? 'Compatible avec limitations' : 'Non recommandé' }; } /** * Filtre une liste de contenus pour un jeu spécifique * @param {Array} contentList - Liste des contenus * @param {string} gameType - Type de jeu * @returns {Array} - Contenus compatibles triés par score */ filterCompatibleContent(contentList, gameType) { return contentList .map(content => ({ ...content, compatibility: this.checkCompatibility(content, gameType) })) .filter(content => content.compatibility.compatible) .sort((a, b) => b.compatibility.score - a.compatibility.score); } /** * Obtient des suggestions d'amélioration pour rendre un contenu compatible * @param {Object} content - Le contenu à analyser * @param {string} gameType - Le type de jeu * @returns {Array} - Liste de suggestions */ getImprovementSuggestions(content, gameType) { const compatibility = this.checkCompatibility(content, gameType); if (compatibility.compatible) return []; const suggestions = []; const capabilities = compatibility.capabilities || this.analyzeContentCapabilities(content); switch (gameType) { case 'whack-a-mole': case 'whack-a-mole-hard': if (capabilities.vocabularyCount < 5) { suggestions.push(`Ajouter ${5 - capabilities.vocabularyCount} mots de vocabulaire supplémentaires`); } if (capabilities.sentenceCount < 3) { suggestions.push(`Ajouter ${3 - capabilities.sentenceCount} phrases supplémentaires`); } break; case 'memory-match': if (capabilities.vocabularyCount < 4) { suggestions.push(`Ajouter ${4 - capabilities.vocabularyCount} paires de vocabulaire supplémentaires`); } break; // Autres cas... } if (suggestions.length === 0) { suggestions.push('Enrichir le contenu général du module'); } return suggestions; } /** * Vide le cache de compatibilité */ clearCache() { this.compatibilityCache.clear(); } } // Export global window.ContentGameCompatibility = ContentGameCompatibility;