// plugins/scratchcard.js - Scratch Cards Casino game plugin const fs = require('fs'); const path = require('path'); module.exports = { init(bot) { console.log('Scratch Cards Casino plugin initialized'); this.bot = bot; // Persistent player statistics (saved to file) this.playerStats = new Map(); // nick -> { totalWins, totalSpent, totalCards, biggestWin, jackpots, name } // Scratch card types with different themes and payouts this.cardTypes = { classic: { name: 'Classic', cost: 5, symbols: ['šŸ’', 'šŸ‹', 'šŸŠ', 'šŸ‡', 'šŸ””', '⭐', 'šŸ’Ž'], payouts: { 'šŸ’': 2, // 2x 'šŸ‹': 3, // 3x 'šŸŠ': 4, // 4x 'šŸ‡': 6, // 6x 'šŸ””': 10, // 10x '⭐': 20, // 20x 'šŸ’Ž': 50 // 50x (jackpot) }, weights: { 'šŸ’': 25, 'šŸ‹': 20, 'šŸŠ': 20, 'šŸ‡': 15, 'šŸ””': 10, '⭐': 8, 'šŸ’Ž': 2 } }, lucky: { name: 'Lucky 7s', cost: 10, symbols: ['šŸŽ°', 'šŸ€', 'šŸŽ²', 'šŸŽÆ', 'šŸ’°', 'šŸ†', 'šŸ’Æ'], payouts: { 'šŸŽ°': 3, 'šŸ€': 5, 'šŸŽ²': 8, 'šŸŽÆ': 12, 'šŸ’°': 25, 'šŸ†': 50, 'šŸ’Æ': 100 // mega jackpot }, weights: { 'šŸŽ°': 30, 'šŸ€': 25, 'šŸŽ²': 20, 'šŸŽÆ': 15, 'šŸ’°': 6, 'šŸ†': 3, 'šŸ’Æ': 1 } }, treasure: { name: 'Treasure Hunt', cost: 20, symbols: ['šŸ—ļø', 'āš“', 'šŸ“ā€ā˜ ļø', 'šŸ’°', 'šŸ‘‘', 'šŸ†', 'šŸ’Ž'], payouts: { 'šŸ—ļø': 4, 'āš“': 6, 'šŸ“ā€ā˜ ļø': 10, 'šŸ’°': 20, 'šŸ‘‘': 40, 'šŸ†': 80, 'šŸ’Ž': 200 // treasure jackpot }, weights: { 'šŸ—ļø': 35, 'āš“': 25, 'šŸ“ā€ā˜ ļø': 20, 'šŸ’°': 12, 'šŸ‘‘': 5, 'šŸ†': 2, 'šŸ’Ž': 1 } } }; // Set up score file path this.scoresFile = path.join(__dirname, 'scratchcard_scores.json'); // Load existing scores this.loadScores(); }, cleanup(bot) { console.log('Scratch Cards Casino plugin cleaned up'); // Save scores before cleanup this.saveScores(); }, // Load scores from file loadScores() { try { if (fs.existsSync(this.scoresFile)) { const data = fs.readFileSync(this.scoresFile, 'utf8'); const scoresObject = JSON.parse(data); // Convert back to Map this.playerStats = new Map(Object.entries(scoresObject)); console.log(`šŸŽ« Loaded ${this.playerStats.size} scratch card player stats from ${this.scoresFile}`); // Log top 3 players if any exist if (this.playerStats.size > 0) { const topPlayers = Array.from(this.playerStats.values()) .sort((a, b) => b.totalWins - a.totalWins) .slice(0, 3); console.log('šŸ† Top scratchers:', topPlayers.map(p => `${p.name}(${p.totalWins})`).join(', ')); } } else { console.log(`šŸŽ« No existing scratch card scores file found, starting fresh`); } } catch (error) { console.error(`āŒ Error loading scratch card scores:`, error); this.playerStats = new Map(); // Reset to empty if load fails } }, // Save scores to file saveScores() { try { // Convert Map to plain object for JSON serialization const scoresObject = Object.fromEntries(this.playerStats); const data = JSON.stringify(scoresObject, null, 2); fs.writeFileSync(this.scoresFile, data, 'utf8'); console.log(`šŸ’¾ Saved ${this.playerStats.size} scratch card player stats to ${this.scoresFile}`); } catch (error) { console.error(`āŒ Error saving scratch card scores:`, error); } }, // Update a player's persistent stats and save to file updatePlayerStats(nick, updates) { // Ensure playerStats Map exists if (!this.playerStats) { this.playerStats = new Map(); } if (!this.playerStats.has(nick)) { this.playerStats.set(nick, { totalWins: 0, totalSpent: 0, totalCards: 0, biggestWin: 0, jackpots: 0, name: nick }); } const player = this.playerStats.get(nick); Object.assign(player, updates); // Save to file after each update this.saveScores(); }, // Generate random symbol based on weights getRandomSymbol(cardType) { const weights = cardType.weights; const totalWeight = Object.values(weights).reduce((sum, weight) => sum + weight, 0); let random = Math.random() * totalWeight; for (const [symbol, weight] of Object.entries(weights)) { random -= weight; if (random <= 0) { return symbol; } } return Object.keys(weights)[0]; // fallback }, // Create a scratch card (3x3 grid) createCard(cardType) { const card = []; for (let i = 0; i < 9; i++) { card.push(this.getRandomSymbol(cardType)); } return card; }, // Check for winning combinations checkWinnings(card, cardType) { const winnings = []; // Check rows (3 in a row) for (let row = 0; row < 3; row++) { const start = row * 3; const symbols = [card[start], card[start + 1], card[start + 2]]; if (symbols[0] === symbols[1] && symbols[1] === symbols[2]) { const payout = cardType.payouts[symbols[0]] || 0; winnings.push({ type: 'row', symbol: symbols[0], payout: payout, positions: [start, start + 1, start + 2] }); } } // Check columns (3 in a column) for (let col = 0; col < 3; col++) { const symbols = [card[col], card[col + 3], card[col + 6]]; if (symbols[0] === symbols[1] && symbols[1] === symbols[2]) { const payout = cardType.payouts[symbols[0]] || 0; winnings.push({ type: 'column', symbol: symbols[0], payout: payout, positions: [col, col + 3, col + 6] }); } } // Check diagonals const diag1 = [card[0], card[4], card[8]]; if (diag1[0] === diag1[1] && diag1[1] === diag1[2]) { const payout = cardType.payouts[diag1[0]] || 0; winnings.push({ type: 'diagonal', symbol: diag1[0], payout: payout, positions: [0, 4, 8] }); } const diag2 = [card[2], card[4], card[6]]; if (diag2[0] === diag2[1] && diag2[1] === diag2[2]) { const payout = cardType.payouts[diag2[0]] || 0; winnings.push({ type: 'diagonal', symbol: diag2[0], payout: payout, positions: [2, 4, 6] }); } return winnings; }, // Format card display formatCard(card) { return `ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”\n│ ${card[0]} │ ${card[1]} │ ${card[2]} │\nā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤\n│ ${card[3]} │ ${card[4]} │ ${card[5]} │\nā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤\n│ ${card[6]} │ ${card[7]} │ ${card[8]} │\nā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜`; }, commands: [ { name: 'scratch', description: 'Buy and scratch a lottery card', execute: function(context, bot) { const plugin = module.exports; const target = context.replyTo; const from = context.nick; const args = context.args; // Parse card type let cardTypeName = 'classic'; if (args.length > 0) { cardTypeName = args[0].toLowerCase(); } const cardType = plugin.cardTypes[cardTypeName]; if (!cardType) { const validTypes = Object.keys(plugin.cardTypes).join(', '); bot.say(target, `${from}: Invalid card type. Choose: ${validTypes}`); return; } // Initialize player if new if (!plugin.playerStats.has(from)) { plugin.updatePlayerStats(from, { totalWins: 0, totalSpent: 0, totalCards: 0, biggestWin: 0, jackpots: 0, name: from }); } // Create and scratch the card const card = plugin.createCard(cardType); const winnings = plugin.checkWinnings(card, cardType); // Calculate total winnings const totalPayout = winnings.reduce((sum, win) => sum + win.payout, 0); const netWin = totalPayout - cardType.cost; // Update player stats const player = plugin.playerStats.get(from); const updates = { totalCards: player.totalCards + 1, totalSpent: player.totalSpent + cardType.cost }; if (totalPayout > 0) { updates.totalWins = player.totalWins + totalPayout; if (totalPayout > player.biggestWin) { updates.biggestWin = totalPayout; } // Check for jackpot (high value wins) const isJackpot = winnings.some(win => win.payout >= 50); if (isJackpot) { updates.jackpots = player.jackpots + 1; } } // Show card with simple format (no ASCII art for IRC) const cardDisplay = `[${card[0]}][${card[1]}][${card[2]}] [${card[3]}][${card[4]}][${card[5]}] [${card[6]}][${card[7]}][${card[8]}]`; let output = `šŸŽ« ${from}: ${cardType.name} Card | ${cardDisplay}`; if (winnings.length > 0) { if (totalPayout >= 50) { output += ` | šŸŽ‰ JACKPOT! šŸŽ‰`; } const winDesc = winnings.map(win => `${win.symbol}x3=${win.payout}`).join(' '); output += ` | āœ… WIN: ${winDesc} = ${totalPayout} pts`; if (netWin > 0) { output += ` (profit: +${netWin})`; } } else { output += ` | āŒ No matches (cost: -${cardType.cost})`; } // Add running totals const newTotal = updates.totalWins || player.totalWins; const newSpent = updates.totalSpent; const profit = newTotal - newSpent; output += ` | Total: ${newTotal} spent: ${newSpent} profit: ${profit}`; bot.say(target, output); plugin.updatePlayerStats(from, updates); } }, { name: 'scratchinfo', description: 'Show scratch card types and payouts', execute: function(context, bot) { const plugin = module.exports; const target = context.replyTo; let output = 'šŸŽ« SCRATCH CARDS: '; const cardDescs = Object.entries(plugin.cardTypes).map(([key, cardType]) => { const topSymbols = Object.entries(cardType.payouts) .sort((a, b) => b[1] - a[1]) .slice(0, 3) .map(([symbol, payout]) => `${symbol}=${payout}x`) .join(' '); return `${cardType.name}(${cardType.cost}pts): ${topSymbols}`; }); output += cardDescs.join(' | '); output += ' | šŸŽÆ Get 3-in-a-row to win!'; bot.say(target, output); } }, { name: 'scratchstats', description: 'Show your scratch card statistics', execute: function(context, bot) { const plugin = module.exports; const target = context.replyTo; const from = context.nick; if (!plugin.playerStats.has(from)) { bot.say(target, `${from}: You haven't scratched any cards yet! Use !scratch to start.`); return; } const player = plugin.playerStats.get(from); const profit = player.totalWins - player.totalSpent; const winRate = player.totalCards > 0 ? ((player.totalWins / player.totalSpent) * 100).toFixed(1) : 0; bot.say(target, `šŸŽ« ${from}: ${player.totalCards} cards | Won: ${player.totalWins} | Spent: ${player.totalSpent} | Profit: ${profit} | ROI: ${winRate}% | Best: ${player.biggestWin} | Jackpots: ${player.jackpots}`); } }, { name: 'topscratch', description: 'Show top scratch card players', execute: function(context, bot) { const plugin = module.exports; const target = context.replyTo; if (plugin.playerStats.size === 0) { bot.say(target, 'No scratch card scores recorded yet! Use !scratch to start playing.'); return; } // Get top players by profit const byProfit = Array.from(plugin.playerStats.values()) .map(p => ({ ...p, profit: p.totalWins - p.totalSpent })) .sort((a, b) => b.profit - a.profit) .slice(0, 3); const byJackpots = Array.from(plugin.playerStats.values()) .sort((a, b) => b.jackpots - a.jackpots) .slice(0, 1); let output = 'šŸ† Top Scratchers: '; byProfit.forEach((player, i) => { const trophy = i === 0 ? 'šŸ„‡' : i === 1 ? '🄈' : 'šŸ„‰'; output += `${trophy}${player.name}(${player.profit > 0 ? '+' : ''}${player.profit}) `; }); if (byJackpots[0] && byJackpots[0].jackpots > 0) { output += `| šŸŽ° Most Jackpots: ${byJackpots[0].name}(${byJackpots[0].jackpots})`; } bot.say(target, output); } }, { name: 'resetscratch', description: 'Reset all scratch card scores (admin command)', execute: function(context, bot) { const plugin = module.exports; const target = context.replyTo; const from = context.nick; // Simple admin check const adminNicks = ['admin', 'owner', 'cancerbot', 'megasconed']; if (!adminNicks.includes(from)) { bot.say(target, `${from}: Access denied - admin only command`); return; } const playerCount = plugin.playerStats.size; plugin.playerStats.clear(); plugin.saveScores(); bot.say(target, `šŸ—‘ļø ${from}: Reset ${playerCount} scratch card player stats`); } } ] };