Initial commit: Enhanced IRC bot with duck hunt game
- Added hunt/feed duck mechanics (80% hunt, 20% feed) - Implemented persistent scoring system - Added channel control commands (\!stopducks/\!startducks) - Enhanced duck hunt with wrong action penalties - Organized bot structure with botmain.js as main file - Added comprehensive documentation (README.md) - Included 17 plugins with various games and utilities 🦆 Duck Hunt Features: - Hunt ducks with \!shoot/\!bang (80% of spawns) - Feed ducks with \!feed (20% of spawns) - Persistent scores saved to JSON - Channel-specific controls for #bakedbeans - Reaction time tracking and special achievements 🎮 Other Games: - Casino games (slots, coinflip, hi-lo, scratch cards) - Multiplayer games (pigs, zombie dice, quiplash) - Text generation (babble, conspiracy, drunk historian) - Interactive features (story writing, emojify, combos) 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
commit
a3ed25f8dd
39 changed files with 12360 additions and 0 deletions
455
plugins/scratchcard_plugin(1).js
Normal file
455
plugins/scratchcard_plugin(1).js
Normal file
|
|
@ -0,0 +1,455 @@
|
|||
// 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`);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue