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
328
plugins/slots_plugin.js
Normal file
328
plugins/slots_plugin.js
Normal file
|
|
@ -0,0 +1,328 @@
|
|||
// plugins/slots.js - Slot Machine game plugin
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
init(bot) {
|
||||
console.log('Slot Machine plugin initialized');
|
||||
this.bot = bot;
|
||||
|
||||
// Player scores storage
|
||||
this.playerScores = new Map(); // nick -> { totalWins, biggestWin, totalSpins, jackpots, name }
|
||||
|
||||
// Slot machine symbols with weights and payouts
|
||||
this.symbols = [
|
||||
{ emoji: '🍒', name: 'Cherry', weight: 25, payout: 2 },
|
||||
{ emoji: '🍊', name: 'Orange', weight: 20, payout: 3 },
|
||||
{ emoji: '🍋', name: 'Lemon', weight: 20, payout: 3 },
|
||||
{ emoji: '🍇', name: 'Grapes', weight: 15, payout: 5 },
|
||||
{ emoji: '🔔', name: 'Bell', weight: 10, payout: 10 },
|
||||
{ emoji: '⭐', name: 'Star', weight: 6, payout: 15 },
|
||||
{ emoji: '💎', name: 'Diamond', weight: 3, payout: 25 },
|
||||
{ emoji: '🎰', name: 'Jackpot', weight: 1, payout: 100 }
|
||||
];
|
||||
|
||||
// Set up score file path
|
||||
this.scoresFile = path.join(__dirname, 'slots_scores.json');
|
||||
|
||||
// Load existing scores
|
||||
this.loadScores();
|
||||
},
|
||||
|
||||
cleanup(bot) {
|
||||
console.log('Slot Machine 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.playerScores = new Map(Object.entries(scoresObject));
|
||||
console.log(`🎰 Loaded ${this.playerScores.size} slot player scores from ${this.scoresFile}`);
|
||||
|
||||
// Log top 3 players if any exist
|
||||
if (this.playerScores.size > 0) {
|
||||
const topPlayers = Array.from(this.playerScores.values())
|
||||
.sort((a, b) => b.totalWins - a.totalWins)
|
||||
.slice(0, 3);
|
||||
console.log('🏆 Top slot players:', topPlayers.map(p => `${p.name}(${p.totalWins})`).join(', '));
|
||||
}
|
||||
} else {
|
||||
console.log(`🎰 No existing slot scores file found, starting fresh`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ Error loading slot scores:`, error);
|
||||
this.playerScores = 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.playerScores);
|
||||
const data = JSON.stringify(scoresObject, null, 2);
|
||||
|
||||
fs.writeFileSync(this.scoresFile, data, 'utf8');
|
||||
console.log(`💾 Saved ${this.playerScores.size} slot player scores to ${this.scoresFile}`);
|
||||
} catch (error) {
|
||||
console.error(`❌ Error saving slot scores:`, error);
|
||||
}
|
||||
},
|
||||
|
||||
// Update a player's score and save to file
|
||||
updatePlayerScore(nick, updates) {
|
||||
if (!this.playerScores.has(nick)) {
|
||||
this.playerScores.set(nick, {
|
||||
totalWins: 0,
|
||||
biggestWin: 0,
|
||||
totalSpins: 0,
|
||||
jackpots: 0,
|
||||
name: nick
|
||||
});
|
||||
}
|
||||
|
||||
const player = this.playerScores.get(nick);
|
||||
Object.assign(player, updates);
|
||||
|
||||
// Save to file after each update
|
||||
this.saveScores();
|
||||
},
|
||||
|
||||
// Weighted random symbol selection
|
||||
getRandomSymbol() {
|
||||
const totalWeight = this.symbols.reduce((sum, symbol) => sum + symbol.weight, 0);
|
||||
let random = Math.random() * totalWeight;
|
||||
|
||||
for (const symbol of this.symbols) {
|
||||
random -= symbol.weight;
|
||||
if (random <= 0) {
|
||||
return symbol;
|
||||
}
|
||||
}
|
||||
return this.symbols[0]; // fallback
|
||||
},
|
||||
|
||||
// Spin the slot machine
|
||||
spinSlots() {
|
||||
const reel1 = this.getRandomSymbol();
|
||||
const reel2 = this.getRandomSymbol();
|
||||
const reel3 = this.getRandomSymbol();
|
||||
|
||||
return { reel1, reel2, reel3 };
|
||||
},
|
||||
|
||||
// Calculate winnings based on the spin result
|
||||
calculateWinnings(spin) {
|
||||
const { reel1, reel2, reel3 } = spin;
|
||||
|
||||
// Check for three of a kind (jackpot)
|
||||
if (reel1.emoji === reel2.emoji && reel2.emoji === reel3.emoji) {
|
||||
return {
|
||||
type: 'triple',
|
||||
amount: reel1.payout,
|
||||
symbol: reel1,
|
||||
message: `🎉 TRIPLE ${reel1.name.toUpperCase()}! 🎉`
|
||||
};
|
||||
}
|
||||
|
||||
// Check for two of a kind
|
||||
if (reel1.emoji === reel2.emoji || reel2.emoji === reel3.emoji || reel1.emoji === reel3.emoji) {
|
||||
let matchSymbol;
|
||||
if (reel1.emoji === reel2.emoji) matchSymbol = reel1;
|
||||
else if (reel2.emoji === reel3.emoji) matchSymbol = reel2;
|
||||
else matchSymbol = reel1;
|
||||
|
||||
const payout = Math.floor(matchSymbol.payout / 3); // Reduced payout for pairs
|
||||
if (payout > 0) {
|
||||
return {
|
||||
type: 'pair',
|
||||
amount: payout,
|
||||
symbol: matchSymbol,
|
||||
message: `✨ Pair of ${matchSymbol.name}s! ✨`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// No match
|
||||
return {
|
||||
type: 'none',
|
||||
amount: 0,
|
||||
symbol: null,
|
||||
message: '💸 No match, try again!'
|
||||
};
|
||||
},
|
||||
|
||||
commands: [
|
||||
{
|
||||
name: 'slots',
|
||||
description: 'Spin the slot machine!',
|
||||
execute: function(context, bot) {
|
||||
const plugin = module.exports;
|
||||
const target = context.replyTo;
|
||||
const from = context.nick;
|
||||
|
||||
// Initialize player if new
|
||||
if (!plugin.playerScores.has(from)) {
|
||||
plugin.updatePlayerScore(from, {
|
||||
totalWins: 0,
|
||||
biggestWin: 0,
|
||||
totalSpins: 0,
|
||||
jackpots: 0,
|
||||
name: from
|
||||
});
|
||||
}
|
||||
|
||||
// Spin the slots
|
||||
const spin = plugin.spinSlots();
|
||||
const result = plugin.calculateWinnings(spin);
|
||||
|
||||
// Update player stats
|
||||
const player = plugin.playerScores.get(from);
|
||||
const updates = {
|
||||
totalSpins: player.totalSpins + 1
|
||||
};
|
||||
|
||||
let output = `🎰 ${from}: [${spin.reel1.emoji}|${spin.reel2.emoji}|${spin.reel3.emoji}] `;
|
||||
|
||||
if (result.amount > 0) {
|
||||
// Player won!
|
||||
updates.totalWins = player.totalWins + result.amount;
|
||||
|
||||
if (result.amount > player.biggestWin) {
|
||||
updates.biggestWin = result.amount;
|
||||
}
|
||||
|
||||
if (result.type === 'triple' && result.symbol.emoji === '🎰') {
|
||||
updates.jackpots = player.jackpots + 1;
|
||||
output += `🚨 JACKPOT! 🚨 `;
|
||||
} else if (result.amount >= 50) {
|
||||
output += `🔥 BIG WIN! 🔥 `;
|
||||
} else if (result.type === 'triple') {
|
||||
output += `🎉 TRIPLE! 🎉 `;
|
||||
} else {
|
||||
output += `✨ PAIR! ✨ `;
|
||||
}
|
||||
|
||||
output += `+${result.amount} pts | Total: ${updates.totalWins}`;
|
||||
} else {
|
||||
// Player lost
|
||||
output += `💸 No match | Spins: ${updates.totalSpins} | Total: ${player.totalWins}`;
|
||||
}
|
||||
|
||||
bot.say(target, output);
|
||||
plugin.updatePlayerScore(from, updates);
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'slotsstats',
|
||||
description: 'Show your slot machine statistics',
|
||||
execute: function(context, bot) {
|
||||
const plugin = module.exports;
|
||||
const target = context.replyTo;
|
||||
const from = context.nick;
|
||||
|
||||
if (!plugin.playerScores.has(from)) {
|
||||
bot.say(target, `${from}: You haven't played slots yet! Use !slots to start.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const player = plugin.playerScores.get(from);
|
||||
const avgPts = player.totalSpins > 0 ? (player.totalWins / player.totalSpins).toFixed(1) : 0;
|
||||
|
||||
bot.say(target, `🎰 ${from}: ${player.totalWins} total pts | Best: ${player.biggestWin} | Spins: ${player.totalSpins} | Jackpots: ${player.jackpots} | Avg: ${avgPts} pts/spin`);
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'topslots',
|
||||
description: 'Show top slot machine players',
|
||||
execute: function(context, bot) {
|
||||
const plugin = module.exports;
|
||||
const target = context.replyTo;
|
||||
|
||||
if (plugin.playerScores.size === 0) {
|
||||
bot.say(target, 'No slot scores recorded yet! Use !slots to start playing.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get top 3 players and biggest win
|
||||
const topPlayers = Array.from(plugin.playerScores.values())
|
||||
.sort((a, b) => b.totalWins - a.totalWins)
|
||||
.slice(0, 3);
|
||||
|
||||
const biggestWin = Array.from(plugin.playerScores.values())
|
||||
.sort((a, b) => b.biggestWin - a.biggestWin)[0];
|
||||
|
||||
let output = '🏆 Top Slots: ';
|
||||
topPlayers.forEach((player, i) => {
|
||||
const trophy = i === 0 ? '🥇' : i === 1 ? '🥈' : '🥉';
|
||||
output += `${trophy}${player.name}(${player.totalWins}) `;
|
||||
});
|
||||
|
||||
if (biggestWin && biggestWin.biggestWin > 0) {
|
||||
output += `| 🎰 Biggest Win: ${biggestWin.name}(${biggestWin.biggestWin})`;
|
||||
}
|
||||
|
||||
bot.say(target, output);
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'slotspayouts',
|
||||
description: 'Show slot machine payout table',
|
||||
execute: function(context, bot) {
|
||||
const plugin = module.exports;
|
||||
const target = context.replyTo;
|
||||
|
||||
// Create compact payout display
|
||||
let output = '🎰 Payouts: ';
|
||||
const sortedSymbols = [...plugin.symbols].sort((a, b) => b.payout - a.payout);
|
||||
|
||||
sortedSymbols.forEach((symbol, i) => {
|
||||
const pairPayout = Math.floor(symbol.payout / 3);
|
||||
if (i > 0) output += ' | ';
|
||||
|
||||
if (symbol.emoji === '🎰') {
|
||||
output += `${symbol.emoji}x3=${symbol.payout}(JACKPOT!)`;
|
||||
} else {
|
||||
output += `${symbol.emoji}x3=${symbol.payout} x2=${pairPayout}`;
|
||||
}
|
||||
});
|
||||
|
||||
bot.say(target, output);
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'resetslots',
|
||||
description: 'Reset all slot scores (admin command)',
|
||||
execute: function(context, bot) {
|
||||
const plugin = module.exports;
|
||||
const target = context.replyTo;
|
||||
const from = context.nick;
|
||||
|
||||
// Simple admin check - you can customize this list
|
||||
const adminNicks = ['admin', 'owner', 'cancerbot']; // Add your admin nicks here
|
||||
|
||||
if (!adminNicks.includes(from)) {
|
||||
bot.say(target, `${from}: Access denied - admin only command`);
|
||||
return;
|
||||
}
|
||||
|
||||
const playerCount = plugin.playerScores.size;
|
||||
plugin.playerScores.clear();
|
||||
plugin.saveScores();
|
||||
|
||||
bot.say(target, `🗑️ ${from}: Reset ${playerCount} slot player scores`);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue