// === MODULE FILL THE BLANK === class FillTheBlankGame { constructor(options) { this.container = options.container; this.content = options.content; this.onScoreUpdate = options.onScoreUpdate || (() => {}); this.onGameEnd = options.onGameEnd || (() => {}); // État du jeu this.score = 0; this.errors = 0; this.currentSentenceIndex = 0; this.isRunning = false; // Données de jeu this.sentences = this.extractSentences(this.content); this.currentSentence = null; this.blanks = []; this.userAnswers = []; this.init(); } init() { // Vérifier que nous avons des phrases if (!this.sentences || this.sentences.length === 0) { console.error('Aucune phrase disponible pour Fill the Blank'); this.showInitError(); return; } this.createGameBoard(); this.setupEventListeners(); // Le jeu démarrera quand start() sera appelé } showInitError() { this.container.innerHTML = `

❌ Erreur de chargement

Ce contenu ne contient pas de phrases compatibles avec Fill the Blank.

Le jeu nécessite des phrases avec leurs traductions.

`; } extractSentences(content) { let sentences = []; console.log('🔍 Extraction phrases depuis:', content?.name || 'contenu'); // Utiliser le contenu brut du module si disponible if (content.rawContent) { console.log('📦 Utilisation du contenu brut du module'); return this.extractSentencesFromRaw(content.rawContent); } // Format avec sentences array if (content.sentences && Array.isArray(content.sentences)) { console.log('📝 Format sentences détecté'); sentences = content.sentences.filter(sentence => sentence.english && sentence.english.trim() !== '' ); } // Format moderne avec contentItems else if (content.contentItems && Array.isArray(content.contentItems)) { console.log('🆕 Format contentItems détecté'); sentences = content.contentItems .filter(item => item.type === 'sentence' && item.english) .map(item => ({ english: item.english, french: item.french || item.translation, chinese: item.chinese })); } return this.finalizeSentences(sentences); } extractSentencesFromRaw(rawContent) { console.log('🔧 Extraction depuis contenu brut:', rawContent.name || 'Module'); let sentences = []; // Format simple (sentences array) if (rawContent.sentences && Array.isArray(rawContent.sentences)) { sentences = rawContent.sentences.filter(sentence => sentence.english && sentence.english.trim() !== '' ); console.log(`📝 ${sentences.length} phrases extraites depuis sentences array`); } // Format contentItems else if (rawContent.contentItems && Array.isArray(rawContent.contentItems)) { sentences = rawContent.contentItems .filter(item => item.type === 'sentence' && item.english) .map(item => ({ english: item.english, french: item.french || item.translation, chinese: item.chinese })); console.log(`🆕 ${sentences.length} phrases extraites depuis contentItems`); } return this.finalizeSentences(sentences); } finalizeSentences(sentences) { // Validation et nettoyage sentences = sentences.filter(sentence => sentence && typeof sentence.english === 'string' && sentence.english.trim() !== '' && sentence.english.split(' ').length >= 3 // Au moins 3 mots pour créer des blanks ); if (sentences.length === 0) { console.error('❌ Aucune phrase valide trouvée'); // Phrases de démonstration en dernier recours sentences = [ { english: "I am learning English.", chinese: "我正在学英语。" }, { english: "She goes to school every day.", chinese: "她每天都去学校。" }, { english: "We like to play games together.", chinese: "我们喜欢一起玩游戏。" } ]; console.warn('🚨 Utilisation de phrases de démonstration'); } // Mélanger les phrases sentences = this.shuffleArray(sentences); console.log(`✅ Fill the Blank: ${sentences.length} phrases finalisées`); return sentences; } createGameBoard() { this.container.innerHTML = `
${this.currentSentenceIndex + 1} / ${this.sentences.length}
${this.errors} Erreurs
${this.score} Score
Complète la phrase en remplissant les blancs !
`; } setupEventListeners() { document.getElementById('check-btn').addEventListener('click', () => this.checkAnswer()); document.getElementById('hint-btn').addEventListener('click', () => this.showHint()); document.getElementById('skip-btn').addEventListener('click', () => this.skipSentence()); // Enter key to check answer document.addEventListener('keydown', (e) => { if (e.key === 'Enter' && this.isRunning) { this.checkAnswer(); } }); } start() { console.log('🎮 Fill the Blank: Démarrage du jeu'); this.loadNextSentence(); } restart() { console.log('🔄 Fill the Blank: Redémarrage du jeu'); this.reset(); this.start(); } reset() { this.score = 0; this.errors = 0; this.currentSentenceIndex = 0; this.isRunning = false; this.currentSentence = null; this.blanks = []; this.userAnswers = []; this.onScoreUpdate(0); } loadNextSentence() { // Si on a fini toutes les phrases, recommencer depuis le début if (this.currentSentenceIndex >= this.sentences.length) { this.currentSentenceIndex = 0; this.sentences = this.shuffleArray(this.sentences); // Mélanger à nouveau this.showFeedback(`🎉 Toutes les phrases terminées ! On recommence avec un nouvel ordre.`, 'success'); setTimeout(() => { this.loadNextSentence(); }, 1500); return; } this.isRunning = true; this.currentSentence = this.sentences[this.currentSentenceIndex]; this.createBlanks(); this.displaySentence(); this.updateUI(); } createBlanks() { const words = this.currentSentence.english.split(' '); this.blanks = []; // Créer 1-3 blanks selon la longueur de la phrase const numBlanks = Math.min(Math.max(1, Math.floor(words.length / 4)), 3); const blankIndices = new Set(); // Sélectionner des mots aléatoires (pas les articles/prépositions courtes) const candidateWords = words.map((word, index) => ({ word, index })) .filter(item => item.word.length > 2 && !['the', 'and', 'but', 'for', 'nor', 'or', 'so', 'yet'].includes(item.word.toLowerCase())); // Si pas assez de candidats, prendre n'importe quels mots if (candidateWords.length < numBlanks) { candidateWords = words.map((word, index) => ({ word, index })); } // Sélectionner aléatoirement les indices des blanks const shuffledCandidates = this.shuffleArray(candidateWords); for (let i = 0; i < Math.min(numBlanks, shuffledCandidates.length); i++) { blankIndices.add(shuffledCandidates[i].index); } // Créer la structure des blanks words.forEach((word, index) => { if (blankIndices.has(index)) { this.blanks.push({ index: index, word: word.replace(/[.,!?;:]$/, ''), // Retirer la ponctuation punctuation: word.match(/[.,!?;:]$/) ? word.match(/[.,!?;:]$/)[0] : '', userAnswer: '' }); } }); } displaySentence() { const words = this.currentSentence.english.split(' '); let sentenceHTML = ''; let blankCounter = 0; words.forEach((word, index) => { const blank = this.blanks.find(b => b.index === index); if (blank) { sentenceHTML += ` ${blank.punctuation} `; blankCounter++; } else { sentenceHTML += `${word} `; } }); document.getElementById('sentence-container').innerHTML = sentenceHTML; // Afficher la traduction si disponible const translation = this.currentSentence.chinese || this.currentSentence.french || ''; document.getElementById('translation-hint').innerHTML = translation ? `💭 ${translation}` : ''; // Focus sur le premier input const firstInput = document.getElementById('blank-0'); if (firstInput) { setTimeout(() => firstInput.focus(), 100); } } checkAnswer() { if (!this.isRunning) return; let allCorrect = true; let correctCount = 0; // Vérifier chaque blank this.blanks.forEach((blank, index) => { const input = document.getElementById(`blank-${index}`); const userAnswer = input.value.trim().toLowerCase(); const correctAnswer = blank.word.toLowerCase(); blank.userAnswer = input.value.trim(); if (userAnswer === correctAnswer) { input.classList.remove('incorrect'); input.classList.add('correct'); correctCount++; } else { input.classList.remove('correct'); input.classList.add('incorrect'); allCorrect = false; } }); if (allCorrect) { // Toutes les réponses sont correctes this.score += 10 * this.blanks.length; this.showFeedback(`🎉 Parfait ! +${10 * this.blanks.length} points`, 'success'); setTimeout(() => { this.currentSentenceIndex++; this.loadNextSentence(); }, 1500); } else { // Quelques erreurs this.errors++; if (correctCount > 0) { this.score += 5 * correctCount; this.showFeedback(`✨ ${correctCount}/${this.blanks.length} correct ! +${5 * correctCount} points. Essaye encore.`, 'partial'); } else { this.showFeedback(`❌ Essaye encore ! (${this.errors} erreurs)`, 'error'); } } this.updateUI(); this.onScoreUpdate(this.score); } showHint() { // Afficher la première lettre de chaque blank vide this.blanks.forEach((blank, index) => { const input = document.getElementById(`blank-${index}`); if (!input.value.trim()) { input.value = blank.word[0]; input.focus(); } }); this.showFeedback('💡 Première lettre ajoutée !', 'info'); } skipSentence() { // Révéler les bonnes réponses this.blanks.forEach((blank, index) => { const input = document.getElementById(`blank-${index}`); input.value = blank.word; input.classList.add('revealed'); }); this.showFeedback('📖 Réponses révélées ! Phrase suivante...', 'info'); setTimeout(() => { this.currentSentenceIndex++; this.loadNextSentence(); }, 2000); } // Méthode endGame supprimée - le jeu continue indéfiniment showFeedback(message, type = 'info') { const feedbackArea = document.getElementById('feedback-area'); feedbackArea.innerHTML = `
${message}
`; } updateUI() { document.getElementById('current-question').textContent = this.currentSentenceIndex + 1; document.getElementById('errors-count').textContent = this.errors; document.getElementById('score-display').textContent = this.score; } shuffleArray(array) { const shuffled = [...array]; for (let i = shuffled.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; } return shuffled; } destroy() { this.isRunning = false; this.container.innerHTML = ''; } } // Enregistrement du module window.GameModules = window.GameModules || {}; window.GameModules.FillTheBlank = FillTheBlankGame;