Class_generator/js/core/content-game-compatibility.js
StillHammer dacc7e98a1 Implement Word Storm game and refactor CSS architecture
Major features:
- Add new Word Storm falling words vocabulary game
- Refactor CSS to modular architecture (global base + game injection)
- Fix template literal syntax errors causing loading failures
- Add comprehensive developer guidelines to prevent common mistakes

Technical changes:
- Word Storm: Complete game with falling words, scoring, levels, lives
- CSS Architecture: Move game-specific styles from global CSS to injectCSS()
- GameLoader: Add Word Storm mapping and improve error handling
- Navigation: Add Word Storm configuration
- Documentation: Add debugging guides and common pitfall prevention

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-19 01:55:08 +08:00

574 lines
20 KiB
JavaScript

// === 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;