import Module from '../core/Module.js'; /** * WordStorm - Fast-paced falling words game where players match vocabulary * Words fall from the sky like meteorites and players must select correct translations */ class WordStorm extends Module { constructor(name, dependencies, config = {}) { super(name, ['eventBus']); // Validate dependencies if (!dependencies.eventBus || !dependencies.content) { throw new Error('WordStorm requires eventBus and content dependencies'); } this._eventBus = dependencies.eventBus; this._content = dependencies.content; this._config = { container: null, maxWords: 50, fallSpeed: 8000, // ms to fall from top to bottom spawnRate: 4000, // ms between spawns wordLifetime: 9200, // ms before word disappears (+15% more time) startingLives: 3, ...config }; // Game state this._vocabulary = null; this._score = 0; this._level = 1; this._lives = this._config.startingLives; this._combo = 0; this._isGamePaused = false; this._isGameOver = false; this._gameStartTime = null; // Game mechanics this._fallingWords = []; this._currentWordIndex = 0; this._spawnInterval = null; this._gameInterval = null; Object.seal(this); } /** * Get game metadata * @returns {Object} Game metadata */ static getMetadata() { return { name: 'Word Storm', description: 'Fast-paced falling words game with vocabulary matching', difficulty: 'intermediate', category: 'action', estimatedTime: 6, // minutes skills: ['vocabulary', 'speed', 'reflexes', 'concentration'] }; } /** * Calculate compatibility score with content * @param {Object} content - Content to check compatibility with * @returns {Object} Compatibility score and details */ static getCompatibilityScore(content) { const vocab = content?.vocabulary || {}; const vocabCount = Object.keys(vocab).length; if (vocabCount < 8) { return { score: 0, reason: `Insufficient vocabulary (${vocabCount}/8 required)`, requirements: ['vocabulary'], minWords: 8, details: 'Word Storm needs at least 8 vocabulary words for meaningful gameplay' }; } // Perfect score at 30+ words, partial score for 8-29 const score = Math.min(vocabCount / 30, 1); return { score, reason: `${vocabCount} vocabulary words available`, requirements: ['vocabulary'], minWords: 8, optimalWords: 30, details: `Can create dynamic gameplay with ${Math.min(vocabCount, this._config?.maxWords || 50)} words` }; } async init() { this._validateNotDestroyed(); try { // Validate container if (!this._config.container) { throw new Error('Game container is required'); } // Extract and validate vocabulary this._vocabulary = this._extractVocabulary(); if (this._vocabulary.length < 8) { throw new Error(`Insufficient vocabulary: need 8, got ${this._vocabulary.length}`); } // Set up event listeners this._eventBus.on('game:pause', this._handlePause.bind(this), this.name); this._eventBus.on('game:resume', this._handleResume.bind(this), this.name); // Inject CSS this._injectCSS(); // Initialize game interface this._createGameInterface(); this._setupEventListeners(); // Start the game this._gameStartTime = Date.now(); this._startSpawning(); // Emit game ready event this._eventBus.emit('game:ready', { gameId: 'word-storm', instanceId: this.name, vocabulary: this._vocabulary.length }, this.name); this._setInitialized(); } catch (error) { this._showError(error.message); throw error; } } async destroy() { this._validateNotDestroyed(); // Clear intervals if (this._spawnInterval) { clearInterval(this._spawnInterval); this._spawnInterval = null; } if (this._gameInterval) { clearInterval(this._gameInterval); this._gameInterval = null; } // Remove CSS this._removeCSS(); // Clean up event listeners if (this._config.container) { this._config.container.innerHTML = ''; } // Emit game end event this._eventBus.emit('game:ended', { gameId: 'word-storm', instanceId: this.name, score: this._score, level: this._level, combo: this._combo, duration: this._gameStartTime ? Date.now() - this._gameStartTime : 0 }, this.name); this._setDestroyed(); } /** * Get current game state * @returns {Object} Current game state */ getGameState() { this._validateInitialized(); return { score: this._score, level: this._level, lives: this._lives, combo: this._combo, isGameOver: this._isGameOver, isPaused: this._isGamePaused, duration: this._gameStartTime ? Date.now() - this._gameStartTime : 0, fallingWordsCount: this._fallingWords.length }; } // Private methods _extractVocabulary() { const vocab = this._content?.vocabulary || {}; const vocabulary = []; for (const [word, data] of Object.entries(vocab)) { if (data.user_language || (typeof data === 'string')) { vocabulary.push({ original: word, translation: data.user_language || data, type: data.type || 'unknown' }); } } // Limit vocabulary and shuffle return this._shuffleArray(vocabulary).slice(0, this._config.maxWords); } _injectCSS() { const cssId = `word-storm-styles-${this.name}`; if (document.getElementById(cssId)) return; const style = document.createElement('style'); style.id = cssId; style.textContent = ` .word-storm-game { height: 100vh; overflow: hidden; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; position: relative; } .word-storm-hud { display: flex; justify-content: space-between; align-items: center; padding: 15px 20px; background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%); color: white; position: relative; z-index: 100; } .hud-section { display: flex; gap: 20px; align-items: center; } .hud-stat { display: flex; flex-direction: column; align-items: center; min-width: 60px; } .hud-label { font-size: 0.8rem; opacity: 0.9; margin-bottom: 2px; } .hud-value { font-size: 1.2rem; font-weight: bold; } .pause-btn { padding: 8px 15px; background: rgba(255, 255, 255, 0.2); border: 1px solid rgba(255, 255, 255, 0.3); color: white; border-radius: 6px; cursor: pointer; font-size: 0.9rem; transition: all 0.3s ease; } .pause-btn:hover { background: rgba(255, 255, 255, 0.3); } .word-storm-area { position: relative; height: calc(80vh - 180px); background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%); overflow: hidden; border-radius: 20px 20px 0 0; margin: 10px 10px 0 10px; box-shadow: inset 0 0 30px rgba(0, 0, 0, 0.3); } .falling-word { position: absolute; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px 30px; border-radius: 25px; font-size: 2rem; font-weight: 600; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3), 0 0 20px rgba(102, 126, 234, 0.4); cursor: default; user-select: none; transform: translateX(-50%); animation: wordGlow 2s ease-in-out infinite; z-index: 10; } .falling-word.exploding { animation: explode 0.8s ease-out forwards; } .falling-word.wrong-shake { animation: wrongShake 0.6s ease-in-out forwards; } .word-storm-answer-panel { position: relative; background: rgba(0, 0, 0, 0.9); padding: 15px; border-top: 3px solid #667eea; border-radius: 0 0 20px 20px; margin: 0 10px 10px 10px; z-index: 100; } .word-storm-answer-panel.wrong-flash { animation: wrongFlash 0.5s ease-in-out; } .answer-buttons-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; max-width: 600px; margin: 0 auto; } .word-storm-answer-btn { padding: 10px 16px; background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%); color: white; border: none; border-radius: 16px; font-size: 0.95rem; font-weight: 500; cursor: pointer; transition: all 0.3s ease; position: relative; overflow: hidden; } .word-storm-answer-btn:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(59, 130, 246, 0.4); } .word-storm-answer-btn:active { transform: translateY(0); } .word-storm-answer-btn.correct { background: linear-gradient(135deg, #10b981 0%, #059669 100%); animation: correctPulse 0.6s ease-out; } .word-storm-answer-btn.incorrect { background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); animation: incorrectShake 0.6s ease-out; } .level-up-popup { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0, 0, 0, 0.9); color: white; padding: 30px; border-radius: 15px; text-align: center; z-index: 1000; animation: levelUpAppear 2s ease-out forwards; } .points-popup { position: absolute; font-size: 2rem; font-weight: bold; color: #10b981; pointer-events: none; z-index: 1000; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5); animation: pointsFloat 1.5s ease-out forwards; } .game-over-screen { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.9); display: flex; align-items: center; justify-content: center; z-index: 2000; } .game-over-content { background: linear-gradient(135deg, #dc2626 0%, #991b1b 100%); color: white; padding: 40px; border-radius: 15px; text-align: center; max-width: 400px; } .game-over-content h2 { margin: 0 0 20px 0; font-size: 2.5rem; } .final-stats { margin: 20px 0; padding: 20px; background: rgba(255, 255, 255, 0.1); border-radius: 8px; } .stat-row { display: flex; justify-content: space-between; margin: 10px 0; } .restart-btn, .exit-btn { margin: 10px; padding: 12px 25px; border: 2px solid white; border-radius: 8px; background: transparent; color: white; font-size: 1rem; cursor: pointer; transition: all 0.3s ease; } .restart-btn:hover, .exit-btn:hover { background: white; color: #dc2626; } /* Animations */ @keyframes wordGlow { 0%, 100% { box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3), 0 0 20px rgba(102, 126, 234, 0.4); } 50% { box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3), 0 0 30px rgba(102, 126, 234, 0.6); } } @keyframes explode { 0% { transform: translateX(-50%) scale(1) rotate(0deg); opacity: 1; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); } 25% { transform: translateX(-50%) scale(1.3) rotate(5deg); opacity: 0.9; background: linear-gradient(135deg, #10b981 0%, #059669 100%); box-shadow: 0 8px 25px rgba(16, 185, 129, 0.5), 0 0 40px rgba(16, 185, 129, 0.8); } 50% { transform: translateX(-50%) scale(1.5) rotate(-3deg); opacity: 0.7; background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%); box-shadow: 0 12px 35px rgba(245, 158, 11, 0.6), 0 0 60px rgba(245, 158, 11, 0.9); } 75% { transform: translateX(-50%) scale(0.8) rotate(2deg); opacity: 0.4; background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); } 100% { transform: translateX(-50%) scale(0.1) rotate(0deg); opacity: 0; } } @keyframes wrongShake { 0%, 100% { transform: translateX(-50%) scale(1); background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); } 10%, 30%, 50%, 70%, 90% { transform: translateX(-60%) scale(0.95); background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); box-shadow: 0 4px 15px rgba(239, 68, 68, 0.6), 0 0 25px rgba(239, 68, 68, 0.8); } 20%, 40%, 60%, 80% { transform: translateX(-40%) scale(0.95); background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); box-shadow: 0 4px 15px rgba(239, 68, 68, 0.6), 0 0 25px rgba(239, 68, 68, 0.8); } } @keyframes wrongFlash { 0%, 100% { background: rgba(0, 0, 0, 0.8); } 50% { background: rgba(239, 68, 68, 0.6); box-shadow: 0 0 20px rgba(239, 68, 68, 0.6), inset 0 0 20px rgba(239, 68, 68, 0.3); } } @keyframes correctPulse { 0% { transform: scale(1); } 50% { transform: scale(1.05); } 100% { transform: scale(1); } } @keyframes incorrectShake { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-5px); } 75% { transform: translateX(5px); } } @keyframes pointsFloat { 0% { transform: translateY(0) scale(1); opacity: 1; } 30% { transform: translateY(-20px) scale(1.3); opacity: 1; } 100% { transform: translateY(-80px) scale(0.5); opacity: 0; } } @keyframes levelUpAppear { 0% { transform: translate(-50%, -50%) scale(0.5); opacity: 0; } 20% { transform: translate(-50%, -50%) scale(1.2); opacity: 1; } 80% { transform: translate(-50%, -50%) scale(1); opacity: 1; } 100% { transform: translate(-50%, -50%) scale(1); opacity: 0; } } @keyframes screenShake { 0%, 100% { transform: translateX(0); } 10% { transform: translateX(-3px) translateY(1px); } 20% { transform: translateX(3px) translateY(-1px); } 30% { transform: translateX(-2px) translateY(2px); } 40% { transform: translateX(2px) translateY(-2px); } 50% { transform: translateX(-1px) translateY(1px); } 60% { transform: translateX(1px) translateY(-1px); } 70% { transform: translateX(-2px) translateY(0px); } 80% { transform: translateX(2px) translateY(1px); } 90% { transform: translateX(-1px) translateY(-1px); } } @media (max-width: 768px) { .falling-word { padding: 15px 25px; font-size: 1.8rem; } .hud-section { gap: 15px; } .answer-buttons-grid { grid-template-columns: 1fr 1fr; gap: 10px; } } @media (max-width: 480px) { .falling-word { font-size: 1.5rem; padding: 12px 20px; } .answer-buttons-grid { grid-template-columns: 1fr; } } `; document.head.appendChild(style); } _removeCSS() { const cssId = `word-storm-styles-${this.name}`; const existingStyle = document.getElementById(cssId); if (existingStyle) { existingStyle.remove(); } } _createGameInterface() { this._config.container.innerHTML = `
Score
0
Level
1
Lives
3
Combo
0
`; this._generateAnswerOptions(); } _setupEventListeners() { // Pause button const pauseBtn = document.getElementById('pause-btn'); if (pauseBtn) { pauseBtn.addEventListener('click', () => this._togglePause()); } // Exit button const exitButton = document.getElementById('exit-storm'); if (exitButton) { exitButton.addEventListener('click', () => { this._eventBus.emit('game:exit-request', { instanceId: this.name }, this.name); }); } // Answer button clicks this._config.container.addEventListener('click', (event) => { if (event.target.matches('.word-storm-answer-btn')) { const answer = event.target.textContent; this._checkAnswer(answer); } if (event.target.matches('.restart-btn')) { this._restartGame(); } }); // Keyboard support document.addEventListener('keydown', (event) => { if (event.key >= '1' && event.key <= '4') { const btnIndex = parseInt(event.key) - 1; const buttons = document.querySelectorAll('.word-storm-answer-btn'); if (buttons[btnIndex]) { buttons[btnIndex].click(); } } if (event.key === ' ' || event.key === 'Escape') { event.preventDefault(); this._togglePause(); } }); } _startSpawning() { this._spawnInterval = setInterval(() => { if (!this._isGamePaused && !this._isGameOver) { this._spawnFallingWord(); } }, this._config.spawnRate); } _spawnFallingWord() { if (this._vocabulary.length === 0) return; const word = this._vocabulary[this._currentWordIndex % this._vocabulary.length]; this._currentWordIndex++; const gameArea = document.getElementById('game-area'); const wordElement = document.createElement('div'); wordElement.className = 'falling-word'; wordElement.textContent = word.original; wordElement.style.left = Math.random() * 80 + 10 + '%'; wordElement.style.top = '80px'; // Start just below the HUD gameArea.appendChild(wordElement); this._fallingWords.push({ element: wordElement, word: word, startTime: Date.now() }); // Generate new answer options when word spawns this._generateAnswerOptions(); // Animate falling this._animateFalling(wordElement); // Remove after lifetime setTimeout(() => { if (wordElement.parentNode) { this._missWord(wordElement); } }, this._config.wordLifetime); } _animateFalling(wordElement) { wordElement.style.transition = `top ${this._config.fallSpeed}ms linear`; setTimeout(() => { wordElement.style.top = 'calc(100vh + 60px)'; // Continue falling past screen }, 50); } _generateAnswerOptions() { if (this._vocabulary.length === 0) return; const buttons = []; const correctWord = this._fallingWords.length > 0 ? this._fallingWords[this._fallingWords.length - 1].word : this._vocabulary[0]; // Add correct answer buttons.push(correctWord.translation); // Add 3 random incorrect answers while (buttons.length < 4) { const randomWord = this._vocabulary[Math.floor(Math.random() * this._vocabulary.length)]; if (!buttons.includes(randomWord.translation)) { buttons.push(randomWord.translation); } } // Shuffle buttons this._shuffleArray(buttons); // Update answer panel const answerButtons = document.getElementById('answer-buttons'); if (answerButtons) { answerButtons.innerHTML = buttons.map(answer => `` ).join(''); } } _checkAnswer(selectedAnswer) { const activeFallingWords = this._fallingWords.filter(fw => fw.element.parentNode); for (let i = 0; i < activeFallingWords.length; i++) { const fallingWord = activeFallingWords[i]; if (fallingWord.word.translation === selectedAnswer) { this._correctAnswer(fallingWord); return; } } // Wrong answer this._wrongAnswer(); } _correctAnswer(fallingWord) { // Remove from game with epic explosion if (fallingWord.element.parentNode) { fallingWord.element.classList.add('exploding'); // Add screen shake effect const gameArea = document.getElementById('game-area'); if (gameArea) { gameArea.style.animation = 'none'; gameArea.offsetHeight; // Force reflow gameArea.style.animation = 'screenShake 0.3s ease-in-out'; setTimeout(() => { gameArea.style.animation = ''; }, 300); } setTimeout(() => { if (fallingWord.element.parentNode) { fallingWord.element.remove(); } }, 800); } // Remove from tracking this._fallingWords = this._fallingWords.filter(fw => fw !== fallingWord); // Update score this._combo++; const points = 10 + (this._combo * 2); this._score += points; // Update display this._updateHUD(); // Add points popup animation this._showPointsPopup(points, fallingWord.element); // Vibration feedback (if supported) if (navigator.vibrate) { navigator.vibrate([50, 30, 50]); } // Level up check if (this._score > 0 && this._score % 100 === 0) { this._levelUp(); } // Emit correct answer event this._eventBus.emit('word-storm:correct-answer', { gameId: 'word-storm', instanceId: this.name, word: fallingWord.word, points, combo: this._combo, score: this._score }, this.name); } _wrongAnswer() { this._combo = 0; // Enhanced wrong answer animation const answerPanel = document.getElementById('answer-panel'); if (answerPanel) { answerPanel.classList.add('wrong-flash'); setTimeout(() => { answerPanel.classList.remove('wrong-flash'); }, 500); } // Shake all falling words to show disappointment this._fallingWords.forEach(fw => { if (fw.element.parentNode && !fw.element.classList.contains('exploding')) { fw.element.classList.add('wrong-shake'); setTimeout(() => { fw.element.classList.remove('wrong-shake'); }, 600); } }); // Screen flash red const gameArea = document.getElementById('game-area'); if (gameArea) { const overlay = document.createElement('div'); overlay.style.cssText = ` position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(239, 68, 68, 0.3); pointer-events: none; animation: wrongFlash 0.4s ease-in-out; z-index: 100; `; gameArea.appendChild(overlay); setTimeout(() => { if (overlay.parentNode) overlay.remove(); }, 400); } this._updateHUD(); // Wrong answer vibration (stronger/longer) if (navigator.vibrate) { navigator.vibrate([200, 100, 200, 100, 200]); } // Emit wrong answer event this._eventBus.emit('word-storm:wrong-answer', { gameId: 'word-storm', instanceId: this.name, score: this._score }, this.name); } _showPointsPopup(points, wordElement) { const popup = document.createElement('div'); popup.textContent = `+${points}`; popup.className = 'points-popup'; popup.style.left = wordElement.style.left; popup.style.top = wordElement.offsetTop + 'px'; const gameArea = document.getElementById('game-area'); if (gameArea) { gameArea.appendChild(popup); setTimeout(() => { if (popup.parentNode) popup.remove(); }, 1500); } } _missWord(wordElement) { // Remove word if (wordElement.parentNode) { wordElement.remove(); } // Remove from tracking this._fallingWords = this._fallingWords.filter(fw => fw.element !== wordElement); // Lose life this._lives--; this._combo = 0; this._updateHUD(); if (this._lives <= 0) { this._gameOver(); } // Emit word missed event this._eventBus.emit('word-storm:word-missed', { gameId: 'word-storm', instanceId: this.name, lives: this._lives, score: this._score }, this.name); } _levelUp() { this._level++; // Increase difficulty by 5% (x1.05 speed = /1.05 time) this._config.fallSpeed = Math.max(1000, this._config.fallSpeed / 1.05); this._config.spawnRate = Math.max(800, this._config.spawnRate / 1.05); // Restart intervals with new timing if (this._spawnInterval) { clearInterval(this._spawnInterval); this._startSpawning(); } this._updateHUD(); // Show level up message const gameArea = document.getElementById('game-area'); const levelUpMsg = document.createElement('div'); levelUpMsg.className = 'level-up-popup'; levelUpMsg.innerHTML = `

⚡ LEVEL UP! ⚡

Level ${this._level}

Words fall faster!

`; gameArea.appendChild(levelUpMsg); setTimeout(() => { if (levelUpMsg.parentNode) { levelUpMsg.remove(); } }, 2000); // Emit level up event this._eventBus.emit('word-storm:level-up', { gameId: 'word-storm', instanceId: this.name, level: this._level, score: this._score }, this.name); } _togglePause() { this._isGamePaused = !this._isGamePaused; const pauseBtn = document.getElementById('pause-btn'); if (pauseBtn) { pauseBtn.textContent = this._isGamePaused ? '▶️ Resume' : '⏸️ Pause'; } if (this._isGamePaused) { this._eventBus.emit('game:paused', { instanceId: this.name }, this.name); } else { this._eventBus.emit('game:resumed', { instanceId: this.name }, this.name); } } _gameOver() { this._isGameOver = true; // Clear intervals if (this._spawnInterval) { clearInterval(this._spawnInterval); this._spawnInterval = null; } // Clear falling words this._fallingWords.forEach(fw => { if (fw.element.parentNode) { fw.element.remove(); } }); this._fallingWords = []; // Show game over screen this._showGameOverScreen(); // Emit game over event this._eventBus.emit('game:completed', { gameId: 'word-storm', instanceId: this.name, score: this._score, level: this._level, duration: this._gameStartTime ? Date.now() - this._gameStartTime : 0 }, this.name); } _showGameOverScreen() { const duration = this._gameStartTime ? Math.round((Date.now() - this._gameStartTime) / 1000) : 0; // Store best score const gameKey = 'word-storm'; const currentScore = this._score; const bestScore = parseInt(localStorage.getItem(`${gameKey}-best-score`) || '0'); const isNewBest = currentScore > bestScore; if (isNewBest) { localStorage.setItem(`${gameKey}-best-score`, currentScore.toString()); } // Show victory popup this._showVictoryPopup({ gameTitle: 'Word Storm', currentScore, bestScore: isNewBest ? currentScore : bestScore, isNewBest, stats: { 'Level Reached': this._level, 'Duration': `${duration}s`, 'Words Caught': this._wordsCaught || 0, 'Accuracy': this._wordsCaught ? `${Math.round((this._wordsCaught / (this._wordsCaught + this._wordsMissed || 0)) * 100)}%` : '0%' } }); } _restartGame() { // Reset game state this._score = 0; this._level = 1; this._lives = this._config.startingLives; this._combo = 0; this._isGamePaused = false; this._isGameOver = false; this._currentWordIndex = 0; this._gameStartTime = Date.now(); // Reset fall speed and spawn rate this._config.fallSpeed = 8000; this._config.spawnRate = 4000; // Clear existing intervals if (this._spawnInterval) { clearInterval(this._spawnInterval); } // Clear falling words this._fallingWords.forEach(fw => { if (fw.element.parentNode) { fw.element.remove(); } }); this._fallingWords = []; // Victory popup is handled by its own close events // Update HUD and restart this._updateHUD(); this._generateAnswerOptions(); this._startSpawning(); } _updateHUD() { const scoreDisplay = document.getElementById('score-display'); const levelDisplay = document.getElementById('level-display'); const livesDisplay = document.getElementById('lives-display'); const comboDisplay = document.getElementById('combo-display'); if (scoreDisplay) scoreDisplay.textContent = this._score; if (levelDisplay) levelDisplay.textContent = this._level; if (livesDisplay) livesDisplay.textContent = this._lives; if (comboDisplay) comboDisplay.textContent = this._combo; } _showError(message) { if (this._config.container) { this._config.container.innerHTML = `

Word Storm Error

${message}

`; } } _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; } _handlePause() { this._isGamePaused = true; const pauseBtn = document.getElementById('pause-btn'); if (pauseBtn) { pauseBtn.textContent = '▶️ Resume'; } this._eventBus.emit('game:paused', { instanceId: this.name }, this.name); } _handleResume() { this._isGamePaused = false; const pauseBtn = document.getElementById('pause-btn'); if (pauseBtn) { pauseBtn.textContent = '⏸️ Pause'; } this._eventBus.emit('game:resumed', { instanceId: this.name }, this.name); } _showVictoryPopup({ gameTitle, currentScore, bestScore, isNewBest, stats }) { const popup = document.createElement('div'); popup.className = 'victory-popup'; popup.innerHTML = `
💥

${gameTitle} Complete!

${isNewBest ? '
🎉 New Best Score!
' : ''}
Your Score
${currentScore}
Best Score
${bestScore}
${Object.entries(stats).map(([key, value]) => `
${key}
${value}
`).join('')}
`; document.body.appendChild(popup); // Animate in requestAnimationFrame(() => { popup.classList.add('show'); }); // Add event listeners popup.querySelector('#play-again-btn').addEventListener('click', () => { popup.remove(); this._restartGame(); }); popup.querySelector('#different-game-btn').addEventListener('click', () => { popup.remove(); if (window.app && window.app.getCore().router) { window.app.getCore().router.navigate('/games'); } else { window.location.href = '/#/games'; } }); popup.querySelector('#main-menu-btn').addEventListener('click', () => { popup.remove(); if (window.app && window.app.getCore().router) { window.app.getCore().router.navigate('/'); } else { window.location.href = '/'; } }); // Close on backdrop click popup.addEventListener('click', (e) => { if (e.target === popup) { popup.remove(); if (window.app && window.app.getCore().router) { window.app.getCore().router.navigate('/games'); } else { window.location.href = '/#/games'; } } }); } } export default WordStorm;