megabot/plugins/quiplash_basic.js
megaproxy a3ed25f8dd 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>
2025-07-17 19:03:45 +00:00

517 lines
No EOL
20 KiB
JavaScript

// plugins/quiplash.js - Quiplash game for IRC
module.exports = {
init(bot) {
console.log('🎮 Quiplash plugin initialized');
this.bot = bot;
// ================================
// EASY CONFIG - EDIT THIS SECTION
// ================================
this.config = {
gameChannel: '#quiplash', // ONLY channel where game works
minPlayers: 3, // Minimum players to start
maxPlayers: 8, // Maximum players allowed
promptTimeout: 120000, // 2 minutes to submit answers (in ms)
votingTimeout: 60000, // 1 minute to vote (in ms)
joinTimeout: 30000 // 30 seconds to join game (in ms)
};
// Game state
this.gameState = {
phase: 'idle', // 'idle', 'joining', 'prompts', 'voting', 'results'
players: [], // Array of player nicknames
currentPrompt: null, // Current prompt being voted on
answers: new Map(), // nick -> answer text
votes: new Map(), // nick -> voted_for_nick
scores: new Map(), // nick -> total_score
promptIndex: 0, // Which prompt we're on
timers: {
join: null,
prompt: null,
voting: null
}
};
// Sample prompts for basic version
this.prompts = [
"The worst superhero power: _____",
"A rejected Netflix series: _____",
"What aliens probably think about humans: _____",
"The most useless app idea: _____",
"A terrible name for a restaurant: _____",
"The worst thing to find in your pocket: _____",
"A bad slogan for a dating site: _____",
"The weirdest thing to collect: _____",
"A terrible excuse for being late: _____",
"The worst fortune cookie message: _____",
"A rejected crayon color: _____",
"The most awkward place to run into your ex: _____",
"A bad name for a pet: _____",
"The worst thing to hear from a pilot: _____",
"A terrible superhero catchphrase: _____"
];
// Track who we've sent prompts to
this.promptsSent = new Set();
this.answersReceived = new Set();
},
cleanup(bot) {
console.log('🎮 Quiplash plugin cleaned up');
this.clearAllTimers();
this.resetGame();
},
// Clear all active timers
clearAllTimers() {
Object.values(this.gameState.timers).forEach(timer => {
if (timer) clearTimeout(timer);
});
this.gameState.timers = { join: null, prompt: null, voting: null };
},
// Reset game to idle state
resetGame() {
this.clearAllTimers();
this.gameState = {
phase: 'idle',
players: [],
currentPrompt: null,
answers: new Map(),
votes: new Map(),
scores: new Map(),
promptIndex: 0,
timers: { join: null, prompt: null, voting: null }
};
this.promptsSent.clear();
this.answersReceived.clear();
},
// Check if command is in the correct channel
isValidChannel(context) {
return context.channel === this.config.gameChannel;
},
// Start the join phase
startJoinPhase(context) {
this.resetGame();
this.gameState.phase = 'joining';
this.gameState.players = [context.nick];
this.bot.say(this.config.gameChannel, `🎮 ${context.nick} started a Quiplash game!`);
this.bot.say(this.config.gameChannel, `💬 Type !join to play! Need ${this.config.minPlayers}-${this.config.maxPlayers} players.`);
this.bot.say(this.config.gameChannel, `⏰ 30 seconds to join...`);
// Set join timer
this.gameState.timers.join = setTimeout(() => {
this.startGameIfReady();
}, this.config.joinTimeout);
},
// Add player to game
addPlayer(nick) {
if (this.gameState.players.includes(nick)) {
return { success: false, message: `${nick}: You're already in the game!` };
}
if (this.gameState.players.length >= this.config.maxPlayers) {
return { success: false, message: `${nick}: Game is full! (${this.config.maxPlayers} players max)` };
}
this.gameState.players.push(nick);
this.gameState.scores.set(nick, 0);
return {
success: true,
message: `🎮 ${nick} joined! (${this.gameState.players.length}/${this.config.maxPlayers} players)`
};
},
// Remove player from game
removePlayer(nick) {
const index = this.gameState.players.indexOf(nick);
if (index === -1) {
return { success: false, message: `${nick}: You're not in the game!` };
}
this.gameState.players.splice(index, 1);
this.gameState.scores.delete(nick);
this.gameState.answers.delete(nick);
this.gameState.votes.delete(nick);
return {
success: true,
message: `👋 ${nick} left the game. (${this.gameState.players.length} players remaining)`
};
},
// Check if we can start the game
startGameIfReady() {
if (this.gameState.players.length < this.config.minPlayers) {
this.bot.say(this.config.gameChannel, `😞 Not enough players (${this.gameState.players.length}/${this.config.minPlayers}). Game cancelled.`);
this.resetGame();
return;
}
this.startPromptPhase();
},
// Start the prompt phase
startPromptPhase() {
this.gameState.phase = 'prompts';
this.gameState.answers.clear();
this.promptsSent.clear();
this.answersReceived.clear();
// Pick a random prompt
const promptText = this.prompts[Math.floor(Math.random() * this.prompts.length)];
this.gameState.currentPrompt = promptText;
this.bot.say(this.config.gameChannel, `🎯 Round starting! Check your PMs for the prompt.`);
this.bot.say(this.config.gameChannel, `⏰ You have 2 minutes to submit your answer!`);
// Send prompt to all players via PM
this.gameState.players.forEach(player => {
this.bot.say(player, `🎯 Quiplash Prompt: "${promptText}"`);
this.bot.say(player, `💬 Reply with: !answer <your funny response>`);
this.promptsSent.add(player);
});
// Set prompt timer
this.gameState.timers.prompt = setTimeout(() => {
this.startVotingPhase();
}, this.config.promptTimeout);
},
// Handle answer submission
submitAnswer(nick, answerText) {
if (this.gameState.phase !== 'prompts') {
return { success: false, message: 'Not accepting answers right now!' };
}
if (!this.gameState.players.includes(nick)) {
return { success: false, message: 'You\'re not in the current game!' };
}
if (this.gameState.answers.has(nick)) {
return { success: false, message: 'You already submitted an answer!' };
}
if (!answerText || answerText.trim().length === 0) {
return { success: false, message: 'Answer cannot be empty!' };
}
if (answerText.length > 100) {
return { success: false, message: 'Answer too long! Keep it under 100 characters.' };
}
this.gameState.answers.set(nick, answerText.trim());
this.answersReceived.add(nick);
// Check if all players have answered
if (this.answersReceived.size === this.gameState.players.length) {
clearTimeout(this.gameState.timers.prompt);
this.startVotingPhase();
}
return {
success: true,
message: `✅ Answer submitted! (${this.answersReceived.size}/${this.gameState.players.length} received)`
};
},
// Start voting phase
startVotingPhase() {
if (this.gameState.answers.size === 0) {
this.bot.say(this.config.gameChannel, `😞 No one submitted answers! Game ended.`);
this.resetGame();
return;
}
this.gameState.phase = 'voting';
this.gameState.votes.clear();
this.bot.say(this.config.gameChannel, `🗳️ VOTING TIME!`);
this.bot.say(this.config.gameChannel, `📝 Prompt: "${this.gameState.currentPrompt}"`);
this.bot.say(this.config.gameChannel, `📋 Answers:`);
// Display all answers with numbers
const answers = Array.from(this.gameState.answers.entries());
answers.forEach(([author, answer], index) => {
this.bot.say(this.config.gameChannel, `${index + 1}) ${answer}`);
});
this.bot.say(this.config.gameChannel, `🗳️ Vote with: !vote <number> (You have 1 minute!)`);
this.bot.say(this.config.gameChannel, `⚠️ You cannot vote for your own answer!`);
// Set voting timer
this.gameState.timers.voting = setTimeout(() => {
this.showResults();
}, this.config.votingTimeout);
},
// Handle vote submission
submitVote(nick, voteNumber) {
if (this.gameState.phase !== 'voting') {
return { success: false, message: 'Not accepting votes right now!' };
}
if (!this.gameState.players.includes(nick)) {
return { success: false, message: 'You\'re not in the current game!' };
}
if (this.gameState.votes.has(nick)) {
return { success: false, message: 'You already voted!' };
}
const answers = Array.from(this.gameState.answers.entries());
if (voteNumber < 1 || voteNumber > answers.length) {
return { success: false, message: `Invalid vote! Choose 1-${answers.length}` };
}
const [votedForNick, votedForAnswer] = answers[voteNumber - 1];
// Check if voting for themselves
if (votedForNick === nick) {
return { success: false, message: 'You cannot vote for your own answer!' };
}
this.gameState.votes.set(nick, votedForNick);
// Check if all players have voted
const eligibleVoters = this.gameState.players.filter(p => this.gameState.answers.has(p));
if (this.gameState.votes.size === eligibleVoters.length) {
clearTimeout(this.gameState.timers.voting);
this.showResults();
}
return {
success: true,
message: `✅ Vote recorded! (${this.gameState.votes.size}/${eligibleVoters.length} votes)`
};
},
// Show results and end game
showResults() {
this.gameState.phase = 'results';
// Count votes
const voteCount = new Map();
for (const votedFor of this.gameState.votes.values()) {
voteCount.set(votedFor, (voteCount.get(votedFor) || 0) + 1);
}
// Update scores
for (const [nick, votes] of voteCount.entries()) {
const currentScore = this.gameState.scores.get(nick) || 0;
this.gameState.scores.set(nick, currentScore + votes);
}
this.bot.say(this.config.gameChannel, `🏆 RESULTS!`);
this.bot.say(this.config.gameChannel, `📝 Prompt: "${this.gameState.currentPrompt}"`);
// Show answers with vote counts and authors
const answers = Array.from(this.gameState.answers.entries());
answers.forEach(([author, answer]) => {
const votes = voteCount.get(author) || 0;
const voteText = votes === 1 ? '1 vote' : `${votes} votes`;
this.bot.say(this.config.gameChannel, `• "${answer}" - ${author} (${voteText})`);
});
// Show current scores
this.bot.say(this.config.gameChannel, `📊 Scores:`);
const sortedScores = Array.from(this.gameState.scores.entries())
.sort((a, b) => b[1] - a[1]);
sortedScores.forEach(([nick, score], index) => {
const position = index === 0 ? '🥇' : index === 1 ? '🥈' : index === 2 ? '🥉' : `${index + 1}.`;
this.bot.say(this.config.gameChannel, `${position} ${nick}: ${score} points`);
});
this.bot.say(this.config.gameChannel, `🎮 Game complete! Use !quiplash to play again.`);
// Reset game after showing results
setTimeout(() => {
this.resetGame();
}, 5000);
},
commands: [
{
name: 'quiplash',
description: 'Start a new Quiplash game or show current status',
execute: function(context, bot) {
const plugin = module.exports;
const target = context.replyTo;
const from = context.nick;
// Check if in correct channel
if (!plugin.isValidChannel(context)) {
bot.say(target, `${from}: Quiplash only works in ${plugin.config.gameChannel}!`);
return;
}
switch (plugin.gameState.phase) {
case 'idle':
plugin.startJoinPhase(context);
break;
case 'joining':
const timeLeft = Math.ceil((plugin.config.joinTimeout - (Date.now() - Date.now())) / 1000);
bot.say(target, `🎮 Game starting soon! Players: ${plugin.gameState.players.join(', ')}`);
bot.say(target, `💬 Type !join to play!`);
break;
case 'prompts':
bot.say(target, `🎯 Game in progress! Waiting for answers to: "${plugin.gameState.currentPrompt}"`);
bot.say(target, `📊 Received: ${plugin.answersReceived.size}/${plugin.gameState.players.length}`);
break;
case 'voting':
bot.say(target, `🗳️ Voting in progress! Use !vote <number>`);
const eligibleVoters = plugin.gameState.players.filter(p => plugin.gameState.answers.has(p));
bot.say(target, `📊 Votes: ${plugin.gameState.votes.size}/${eligibleVoters.length}`);
break;
case 'results':
bot.say(target, `🏆 Game finishing up! New game starting soon...`);
break;
}
}
},
{
name: 'join',
description: 'Join the current Quiplash game',
execute: function(context, bot) {
const plugin = module.exports;
const target = context.replyTo;
const from = context.nick;
if (!plugin.isValidChannel(context)) {
bot.say(target, `${from}: Quiplash only works in ${plugin.config.gameChannel}!`);
return;
}
if (plugin.gameState.phase !== 'joining') {
bot.say(target, `${from}: No game to join! Use !quiplash to start one.`);
return;
}
const result = plugin.addPlayer(from);
bot.say(target, result.message);
}
},
{
name: 'leave',
description: 'Leave the current Quiplash game',
execute: function(context, bot) {
const plugin = module.exports;
const target = context.replyTo;
const from = context.nick;
if (!plugin.isValidChannel(context)) {
bot.say(target, `${from}: Quiplash only works in ${plugin.config.gameChannel}!`);
return;
}
if (plugin.gameState.phase === 'idle') {
bot.say(target, `${from}: No active game to leave!`);
return;
}
const result = plugin.removePlayer(from);
bot.say(target, result.message);
// Check if too few players remain
if (plugin.gameState.players.length < plugin.config.minPlayers && plugin.gameState.phase !== 'idle') {
bot.say(target, `😞 Too few players remaining. Game cancelled.`);
plugin.resetGame();
}
}
},
{
name: 'vote',
description: 'Vote for an answer during voting phase',
execute: function(context, bot) {
const plugin = module.exports;
const target = context.replyTo;
const from = context.nick;
const args = context.args;
if (!plugin.isValidChannel(context)) {
return; // Silently ignore votes in wrong channel
}
if (args.length === 0) {
bot.say(target, `${from}: Usage: !vote <number>`);
return;
}
const voteNumber = parseInt(args[0]);
if (isNaN(voteNumber)) {
bot.say(target, `${from}: Vote must be a number!`);
return;
}
const result = plugin.submitVote(from, voteNumber);
if (!result.success) {
bot.say(target, `${from}: ${result.message}`);
} else {
// Send confirmation via PM to avoid spam
bot.say(from, result.message);
}
}
},
{
name: 'answer',
description: 'Submit your answer (use in PM)',
execute: function(context, bot) {
const plugin = module.exports;
const target = context.replyTo;
const from = context.nick;
const args = context.args;
// This command should only work in PM
if (context.channel) {
bot.say(target, `${from}: Send your answer via PM! Type /msg ${bot.config.nick} !answer <your response>`);
return;
}
if (args.length === 0) {
bot.say(target, 'Usage: !answer <your funny response>');
return;
}
const answer = args.join(' ');
const result = plugin.submitAnswer(from, answer);
bot.say(target, result.message);
}
},
{
name: 'players',
description: 'Show current players in the game',
execute: function(context, bot) {
const plugin = module.exports;
const target = context.replyTo;
const from = context.nick;
if (!plugin.isValidChannel(context)) {
bot.say(target, `${from}: Quiplash only works in ${plugin.config.gameChannel}!`);
return;
}
if (plugin.gameState.players.length === 0) {
bot.say(target, 'No active game. Use !quiplash to start one!');
} else {
bot.say(target, `🎮 Players (${plugin.gameState.players.length}): ${plugin.gameState.players.join(', ')}`);
}
}
}
]
};