// === MODULE FILL THE BLANK === class FillTheBlankGame { constructor(options) { this.container = options.container; this.content = options.content; this.onScoreUpdate = options.onScoreUpdate || (() => {}); this.onGameEnd = options.onGameEnd || (() => {}); // Game state this.score = 0; this.errors = 0; this.currentSentenceIndex = 0; this.isRunning = false; // Game data this.vocabulary = this.extractVocabulary(this.content); this.sentences = this.generateSentencesFromVocabulary(); this.currentSentence = null; this.blanks = []; this.userAnswers = []; this.init(); } init() { // Check that we have vocabulary if (!this.vocabulary || this.vocabulary.length === 0) { logSh('No vocabulary available for Fill the Blank', 'ERROR'); this.showInitError(); return; } this.createGameBoard(); this.setupEventListeners(); // The game will start when start() is called } showInitError() { this.container.innerHTML = `

โŒ Loading Error

This content does not contain vocabulary compatible with Fill the Blank.

The game requires words with their translations in ultra-modular format.

`; } extractVocabulary(content) { let vocabulary = []; logSh('๐Ÿ” Extracting vocabulary from:', content?.name || 'content', 'INFO'); // Priority 1: Use raw module content (ultra-modular format) if (content.rawContent) { logSh('๐Ÿ“ฆ Using raw module content', 'INFO'); return this.extractVocabularyFromRaw(content.rawContent); } // Priority 2: Ultra-modular format (vocabulary object) - ONLY format supported if (content.vocabulary && typeof content.vocabulary === 'object' && !Array.isArray(content.vocabulary)) { logSh('โœจ Ultra-modular format detected (vocabulary object)', 'INFO'); vocabulary = Object.entries(content.vocabulary).map(([word, data]) => { // Support ultra-modular format ONLY if (typeof data === 'object' && data.user_language) { return { original: word, // Clรฉ = original_language translation: data.user_language.split('๏ผ›')[0], // First translation fullTranslation: data.user_language, // Complete translation type: data.type || 'general', audio: data.audio, image: data.image, examples: data.examples, pronunciation: data.pronunciation, category: data.type || 'general' }; } // Legacy fallback - simple string (temporary, will be removed) else if (typeof data === 'string') { return { original: word, translation: data.split('๏ผ›')[0], fullTranslation: data, type: 'general', category: 'general' }; } return null; }).filter(Boolean); } // No other formats supported - ultra-modular only return this.finalizeVocabulary(vocabulary); } extractVocabularyFromRaw(rawContent) { logSh('๐Ÿ”ง Extracting from raw content:', rawContent.name || 'Module', 'INFO'); let vocabulary = []; // Ultra-modular format (vocabulary object) - ONLY format supported if (rawContent.vocabulary && typeof rawContent.vocabulary === 'object' && !Array.isArray(rawContent.vocabulary)) { vocabulary = Object.entries(rawContent.vocabulary).map(([word, data]) => { // Support ultra-modular format ONLY if (typeof data === 'object' && data.user_language) { return { original: word, // Clรฉ = original_language translation: data.user_language.split('๏ผ›')[0], // First translation fullTranslation: data.user_language, // Complete translation type: data.type || 'general', audio: data.audio, image: data.image, examples: data.examples, pronunciation: data.pronunciation, category: data.type || 'general' }; } // Legacy fallback - simple string (temporary, will be removed) else if (typeof data === 'string') { return { original: word, translation: data.split('๏ผ›')[0], fullTranslation: data, type: 'general', category: 'general' }; } return null; }).filter(Boolean); logSh(`โœจ ${vocabulary.length} words extracted from ultra-modular vocabulary`, 'INFO'); } // No other formats supported - ultra-modular only else { logSh('โš ๏ธ Content format not supported - ultra-modular format required', 'WARN'); } return this.finalizeVocabulary(vocabulary); } finalizeVocabulary(vocabulary) { // Validation and cleanup for ultra-modular format vocabulary = vocabulary.filter(word => word && typeof word.original === 'string' && typeof word.translation === 'string' && word.original.trim() !== '' && word.translation.trim() !== '' ); if (vocabulary.length === 0) { logSh('โŒ No valid vocabulary found', 'ERROR'); // Demo vocabulary as last resort vocabulary = [ { original: 'hello', translation: 'bonjour', category: 'greetings' }, { original: 'goodbye', translation: 'au revoir', category: 'greetings' }, { original: 'thank you', translation: 'merci', category: 'greetings' }, { original: 'cat', translation: 'chat', category: 'animals' }, { original: 'dog', translation: 'chien', category: 'animals' }, { original: 'house', translation: 'maison', category: 'objects' }, { original: 'school', translation: 'รฉcole', category: 'places' }, { original: 'book', translation: 'livre', category: 'objects' } ]; logSh('๐Ÿšจ Using demo vocabulary', 'WARN'); } logSh(`โœ… Fill the Blank: ${vocabulary.length} words finalized`, 'INFO'); return vocabulary; } generateSentencesFromVocabulary() { // Generate sentences based on word types const nounTemplates = [ { pattern: 'I see a {word}.', translation: 'Je vois un {translation}.' }, { pattern: 'The {word} is here.', translation: 'Le {translation} est ici.' }, { pattern: 'I like the {word}.', translation: 'J\'aime le {translation}.' }, { pattern: 'Where is the {word}?', translation: 'Oรน est le {translation}?' }, { pattern: 'This is a {word}.', translation: 'C\'est un {translation}.' }, { pattern: 'I have a {word}.', translation: 'J\'ai un {translation}.' } ]; const verbTemplates = [ { pattern: 'I {word} every day.', translation: 'Je {translation} tous les jours.' }, { pattern: 'We {word} together.', translation: 'Nous {translation} ensemble.' }, { pattern: 'They {word} quickly.', translation: 'Ils {translation} rapidement.' }, { pattern: 'I like to {word}.', translation: 'J\'aime {translation}.' } ]; const adjectiveTemplates = [ { pattern: 'The cat is {word}.', translation: 'Le chat est {translation}.' }, { pattern: 'This house is {word}.', translation: 'Cette maison est {translation}.' }, { pattern: 'I am {word}.', translation: 'Je suis {translation}.' }, { pattern: 'The weather is {word}.', translation: 'Le temps est {translation}.' } ]; let sentences = []; // Generate sentences for each vocabulary word based on type this.vocabulary.forEach(vocab => { let templates; // Choose templates based on word type if (vocab.type === 'verb') { templates = verbTemplates; } else if (vocab.type === 'adjective') { templates = adjectiveTemplates; } else { // Default to noun templates for nouns and unknown types templates = nounTemplates; } const template = templates[Math.floor(Math.random() * templates.length)]; const sentence = { original: template.pattern.replace('{word}', vocab.original), translation: template.translation.replace('{translation}', vocab.translation), targetWord: vocab.original, wordType: vocab.type || 'noun' }; // Ensure sentence has at least 3 words for blanks if (sentence.original.split(' ').length >= 3) { sentences.push(sentence); } }); // Shuffle and limit sentences sentences = this.shuffleArray(sentences); logSh(`โœ… Generated ${sentences.length} sentences from vocabulary`, 'INFO'); return sentences; } createGameBoard() { this.container.innerHTML = `
${this.currentSentenceIndex + 1} / ${this.sentences.length}
${this.errors} Errors
${this.score} Score
Complete the sentence by filling in the blanks!
`; } 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() { logSh('๐ŸŽฎ Fill the Blank: Starting game', 'INFO'); this.loadNextSentence(); } restart() { logSh('๐Ÿ”„ Fill the Blank: Restarting game', 'INFO'); 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() { // If we've finished all sentences, restart from the beginning if (this.currentSentenceIndex >= this.sentences.length) { this.currentSentenceIndex = 0; this.sentences = this.shuffleArray(this.sentences); // Shuffle again this.showFeedback(`๐ŸŽ‰ All sentences completed! Starting over with a new order.`, '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.original.split(' '); this.blanks = []; // Create 1-3 blanks depending on sentence length const numBlanks = Math.min(Math.max(1, Math.floor(words.length / 4)), 3); const blankIndices = new Set(); // Select random words (not articles/short prepositions) 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())); // If not enough candidates, take any words if (candidateWords.length < numBlanks) { candidateWords = words.map((word, index) => ({ word, index })); } // Randomly select blank indices const shuffledCandidates = this.shuffleArray(candidateWords); for (let i = 0; i < Math.min(numBlanks, shuffledCandidates.length); i++) { blankIndices.add(shuffledCandidates[i].index); } // Create blank structure words.forEach((word, index) => { if (blankIndices.has(index)) { this.blanks.push({ index: index, word: word.replace(/[.,!?;:]$/, ''), // Remove punctuation punctuation: word.match(/[.,!?;:]$/) ? word.match(/[.,!?;:]$/)[0] : '', userAnswer: '' }); } }); } displaySentence() { const words = this.currentSentence.original.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; // Display translation if available const translation = this.currentSentence.translation || ''; document.getElementById('translation-hint').innerHTML = translation ? `๐Ÿ’ญ ${translation}` : ''; // Focus on first input const firstInput = document.getElementById('blank-0'); if (firstInput) { setTimeout(() => firstInput.focus(), 100); } } checkAnswer() { if (!this.isRunning) return; let allCorrect = true; let correctCount = 0; // Check each 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) { // All answers are correct this.score += 10 * this.blanks.length; this.showFeedback(`๐ŸŽ‰ Perfect! +${10 * this.blanks.length} points`, 'success'); setTimeout(() => { this.currentSentenceIndex++; this.loadNextSentence(); }, 1500); } else { // Some errors this.errors++; if (correctCount > 0) { this.score += 5 * correctCount; this.showFeedback(`โœจ ${correctCount}/${this.blanks.length} correct! +${5 * correctCount} points. Try again.`, 'partial'); } else { this.showFeedback(`โŒ Try again! (${this.errors} errors)`, 'error'); } } this.updateUI(); this.onScoreUpdate(this.score); } showHint() { // Show first letter of each empty blank this.blanks.forEach((blank, index) => { const input = document.getElementById(`blank-${index}`); if (!input.value.trim()) { input.value = blank.word[0]; input.focus(); } }); this.showFeedback('๐Ÿ’ก First letter added!', 'info'); } skipSentence() { // Reveal correct answers this.blanks.forEach((blank, index) => { const input = document.getElementById(`blank-${index}`); input.value = blank.word; input.classList.add('revealed'); }); this.showFeedback('๐Ÿ“– Answers revealed! Next sentence...', 'info'); setTimeout(() => { this.currentSentenceIndex++; this.loadNextSentence(); }, 2000); } // endGame method removed - game continues indefinitely 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 = ''; } } // Module registration window.GameModules = window.GameModules || {}; window.GameModules.FillTheBlank = FillTheBlankGame;