// === MODULE QUIZ GAME === class QuizGame { constructor(options) { this.container = options.container; this.content = options.content; this.onScoreUpdate = options.onScoreUpdate || (() => {}); this.onGameEnd = options.onGameEnd || (() => {}); // Game state this.vocabulary = []; this.currentQuestion = 0; this.totalQuestions = 10; this.score = 0; this.correctAnswers = 0; this.currentQuestionData = null; this.hasAnswered = false; this.quizDirection = 'original_to_translation'; // 'original_to_translation' or 'translation_to_original' // Extract vocabulary and additional words from texts/stories this.vocabulary = this.extractVocabulary(this.content); this.allWords = this.extractAllWords(this.content); this.init(); } init() { // Check if we have enough vocabulary if (!this.vocabulary || this.vocabulary.length < 6) { logSh('Not enough vocabulary for Quiz Game', 'ERROR'); this.showInitError(); return; } // Adjust total questions based on available vocabulary this.totalQuestions = Math.min(this.totalQuestions, this.vocabulary.length); this.createGameInterface(); this.generateQuestion(); } showInitError() { this.container.innerHTML = `

❌ Error loading

This content doesn't have enough vocabulary for Quiz Game.

The game needs at least 6 vocabulary items.

`; } extractVocabulary(content) { let vocabulary = []; logSh('🔍 Extracting vocabulary from:', content?.name || 'content', 'INFO'); // Priority 1: Use raw module content (simple 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: 'car', translation: 'voiture', category: 'objects' }, { original: 'book', translation: 'livre', category: 'objects' } ]; logSh('🚨 Using demo vocabulary', 'WARN'); } // Shuffle vocabulary for random questions vocabulary = this.shuffleArray(vocabulary); logSh(`✅ Quiz Game: ${vocabulary.length} vocabulary words finalized`, 'INFO'); return vocabulary; } extractAllWords(content) { let allWords = []; // Add vocabulary words first allWords = [...this.vocabulary]; // Extract from stories/texts if (content.rawContent?.story?.chapters) { content.rawContent.story.chapters.forEach(chapter => { if (chapter.sentences) { chapter.sentences.forEach(sentence => { if (sentence.words && Array.isArray(sentence.words)) { sentence.words.forEach(wordObj => { if (wordObj.word && wordObj.translation) { allWords.push({ original: wordObj.word, translation: wordObj.translation, type: wordObj.type || 'word', pronunciation: wordObj.pronunciation }); } }); } }); } }); } // Extract from additional stories (like WTA1B1) if (content.rawContent?.additionalStories) { content.rawContent.additionalStories.forEach(story => { if (story.chapters) { story.chapters.forEach(chapter => { if (chapter.sentences) { chapter.sentences.forEach(sentence => { if (sentence.words && Array.isArray(sentence.words)) { sentence.words.forEach(wordObj => { if (wordObj.word && wordObj.translation) { allWords.push({ original: wordObj.word, translation: wordObj.translation, type: wordObj.type || 'word', pronunciation: wordObj.pronunciation }); } }); } }); } }); } }); } // Remove duplicates based on original word const uniqueWords = []; const seenWords = new Set(); allWords.forEach(word => { const key = word.original.toLowerCase(); if (!seenWords.has(key)) { seenWords.add(key); uniqueWords.push(word); } }); logSh(`📚 Extracted ${uniqueWords.length} total words for quiz options`, 'INFO'); return uniqueWords; } 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; } createGameInterface() { this.container.innerHTML = `
1 / ${this.totalQuestions} Score: 0
Loading question...
Choose the correct translation!
`; // Add CSS for top controls const style = document.createElement('style'); style.textContent = ` .quiz-top-controls { position: absolute; top: 10px; left: 10px; z-index: 10; } .restart-top { background: rgba(255, 255, 255, 0.9) !important; border: 2px solid #ccc !important; color: #666 !important; font-size: 12px !important; padding: 8px 12px !important; border-radius: 6px !important; box-shadow: 0 2px 4px rgba(0,0,0,0.1) !important; } .restart-top:hover { background: rgba(255, 255, 255, 1) !important; border-color: #999 !important; color: #333 !important; } `; document.head.appendChild(style); this.setupEventListeners(); } setupEventListeners() { document.getElementById('next-btn').addEventListener('click', () => this.nextQuestion()); document.getElementById('restart-btn').addEventListener('click', () => this.restart()); } generateQuestion() { if (this.currentQuestion >= this.totalQuestions) { this.gameComplete(); return; } this.hasAnswered = false; // Get current vocabulary item const correctAnswer = this.vocabulary[this.currentQuestion]; // Randomly choose quiz direction this.quizDirection = Math.random() < 0.5 ? 'original_to_translation' : 'translation_to_original'; let questionText, correctAnswerText, sourceForWrongAnswers; if (this.quizDirection === 'original_to_translation') { questionText = correctAnswer.original; correctAnswerText = correctAnswer.translation; sourceForWrongAnswers = 'translation'; } else { questionText = correctAnswer.translation; correctAnswerText = correctAnswer.original; sourceForWrongAnswers = 'original'; } // Generate 5 wrong answers from allWords (which includes story words) const availableWords = this.allWords.length >= 6 ? this.allWords : this.vocabulary; const wrongAnswers = availableWords .filter(item => item !== correctAnswer) .sort(() => Math.random() - 0.5) .slice(0, 5) .map(item => sourceForWrongAnswers === 'translation' ? item.translation : item.original); // Combine and shuffle all options (1 correct + 5 wrong = 6 total) const allOptions = [correctAnswerText, ...wrongAnswers].sort(() => Math.random() - 0.5); this.currentQuestionData = { question: questionText, correctAnswer: correctAnswerText, options: allOptions, direction: this.quizDirection }; this.renderQuestion(); this.updateProgress(); } renderQuestion() { const { question, options } = this.currentQuestionData; // Update question text with direction indicator const direction = this.currentQuestionData.direction; const directionText = direction === 'original_to_translation' ? 'What is the translation of' : 'What is the original word for'; document.getElementById('question-text').innerHTML = ` ${directionText} "${question}"? `; // Clear and generate options const optionsArea = document.getElementById('options-area'); optionsArea.innerHTML = ''; options.forEach((option, index) => { const optionButton = document.createElement('button'); optionButton.className = 'quiz-option'; optionButton.textContent = option; optionButton.addEventListener('click', () => this.selectAnswer(option, optionButton)); optionsArea.appendChild(optionButton); }); // Hide next button document.getElementById('next-btn').style.display = 'none'; } selectAnswer(selectedAnswer, buttonElement) { if (this.hasAnswered) return; this.hasAnswered = true; const isCorrect = selectedAnswer === this.currentQuestionData.correctAnswer; // Disable all option buttons and show results const allOptions = document.querySelectorAll('.quiz-option'); allOptions.forEach(btn => { btn.disabled = true; if (btn.textContent === this.currentQuestionData.correctAnswer) { btn.classList.add('correct'); } else if (btn === buttonElement && !isCorrect) { btn.classList.add('wrong'); } else if (btn !== buttonElement && btn.textContent !== this.currentQuestionData.correctAnswer) { btn.classList.add('disabled'); } }); // Update score and feedback if (isCorrect) { this.correctAnswers++; this.score += 10; this.showFeedback('✅ Correct! Well done!', 'success'); } else { this.score = Math.max(0, this.score - 5); this.showFeedback(`❌ Wrong! Correct answer: "${this.currentQuestionData.correctAnswer}"`, 'error'); } this.updateScore(); // Show next button or finish if (this.currentQuestion < this.totalQuestions - 1) { document.getElementById('next-btn').style.display = 'block'; } else { setTimeout(() => this.gameComplete(), 250); } } nextQuestion() { this.currentQuestion++; this.generateQuestion(); } updateProgress() { const progressFill = document.getElementById('progress-fill'); const progressPercent = ((this.currentQuestion + 1) / this.totalQuestions) * 100; progressFill.style.width = `${progressPercent}%`; document.getElementById('question-counter').textContent = `${this.currentQuestion + 1} / ${this.totalQuestions}`; } updateScore() { document.getElementById('score-display').textContent = `Score: ${this.score}`; this.onScoreUpdate(this.score); } gameComplete() { const accuracy = Math.round((this.correctAnswers / this.totalQuestions) * 100); // Bonus for high accuracy if (accuracy >= 90) { this.score += 50; // Excellence bonus } else if (accuracy >= 70) { this.score += 20; // Good performance bonus } this.updateScore(); this.showFeedback( `🎉 Quiz completed! ${this.correctAnswers}/${this.totalQuestions} correct (${accuracy}%)`, 'success' ); setTimeout(() => { this.onGameEnd(this.score); }, 3000); } showFeedback(message, type = 'info') { const feedbackArea = document.getElementById('feedback-area'); feedbackArea.innerHTML = `
${message}
`; } start() { logSh('❓ Quiz Game: Starting', 'INFO'); this.showFeedback('Choose the correct translation for each word!', 'info'); } restart() { logSh('🔄 Quiz Game: Restarting', 'INFO'); this.reset(); this.start(); } reset() { this.currentQuestion = 0; this.score = 0; this.correctAnswers = 0; this.hasAnswered = false; this.currentQuestionData = null; // Re-shuffle vocabulary this.vocabulary = this.shuffleArray(this.vocabulary); this.generateQuestion(); this.updateScore(); } destroy() { this.container.innerHTML = ''; } } // Module registration window.GameModules = window.GameModules || {}; window.GameModules.QuizGame = QuizGame;