class WordDiscovery { constructor({ container, content, onScoreUpdate, onGameEnd }) { this.container = container; this.content = content; this.onScoreUpdate = onScoreUpdate; this.onGameEnd = onGameEnd; // Expose content globally for SettingsManager TTS language detection window.currentGameContent = content; this.currentWordIndex = 0; this.discoveredWords = []; this.currentPhase = 'discovery'; // discovery, practice this.score = 0; this.lives = 3; this.wordsToLearn = []; // Practice system - Global practice after all words discovered this.practiceLevel = 1; // 1=Easy, 2=Medium, 3=Hard, 4=Expert this.practiceRound = 0; this.maxPracticeRounds = 6; // More rounds for mixed practice this.practiceCorrectAnswers = 0; this.practiceErrors = 0; this.currentPracticeWords = []; // Mixed selection of all discovered words this.injectCSS(); this.extractContent(); this.init(); } injectCSS() { if (document.getElementById('word-discovery-styles')) return; const styleSheet = document.createElement('style'); styleSheet.id = 'word-discovery-styles'; styleSheet.textContent = ` .word-discovery-wrapper { display: flex; flex-direction: column; height: 100%; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; } .discovery-hud { display: flex; justify-content: space-between; align-items: center; padding: 15px 20px; background: rgba(0,0,0,0.2); backdrop-filter: blur(10px); } .discovery-progress { display: flex; align-items: center; gap: 15px; } .progress-bar { width: 200px; height: 8px; background: rgba(255,255,255,0.2); border-radius: 4px; overflow: hidden; } .progress-fill { height: 100%; background: linear-gradient(90deg, #00ff88, #00cc6a); transition: width 0.3s ease; } .discovery-main { flex: 1; display: flex; flex-direction: column; justify-content: center; align-items: center; padding: 20px; position: relative; } .word-card { background: white; border-radius: 20px; padding: 40px; box-shadow: 0 20px 40px rgba(0,0,0,0.3); text-align: center; max-width: 500px; width: 100%; color: #333; transform: scale(0.9); opacity: 0; animation: cardAppear 0.5s ease forwards; } @keyframes cardAppear { to { transform: scale(1); opacity: 1; } } .word-image { width: 200px; height: 200px; object-fit: cover; border-radius: 15px; margin-bottom: 20px; box-shadow: 0 10px 20px rgba(0,0,0,0.2); } .word-text { font-size: 2.5em; font-weight: bold; color: #2c3e50; margin-bottom: 10px; } .word-pronunciation { font-size: 1.2em; color: #7f8c8d; margin-bottom: 15px; font-style: italic; } .word-translation { font-size: 1.8em; color: #e74c3c; font-weight: 600; margin-bottom: 20px; } .discovery-controls { display: flex; gap: 15px; margin-top: 20px; } .discovery-btn { padding: 12px 25px; border: none; border-radius: 25px; font-size: 1.1em; font-weight: 600; cursor: pointer; transition: all 0.3s ease; min-width: 120px; } .btn-primary { background: linear-gradient(45deg, #667eea, #764ba2); color: white; } .btn-secondary { background: linear-gradient(45deg, #f093fb, #f5576c); color: white; } .btn-success { background: linear-gradient(45deg, #4facfe, #00f2fe); color: white; } .discovery-btn:hover { transform: translateY(-2px); box-shadow: 0 10px 20px rgba(0,0,0,0.2); } .association-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 30px; max-width: 800px; width: 100%; } .association-item { background: white; border-radius: 15px; padding: 20px; text-align: center; cursor: pointer; transition: all 0.3s ease; color: #333; border: 3px solid transparent; } .association-item:hover { transform: translateY(-5px); box-shadow: 0 15px 30px rgba(0,0,0,0.2); } .association-item.selected { border-color: #667eea; background: #f8f9ff; } .association-item.correct { border-color: #00ff88; background: #f0fff4; } .association-item.incorrect { border-color: #ff4757; background: #fff0f0; } .association-image { width: 120px; height: 120px; object-fit: cover; border-radius: 10px; margin-bottom: 15px; } .association-text { font-size: 1.4em; font-weight: 600; } .phase-indicator { position: absolute; top: 20px; left: 20px; background: rgba(255,255,255,0.2); padding: 8px 16px; border-radius: 20px; font-weight: 600; backdrop-filter: blur(10px); } .practice-progress { position: absolute; top: 20px; right: 20px; background: rgba(255,255,255,0.2); padding: 8px 16px; border-radius: 20px; font-weight: 600; backdrop-filter: blur(10px); font-size: 0.9em; } .difficulty-badge { display: inline-block; padding: 4px 12px; border-radius: 15px; font-size: 0.8em; font-weight: bold; margin-left: 10px; } .difficulty-easy { background: #4CAF50; color: white; } .difficulty-medium { background: #FF9800; color: white; } .difficulty-hard { background: #F44336; color: white; } .difficulty-expert { background: #9C27B0; color: white; } .practice-grid-6 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; max-width: 900px; width: 100%; } .practice-grid-8 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px; max-width: 1000px; width: 100%; } .practice-challenge { text-align: center; margin-bottom: 30px; padding: 20px; background: rgba(255,255,255,0.1); border-radius: 15px; backdrop-filter: blur(10px); } .challenge-timer { font-size: 2em; font-weight: bold; color: #FFD700; margin-bottom: 10px; } .challenge-text { font-size: 1.2em; margin-bottom: 15px; } .practice-stats { display: flex; justify-content: space-around; margin-top: 20px; font-size: 1.1em; } .stat-item { text-align: center; padding: 10px; background: rgba(255,255,255,0.1); border-radius: 10px; backdrop-filter: blur(5px); } .association-item.time-pressure { animation: timePressure 0.5s ease-in-out infinite alternate; } @keyframes timePressure { from { box-shadow: 0 0 10px rgba(255,215,0,0.5); } to { box-shadow: 0 0 20px rgba(255,215,0,0.8); } } .audio-btn { background: none; border: none; font-size: 2em; cursor: pointer; color: #667eea; margin-left: 10px; transition: all 0.3s ease; } .audio-btn:hover { transform: scale(1.2); color: #764ba2; } .completion-message { text-align: center; padding: 40px; background: rgba(255,255,255,0.1); border-radius: 20px; backdrop-filter: blur(10px); } .completion-title { font-size: 2.5em; margin-bottom: 20px; color: #00ff88; } .completion-stats { font-size: 1.3em; margin-bottom: 30px; line-height: 1.6; } .content-warning { background: rgba(255, 193, 7, 0.2); border: 2px solid #FFC107; border-radius: 10px; padding: 15px; margin: 20px 0; color: #856404; font-size: 0.9em; } .feature-missing { opacity: 0.6; position: relative; } .feature-missing::after { content: '📵'; position: absolute; top: 5px; right: 5px; font-size: 0.8em; } `; document.head.appendChild(styleSheet); } extractContent() { if (!this.content || !this.content.vocabulary) { this.wordsToLearn = []; return; } this.wordsToLearn = Object.entries(this.content.vocabulary).map(([word, data]) => ({ word: word, translation: typeof data === 'string' ? data : data.translation, pronunciation: typeof data === 'object' ? data.pronunciation : null, image: typeof data === 'object' ? data.image : null, type: typeof data === 'object' ? data.type : 'word' })).filter(item => item.translation); // Shuffle words for variety this.wordsToLearn = this.shuffleArray([...this.wordsToLearn]); } shuffleArray(array) { for (let i = array.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; } return array; } init() { this.container.innerHTML = `
Progress:
0/${this.wordsToLearn.length}
Score: 0 ❤️ 3
Discovery Phase
`; if (this.wordsToLearn.length === 0) { this.showNoContent(); return; } this.updateHUD(); this.startDiscoveryPhase(); } updateHUD() { const progressFill = this.container.querySelector('.progress-fill'); const progressText = this.container.querySelector('.progress-text'); const scoreDisplay = this.container.querySelector('.score-display'); const livesDisplay = this.container.querySelector('.lives-display'); const progressPercent = (this.currentWordIndex / this.wordsToLearn.length) * 100; progressFill.style.width = `${progressPercent}%`; progressText.textContent = `${this.currentWordIndex}/${this.wordsToLearn.length}`; scoreDisplay.textContent = this.score; livesDisplay.textContent = this.lives; } startDiscoveryPhase() { this.currentPhase = 'discovery'; this.container.querySelector('.phase-indicator').textContent = 'Discovery Phase'; this.showWordCard(); } showWordCard() { if (this.currentWordIndex >= this.wordsToLearn.length) { // All words discovered - start global practice phase this.startGlobalPractice(); return; } const word = this.wordsToLearn[this.currentWordIndex]; const gameContent = this.container.querySelector('.game-content'); // Check what features are missing for this word const missingFeatures = []; if (!word.image) missingFeatures.push('image'); if (!word.pronunciation) missingFeatures.push('pronunciation'); gameContent.innerHTML = `
${word.image ? `${word.word}` : `
📷 No Image
` }
${word.word}
${word.pronunciation ? `
/${word.pronunciation}/
` : `
No pronunciation guide
` }
${word.translation}
${missingFeatures.length > 0 ? `
⚠️ Missing: ${missingFeatures.join(', ')}. Practice questions will be adapted accordingly.
` : '' }
`; // Store game reference for button access this.container.querySelector('.word-discovery-wrapper').game = this; // Auto-play TTS when new word appears (with delay for card animation) setTimeout(() => { this.hearPronunciation(); }, 800); } async hearPronunciation(options = {}) { let wordToSpeak; if (this.currentPhase === 'practice') { // In practice phase, use current practice word wordToSpeak = this.currentPracticeWords[this.practiceRound % this.currentPracticeWords.length]; } else { // In discovery phase, use current word being learned wordToSpeak = this.wordsToLearn[this.currentWordIndex]; } if (!wordToSpeak) return; // Try to play audio file first if available if (wordToSpeak.audioFile || wordToSpeak.pronunciation) { const audioPath = wordToSpeak.audioFile; if (audioPath) { try { const audio = new Audio(audioPath); // Handle audio loading errors audio.onerror = () => { console.warn(`Audio file not found: ${audioPath}, falling back to TTS`); this.fallbackToTTS(wordToSpeak, options); }; // Handle successful audio loading audio.oncanplaythrough = () => { // Adjust playback rate if supported if (options.rate && audio.playbackRate !== undefined) { audio.playbackRate = options.rate; } audio.play().catch(error => { console.warn('Audio playback failed:', error); this.fallbackToTTS(wordToSpeak, options); }); }; // Load the audio audio.load(); // Timeout fallback if audio takes too long setTimeout(() => { if (audio.readyState === 0) { console.warn('Audio loading timeout, falling back to TTS'); this.fallbackToTTS(wordToSpeak, options); } }, 2000); return; // Don't proceed to TTS if we're trying audio } catch (error) { console.warn('Audio creation failed:', error); } } } // Fallback to TTS immediately if no audio file this.fallbackToTTS(wordToSpeak, options); } fallbackToTTS(wordToSpeak, options = {}) { // Use SettingsManager if available, otherwise fallback to basic TTS if (window.SettingsManager) { // Pass custom rate if specified const ttsOptions = {}; if (options.rate) { ttsOptions.rate = options.rate; } window.SettingsManager.speak(wordToSpeak.word, ttsOptions) .catch(error => { console.warn('SettingsManager TTS failed:', error); this.basicTTS(wordToSpeak, options); }); } else { this.basicTTS(wordToSpeak, options); } } basicTTS(wordToSpeak, options = {}) { // Try to speak the word using Web Speech API if ('speechSynthesis' in window && wordToSpeak) { const utterance = new SpeechSynthesisUtterance(wordToSpeak.word); utterance.lang = 'en-US'; utterance.rate = options.rate || 0.8; speechSynthesis.speak(utterance); } else { // Last resort: show pronunciation text if available if (wordToSpeak.pronunciation) { alert(`Pronunciation: /${wordToSpeak.pronunciation}/`); } else { alert(`Word: ${wordToSpeak.word}`); } } } updatePhaseIndicator() { const phaseIndicator = this.container.querySelector('.phase-indicator'); const difficultyNames = ['', 'Easy', 'Medium', 'Hard', 'Expert']; const difficultyClasses = ['', 'difficulty-easy', 'difficulty-medium', 'difficulty-hard', 'difficulty-expert']; if (this.currentPhase === 'practice') { phaseIndicator.innerHTML = `Mixed Practice ${difficultyNames[this.practiceLevel]}`; } else { phaseIndicator.textContent = 'Discovery Phase'; } // Update or create practice progress indicator let progressIndicator = this.container.querySelector('.practice-progress'); if (!progressIndicator) { progressIndicator = document.createElement('div'); progressIndicator.className = 'practice-progress'; this.container.querySelector('.discovery-main').appendChild(progressIndicator); } if (this.currentPhase === 'practice') { progressIndicator.textContent = `Round ${this.practiceRound + 1}/${this.maxPracticeRounds}`; } else { progressIndicator.textContent = ''; } } showMixedPracticeChallenge() { // Get a random word from discovered words for this challenge const currentWord = this.currentPracticeWords[this.practiceRound % this.currentPracticeWords.length]; const gameContent = this.container.querySelector('.game-content'); // Check available content features const hasImages = this.discoveredWords.some(word => word.image); const hasPronunciation = this.discoveredWords.some(word => word.pronunciation); const hasAudioFiles = this.discoveredWords.some(word => word.audioFile); const currentWordHasImage = currentWord.image; const currentWordHasPronunciation = currentWord.pronunciation; const currentWordHasAudio = currentWord.audioFile; // Determine challenge based on practice level and available content const challenges = { 1: { options: 4, time: null, question: 'translation' }, // Easy: 4 options, no timer 2: { options: 6, time: 15, question: 'translation' }, // Medium: 6 options, 15s timer 3: { options: 6, time: 10, question: hasImages ? 'mixed' : 'translation' }, // Hard: mixed if images available 4: { options: 8, time: 8, question: (hasAudioFiles || hasPronunciation) ? 'audio' : 'translation' } // Expert: audio if available }; const challenge = challenges[this.practiceLevel]; const numOptions = challenge.options; // Create options: current word + random others from ALL discovered words const options = [currentWord]; const otherWords = this.discoveredWords.filter(word => word.word !== currentWord.word); const randomOthers = this.shuffleArray([...otherWords]).slice(0, numOptions - 1); options.push(...randomOthers); // Shuffle the options const shuffledOptions = this.shuffleArray([...options]); // Determine question type - TEST FOREIGN WORD KNOWLEDGE, NOT NATIVE LANGUAGE let questionText = ''; let showImages = true; let showText = true; if (challenge.question === 'translation') { // Test: Show foreign word, find translation/image questionText = `Which one means "${currentWord.word}"?`; } else if (challenge.question === 'mixed') { // Build available question types based on content const questionTypes = [`Which one means "${currentWord.word}"?`]; // Add pronunciation question if available (text or audio) if (currentWordHasPronunciation || currentWordHasAudio) { questionTypes.push(`Find the word that sounds like "${currentWord.pronunciation || currentWord.word}"`); } // Add image question if current word has image AND other words have images for comparison if (currentWordHasImage && hasImages) { questionTypes.push(`Which image represents "${currentWord.word}"?`); } questionText = questionTypes[Math.floor(Math.random() * questionTypes.length)]; if (questionText.includes('image')) { showText = false; // Ensure we only show options that have images const imageOptions = [currentWord]; const otherWordsWithImages = this.discoveredWords.filter(word => word.word !== currentWord.word && word.image ); if (otherWordsWithImages.length >= numOptions - 1) { const randomOthers = this.shuffleArray([...otherWordsWithImages]).slice(0, numOptions - 1); options.length = 1; // Reset to just current word options.push(...randomOthers); } } } else if (challenge.question === 'audio') { if (currentWordHasPronunciation || currentWordHasAudio) { questionText = 'Listen and find the correct word!'; showImages = false; // Auto-play pronunciation setTimeout(() => this.hearPronunciation(), 500); } else { // Fallback to translation if no audio questionText = `Which one means "${currentWord.word}"?`; } } const gridClass = numOptions <= 4 ? 'association-grid' : numOptions <= 6 ? 'practice-grid-6' : 'practice-grid-8'; gameContent.innerHTML = `
${challenge.time ? `
${challenge.time}
` : ''}
${questionText}
Correct: ${this.practiceCorrectAnswers}
Errors: ${this.practiceErrors}
Level: ${this.practiceLevel}/4
${shuffledOptions.map((option, index) => `
${showImages && option.image ? `${option.word}` : ''} ${showText ? `
${option.translation}
` : ''} ${!showText && !showImages ? `
?
` : ''}
`).join('')}
`; // Start timer if needed if (challenge.time) { this.startPracticeTimer(challenge.time); } // Auto-play TTS based on practice level with appropriate speed setTimeout(() => { let ttsSpeed; switch (this.practiceLevel) { case 1: // Easy - 0.7 speed ttsSpeed = 0.7; break; case 2: // Medium - 0.9 speed ttsSpeed = 0.9; break; case 3: // Hard - 1.0 speed ttsSpeed = 1.0; break; case 4: // Expert - 1.1 speed (already has audio auto-play) ttsSpeed = 1.1; break; default: ttsSpeed = 0.8; // Fallback } // Don't auto-play if it's an audio-only challenge (Expert mode already handles this) if (challenge.question !== 'audio') { this.hearPronunciation({ rate: ttsSpeed }); } }, 1000); // Delay to let interface render } startPracticeTimer(seconds) { this.practiceTimer = seconds; this.practiceTimerInterval = setInterval(() => { this.practiceTimer--; const timerElement = document.getElementById('practice-timer'); if (timerElement) { timerElement.textContent = this.practiceTimer; if (this.practiceTimer <= 3) { timerElement.style.color = '#FF4444'; timerElement.style.animation = 'pulse 0.5s infinite'; } } if (this.practiceTimer <= 0) { this.clearPracticeTimer(); this.selectPractice(-1, 'TIMEOUT'); // Handle timeout } }, 1000); } clearPracticeTimer() { if (this.practiceTimerInterval) { clearInterval(this.practiceTimerInterval); this.practiceTimerInterval = null; } } selectMixedPractice(selectedIndex, selectedWord) { this.clearPracticeTimer(); const currentWord = this.currentPracticeWords[this.practiceRound % this.currentPracticeWords.length]; const items = this.container.querySelectorAll('.association-item'); let isCorrect = false; if (selectedWord === 'TIMEOUT') { // Timer expired this.practiceErrors++; // Show correct answer items.forEach((item) => { const text = item.querySelector('.association-text'); if (text && text.textContent === currentWord.word) { item.classList.add('correct'); } }); } else if (selectedWord === currentWord.word) { // Correct answer isCorrect = true; items[selectedIndex].classList.add('correct'); this.practiceCorrectAnswers++; // Score based on difficulty level const scoreBonus = [0, 5, 10, 15, 25][this.practiceLevel]; this.score += scoreBonus; this.onScoreUpdate(this.score); } else { // Wrong answer items[selectedIndex].classList.add('incorrect'); this.practiceErrors++; // Show correct answer items.forEach((item) => { const text = item.querySelector('.association-text'); if (text && text.textContent === currentWord.word) { item.classList.add('correct'); } }); } this.updateHUD(); // Continue to next practice round or advance setTimeout(() => { this.practiceRound++; if (this.practiceRound >= this.maxPracticeRounds) { // Check if ready for next level const accuracy = this.practiceCorrectAnswers / this.maxPracticeRounds; if (accuracy >= 0.75 && this.practiceLevel < 4) { // Advance to next difficulty level this.practiceLevel++; this.practiceRound = 0; this.practiceCorrectAnswers = 0; this.practiceErrors = 0; // SHUFFLE words again for new difficulty level this.currentPracticeWords = this.shuffleArray([...this.discoveredWords]); console.log(`🔀 Shuffled words for Level ${this.practiceLevel} - new variation order`); this.updatePhaseIndicator(); setTimeout(() => { this.showLevelUpMessage(); }, 500); } else if (accuracy >= 0.5) { // Passed all practice levels - show completion this.showCompletion(); } else { // Failed practice - retry current level this.practiceRound = Math.max(0, this.practiceRound - 2); // Go back 2 rounds this.practiceCorrectAnswers = 0; this.practiceErrors = 0; this.lives--; if (this.lives <= 0) { this.endGame(); return; } } } else { // Continue current difficulty level with next random word this.updatePhaseIndicator(); this.showMixedPracticeChallenge(); } }, 1500); } showLevelUpMessage() { const gameContent = this.container.querySelector('.game-content'); const difficultyNames = ['', 'Easy', 'Medium', 'Hard', 'Expert']; gameContent.innerHTML = `
🎉 Level Up!
Advanced to ${difficultyNames[this.practiceLevel]} difficulty!
Keep practicing to master this word!
`; setTimeout(() => { this.showMixedPracticeChallenge(); }, 2000); } markAsLearned() { this.discoveredWords.push(this.wordsToLearn[this.currentWordIndex]); this.currentWordIndex++; this.score += 5; this.onScoreUpdate(this.score); this.updateHUD(); setTimeout(() => { this.startDiscoveryPhase(); }, 300); } startGlobalPractice() { // Transition message const gameContent = this.container.querySelector('.game-content'); gameContent.innerHTML = `
🏆 Discovery Complete!
You've discovered all ${this.discoveredWords.length} words!
Now let's practice with mixed vocabulary challenges!
`; } startMixedPractice() { this.currentPhase = 'practice'; this.practiceLevel = 1; this.practiceRound = 0; this.practiceCorrectAnswers = 0; this.practiceErrors = 0; // SHUFFLE discovered words for varied practice order this.currentPracticeWords = this.shuffleArray([...this.discoveredWords]); console.log(`🔀 Shuffled ${this.currentPracticeWords.length} words for practice variation`); this.updatePhaseIndicator(); this.showMixedPracticeChallenge(); } skipToCompletion() { this.showCompletion(); } showCompletion() { const gameContent = this.container.querySelector('.game-content'); const accuracy = Math.round((this.discoveredWords.length / this.wordsToLearn.length) * 100); const practiceAccuracy = this.practiceRound > 0 ? Math.round((this.practiceCorrectAnswers / this.practiceRound) * 100) : 0; gameContent.innerHTML = `
🏆 Vocabulary Mastered!
Words Discovered: ${this.discoveredWords.length}/${this.wordsToLearn.length}
Practice Accuracy: ${practiceAccuracy}%
Final Score: ${this.score}
Practice Level Reached: ${this.practiceLevel}/4
`; } showNoContent() { const gameContent = this.container.querySelector('.game-content'); gameContent.innerHTML = `
📚 No Vocabulary Found
This content doesn't have vocabulary for the Word Discovery game.
Note: Images and audio are optional but enhance the experience!
`; } start() { // Game starts automatically in constructor } restart() { this.currentWordIndex = 0; this.discoveredWords = []; this.score = 0; this.lives = 3; this.practiceLevel = 1; this.practiceRound = 0; this.practiceCorrectAnswers = 0; this.practiceErrors = 0; this.currentPracticeWords = []; this.clearPracticeTimer(); this.wordsToLearn = this.shuffleArray([...this.wordsToLearn]); this.updateHUD(); this.startDiscoveryPhase(); } endGame() { this.onGameEnd(this.score); } destroy() { this.clearPracticeTimer(); // Clean up global content reference if (window.currentGameContent === this.content) { window.currentGameContent = null; } const styleSheet = document.getElementById('word-discovery-styles'); if (styleSheet) { styleSheet.remove(); } } } // Register the game module window.GameModules = window.GameModules || {}; window.GameModules.WordDiscovery = WordDiscovery;