// plugins/word_scramble_plugin.js - Word Scramble Game Plugin const fs = require('fs'); const path = require('path'); module.exports = { gameState: { activeScrambles: new Map(), // channel -> scramble data scores: new Map() // nick -> score data }, // Word lists organized by difficulty - easy to expand! wordLists: { easy: [ 'cat', 'dog', 'run', 'sun', 'fun', 'big', 'red', 'car', 'hat', 'map', 'pen', 'box', 'key', 'cup', 'bag', 'egg', 'ice', 'zoo', 'owl', 'fox', 'bus', 'toy', 'fly', 'sky', 'day', 'way', 'boy', 'joy', 'may', 'say', 'boat', 'coat', 'goat', 'road', 'toad', 'load', 'cold', 'gold', 'hold', 'book', 'look', 'took', 'cook', 'hook', 'good', 'wood', 'food', 'mood', 'hand', 'land', 'band', 'sand', 'wind', 'kind', 'find', 'mind', 'wild' ], medium: [ 'house', 'mouse', 'horse', 'nurse', 'purse', 'curse', 'force', 'voice', 'table', 'cable', 'fable', 'stable', 'noble', 'double', 'trouble', 'bubble', 'water', 'later', 'paper', 'tiger', 'tower', 'power', 'flower', 'shower', 'school', 'smooth', 'choose', 'cheese', 'freeze', 'breeze', 'please', 'sleeve', 'market', 'basket', 'rocket', 'socket', 'pocket', 'ticket', 'jacket', 'bucket', 'garden', 'golden', 'wooden', 'broken', 'spoken', 'chosen', 'frozen', 'proven', 'friend', 'prince', 'bridge', 'orange', 'change', 'chance', 'france', 'dance', 'winter', 'summer', 'spring', 'autumn', 'season', 'reason', 'person', 'lesson' ], hard: [ 'computer', 'elephant', 'treasure', 'building', 'mountain', 'champion', 'surprise', 'together', 'birthday', 'question', 'language', 'sandwich', 'princess', 'keyboard', 'umbrella', 'hospital', 'festival', 'material', 'original', 'terminal', 'personal', 'elephant', 'telephone', 'envelope', 'magazine', 'medicine', 'adventure', 'passenger', 'butterfly', 'wonderful', 'beautiful', 'dangerous', 'important', 'different', 'excellent', 'knowledge', 'challenge', 'advantage', 'encourage', 'education', 'operation', 'direction', 'beginning', 'happening', 'lightning', 'frightening', 'strengthening', 'interesting', 'playground', 'background', 'understand', 'recommend', 'restaurant', 'government' ] }, init(bot) { console.log('Word Scramble plugin initialized - Get ready to unscramble! 🔤'); // Set up score file path this.scoresFile = path.join(__dirname, 'scramble_scores.json'); // Load existing scores this.loadScores(); }, cleanup(bot) { console.log('Word Scramble plugin cleaned up'); // Save scores before cleanup this.saveScores(); // Clear all active scrambles and timers this.gameState.activeScrambles.forEach(scramble => { if (scramble.timer) { clearTimeout(scramble.timer); } }); this.gameState.activeScrambles.clear(); }, commands: [ { name: 'scramble', description: 'Start a word scramble game (easy/medium/hard)', execute(context, bot) { module.exports.startScramble(context, bot); } }, { name: 'hint', description: 'Get a hint for the current scramble', execute(context, bot) { module.exports.giveHint(context, bot); } }, { name: 'skip', description: 'Skip the current scramble', execute(context, bot) { module.exports.skipScramble(context, bot); } }, { name: 'scramblestats', description: 'Show your word scramble statistics', execute(context, bot) { module.exports.showPlayerStats(context, bot); } }, { name: 'topscramble', description: 'Show top word scramble players', execute(context, bot) { module.exports.showLeaderboard(context, bot); } }, { name: 'scramblescore', description: 'Show your current scramble score', execute(context, bot) { module.exports.showScore(context, bot); } } ], // Handle channel messages for answer checking onMessage(data, bot) { if (data.isChannel) { this.checkAnswer(data, bot); } }, startScramble(context, bot) { const channel = context.channel; if (!channel) { bot.say(context.replyTo, '🔤 Word scramble can only be played in channels!'); return; } // Check if there's already an active scramble if (this.gameState.activeScrambles.has(channel)) { const scramble = this.gameState.activeScrambles.get(channel); const timeLeft = Math.ceil((scramble.endTime - Date.now()) / 1000); bot.say(context.replyTo, `🔤 Scramble already active! "${scramble.scrambledWord}" (${timeLeft}s left)`); return; } // Determine difficulty const difficulty = this.parseDifficulty(context.args[0]); // Select random word const word = this.getRandomWord(difficulty); const scrambledWord = this.scrambleWord(word); // Create scramble data const scrambleData = { word: word, scrambledWord: scrambledWord, difficulty: difficulty, startTime: Date.now(), endTime: Date.now() + 60000, // 60 seconds hintUsed: false, starter: context.nick }; // Set timer for timeout scrambleData.timer = setTimeout(() => { this.timeoutScramble(channel, bot, scrambleData); }, 60000); this.gameState.activeScrambles.set(channel, scrambleData); // Announce the scramble bot.say(channel, `🔤 WORD SCRAMBLE: "${scrambledWord}" (${word.length} letters, ${difficulty}) - 60s to solve!`); bot.say(channel, `💡 Use !hint for a clue or !skip to skip (started by ${context.nick})`); console.log(`🔤 Started scramble in ${channel}: ${word} → ${scrambledWord} (${difficulty})`); }, checkAnswer(data, bot) { const channel = data.target; const scramble = this.gameState.activeScrambles.get(channel); if (!scramble) { return; // No active scramble } const answer = data.message.toLowerCase().trim(); const correctAnswer = scramble.word.toLowerCase(); if (answer === correctAnswer) { // Correct answer! this.handleCorrectAnswer(channel, data.nick, scramble, bot); } }, handleCorrectAnswer(channel, nick, scramble, bot) { // Clear timer clearTimeout(scramble.timer); // Calculate solve time const solveTime = (Date.now() - scramble.startTime) / 1000; // Calculate points let points = this.getDifficultyPoints(scramble.difficulty); // Speed bonus if (solveTime < 10) { points += 1; } // Hint penalty if (scramble.hintUsed) { points = Math.max(1, points - 1); } // Update player score this.updatePlayerScore(nick, scramble.difficulty, points, solveTime); // Save scores this.saveScores(); // Remove scramble this.gameState.activeScrambles.delete(channel); // Announce success const speedBonus = solveTime < 10 ? ' ⚡ SPEED BONUS!' : ''; const hintPenalty = scramble.hintUsed ? ' (hint used)' : ''; bot.say(channel, `✅ Correct! ${nick} solved "${scramble.word}" in ${solveTime.toFixed(1)}s (+${points} points)${speedBonus}${hintPenalty}`); console.log(`🎯 ${nick} solved scramble in ${channel}: ${scramble.word} (${solveTime.toFixed(1)}s, ${points}pts)`); }, giveHint(context, bot) { const channel = context.channel; if (!channel) { bot.say(context.replyTo, '🔤 Hints only work in channels!'); return; } const scramble = this.gameState.activeScrambles.get(channel); if (!scramble) { bot.say(context.replyTo, '🔤 No active scramble to give hints for!'); return; } if (scramble.hintUsed) { bot.say(context.replyTo, '💡 Hint already used for this scramble!'); return; } // Generate hint (first and last letter) const word = scramble.word; const hint = word.length > 2 ? `${word[0]}${'_'.repeat(word.length - 2)}${word[word.length - 1]}` : `${word[0]}${'_'.repeat(word.length - 1)}`; scramble.hintUsed = true; bot.say(channel, `💡 Hint: ${hint} (points reduced by 1)`); }, skipScramble(context, bot) { const channel = context.channel; if (!channel) { bot.say(context.replyTo, '🔤 Skip only works in channels!'); return; } const scramble = this.gameState.activeScrambles.get(channel); if (!scramble) { bot.say(context.replyTo, '🔤 No active scramble to skip!'); return; } // Only starter or admin can skip if (context.nick !== scramble.starter && context.nick !== 'megasconed') { bot.say(context.replyTo, '🔤 Only the scramble starter or admin can skip!'); return; } this.timeoutScramble(channel, bot, scramble); }, timeoutScramble(channel, bot, scramble) { clearTimeout(scramble.timer); this.gameState.activeScrambles.delete(channel); bot.say(channel, `⏰ Time's up! The word was "${scramble.word}" - better luck next time!`); console.log(`⏰ Scramble timed out in ${channel}: ${scramble.word}`); }, // Utility functions parseDifficulty(input) { if (!input) return 'medium'; const difficulty = input.toLowerCase(); if (['easy', 'medium', 'hard'].includes(difficulty)) { return difficulty; } if (difficulty === 'random') { const difficulties = ['easy', 'medium', 'hard']; return difficulties[Math.floor(Math.random() * difficulties.length)]; } return 'medium'; }, getRandomWord(difficulty) { const words = this.wordLists[difficulty]; return words[Math.floor(Math.random() * words.length)]; }, scrambleWord(word) { const letters = word.split(''); // Fisher-Yates shuffle for (let i = letters.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [letters[i], letters[j]] = [letters[j], letters[i]]; } const scrambled = letters.join(''); // If scrambled word is the same as original, try again if (scrambled === word && word.length > 2) { return this.scrambleWord(word); } return scrambled; }, getDifficultyPoints(difficulty) { const points = { easy: 1, medium: 2, hard: 3 }; return points[difficulty] || 2; }, // Player score management initializePlayerData(nick) { if (!this.gameState.scores.has(nick)) { this.gameState.scores.set(nick, { totalPoints: 0, totalSolved: 0, avgTime: 0, bestTime: null, currentStreak: 0, longestStreak: 0, difficultyStats: { easy: 0, medium: 0, hard: 0 } }); } }, updatePlayerScore(nick, difficulty, points, solveTime) { this.initializePlayerData(nick); const playerData = this.gameState.scores.get(nick); // Update totals playerData.totalPoints += points; playerData.totalSolved += 1; playerData.currentStreak += 1; // Update best time if (playerData.bestTime === null || solveTime < playerData.bestTime) { playerData.bestTime = solveTime; } // Update longest streak if (playerData.currentStreak > playerData.longestStreak) { playerData.longestStreak = playerData.currentStreak; } // Update average time playerData.avgTime = ((playerData.avgTime * (playerData.totalSolved - 1)) + solveTime) / playerData.totalSolved; // Update difficulty stats playerData.difficultyStats[difficulty] += 1; this.gameState.scores.set(nick, playerData); }, showScore(context, bot) { const playerData = this.gameState.scores.get(context.nick); if (!playerData) { bot.say(context.replyTo, `🔤 ${context.nick} hasn't solved any scrambles yet!`); return; } bot.say(context.replyTo, `🎯 ${context.nick}: ${playerData.totalPoints} points from ${playerData.totalSolved} solved words!`); }, showPlayerStats(context, bot) { const playerData = this.gameState.scores.get(context.nick); if (!playerData) { bot.say(context.replyTo, `🔤 ${context.nick} hasn't solved any scrambles yet!`); return; } const stats = playerData.difficultyStats; bot.say(context.replyTo, `📊 ${context.nick}'s Stats:`); bot.say(context.replyTo, `🎯 Points: ${playerData.totalPoints} | Words: ${playerData.totalSolved} | Streak: ${playerData.currentStreak} (best: ${playerData.longestStreak})`); bot.say(context.replyTo, `⏱️ Avg time: ${playerData.avgTime.toFixed(1)}s | Best: ${playerData.bestTime ? playerData.bestTime.toFixed(1) + 's' : 'N/A'}`); bot.say(context.replyTo, `🔤 Easy: ${stats.easy} | Medium: ${stats.medium} | Hard: ${stats.hard}`); }, showLeaderboard(context, bot) { const scores = Array.from(this.gameState.scores.entries()) .sort((a, b) => b[1].totalPoints - a[1].totalPoints) .slice(0, 10); if (scores.length === 0) { bot.say(context.replyTo, '🔤 No one has solved any scrambles yet!'); return; } bot.say(context.replyTo, '🏆 TOP SCRAMBLERS 🏆'); scores.forEach(([nick, data], index) => { const medal = index === 0 ? '🥇' : index === 1 ? '🥈' : index === 2 ? '🥉' : '🎯'; bot.say(context.replyTo, `${medal} ${index + 1}. ${nick}: ${data.totalPoints} points (${data.totalSolved} words)`); }); }, // Score persistence loadScores() { try { if (fs.existsSync(this.scoresFile)) { const data = fs.readFileSync(this.scoresFile, 'utf8'); const scoresObject = JSON.parse(data); // Convert back to Map this.gameState.scores = new Map(Object.entries(scoresObject)); console.log(`🔤 Loaded ${this.gameState.scores.size} scramble scores from ${this.scoresFile}`); // Log top 3 players if any exist if (this.gameState.scores.size > 0) { const topPlayers = Array.from(this.gameState.scores.entries()) .sort((a, b) => b[1].totalPoints - a[1].totalPoints) .slice(0, 3); console.log('🏆 Top scramblers:', topPlayers.map(([name, data]) => `${name}(${data.totalPoints}pts)`).join(', ')); } } else { console.log(`🔤 No existing scramble scores file found, starting fresh`); } } catch (error) { console.error(`❌ Error loading scramble scores:`, error); this.gameState.scores = new Map(); } }, saveScores() { try { const scoresObject = Object.fromEntries(this.gameState.scores); const data = JSON.stringify(scoresObject, null, 2); fs.writeFileSync(this.scoresFile, data, 'utf8'); console.log(`💾 Saved ${this.gameState.scores.size} scramble scores to ${this.scoresFile}`); } catch (error) { console.error(`❌ Error saving scramble scores:`, error); } } };