Add new IRC games and enhance bot functionality
- Add Rock Paper Scissors game with PvP and bot modes - Fixed syntax errors and improved game mechanics - PvP moves now require PM for secrecy - Add Word Scramble game with difficulty levels - Multiple word categories and persistent scoring - Enhance duck hunt with better statistics tracking - Separate points vs duck count tracking - Fixed migration logic issues - Add core rate limiting system (5 commands/30s) - Admin whitelist for megasconed - Automatic cleanup and unblocking - Improve reload functionality for hot-reloading plugins - Add channel-specific commands (\!stopducks/\!startducks) 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
a3ed25f8dd
commit
8552887b6c
9 changed files with 1536 additions and 30 deletions
594
plugins/rock_paper_scissors_plugin.js
Normal file
594
plugins/rock_paper_scissors_plugin.js
Normal file
|
|
@ -0,0 +1,594 @@
|
|||
// plugins/rock_paper_scissors_plugin.js - Rock Paper Scissors Game Plugin
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
gameState: {
|
||||
activeGames: new Map(), // channel -> game data
|
||||
challenges: new Map(), // channel -> challenge data
|
||||
scores: new Map() // nick -> score data
|
||||
},
|
||||
|
||||
// Game choices and their relationships
|
||||
choices: {
|
||||
'rock': {
|
||||
emoji: '🪨',
|
||||
beats: 'scissors',
|
||||
losesTo: 'paper'
|
||||
},
|
||||
'paper': {
|
||||
emoji: '📄',
|
||||
beats: 'rock',
|
||||
losesTo: 'scissors'
|
||||
},
|
||||
'scissors': {
|
||||
emoji: '✂️',
|
||||
beats: 'paper',
|
||||
losesTo: 'rock'
|
||||
}
|
||||
},
|
||||
|
||||
// Aliases for user input
|
||||
aliases: {
|
||||
'r': 'rock',
|
||||
'p': 'paper',
|
||||
's': 'scissors',
|
||||
'stone': 'rock',
|
||||
'scissor': 'scissors'
|
||||
},
|
||||
|
||||
init(bot) {
|
||||
console.log('Rock Paper Scissors plugin initialized - Ready to rumble! 🪨📄✂️');
|
||||
|
||||
// Set up score file path
|
||||
this.scoresFile = path.join(__dirname, 'rps_scores.json');
|
||||
|
||||
// Load existing scores
|
||||
this.loadScores();
|
||||
},
|
||||
|
||||
cleanup(bot) {
|
||||
console.log('Rock Paper Scissors plugin cleaned up');
|
||||
|
||||
// Save scores before cleanup
|
||||
this.saveScores();
|
||||
|
||||
// Clear all active games and timers
|
||||
this.gameState.activeGames.forEach(game => {
|
||||
if (game.timer) {
|
||||
clearTimeout(game.timer);
|
||||
}
|
||||
});
|
||||
this.gameState.activeGames.clear();
|
||||
|
||||
// Clear all challenges and timers
|
||||
this.gameState.challenges.forEach(challenge => {
|
||||
if (challenge.timer) {
|
||||
clearTimeout(challenge.timer);
|
||||
}
|
||||
});
|
||||
this.gameState.challenges.clear();
|
||||
},
|
||||
|
||||
commands: [
|
||||
{
|
||||
name: 'rps',
|
||||
description: 'Play Rock Paper Scissors (!rps vs bot OR !rps @player)',
|
||||
execute(context, bot) {
|
||||
module.exports.startGame(context, bot);
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'rock',
|
||||
description: 'Play rock in active RPS game',
|
||||
execute(context, bot) {
|
||||
module.exports.playMove(context, bot, 'rock');
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'paper',
|
||||
description: 'Play paper in active RPS game',
|
||||
execute(context, bot) {
|
||||
module.exports.playMove(context, bot, 'paper');
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'scissors',
|
||||
description: 'Play scissors in active RPS game',
|
||||
execute(context, bot) {
|
||||
module.exports.playMove(context, bot, 'scissors');
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'accept',
|
||||
description: 'Accept a Rock Paper Scissors challenge',
|
||||
execute(context, bot) {
|
||||
module.exports.acceptChallenge(context, bot);
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'rpsstats',
|
||||
description: 'Show your Rock Paper Scissors statistics',
|
||||
execute(context, bot) {
|
||||
module.exports.showPlayerStats(context, bot);
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'toprps',
|
||||
description: 'Show top Rock Paper Scissors players',
|
||||
execute(context, bot) {
|
||||
module.exports.showLeaderboard(context, bot);
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'rpsscore',
|
||||
description: 'Show your current RPS score',
|
||||
execute(context, bot) {
|
||||
module.exports.showScore(context, bot);
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
startGame(context, bot) {
|
||||
const channel = context.channel;
|
||||
if (!channel) {
|
||||
bot.say(context.replyTo, '🪨📄✂️ Rock Paper Scissors can only be played in channels!');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if there's already an active game
|
||||
if (this.gameState.activeGames.has(channel)) {
|
||||
bot.say(context.replyTo, '🪨📄✂️ There\'s already an active RPS game in this channel!');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if there's already a challenge
|
||||
if (this.gameState.challenges.has(channel)) {
|
||||
bot.say(context.replyTo, '🪨📄✂️ There\'s already a pending challenge in this channel!');
|
||||
return;
|
||||
}
|
||||
|
||||
const args = context.args;
|
||||
|
||||
// Check if challenging another player
|
||||
if (args.length > 0) {
|
||||
const target = args[0].replace('@', '');
|
||||
|
||||
if (target === context.nick) {
|
||||
bot.say(context.replyTo, '🪨📄✂️ You can\'t challenge yourself! Use !rps to play vs the bot.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (target === bot.config.nick) {
|
||||
// Play against bot
|
||||
this.startBotGame(context, bot, channel);
|
||||
return;
|
||||
}
|
||||
|
||||
// Challenge another player
|
||||
this.createChallenge(context, bot, channel, target);
|
||||
return;
|
||||
}
|
||||
|
||||
// Default: play against bot
|
||||
this.startBotGame(context, bot, channel);
|
||||
},
|
||||
|
||||
startBotGame(context, bot, channel) {
|
||||
const gameData = {
|
||||
type: 'bot',
|
||||
player: context.nick,
|
||||
startTime: Date.now(),
|
||||
playerMove: null,
|
||||
botMove: null
|
||||
};
|
||||
|
||||
// Set 30 second timer
|
||||
gameData.timer = setTimeout(() => {
|
||||
this.timeoutGame(channel, bot, gameData);
|
||||
}, 30000);
|
||||
|
||||
this.gameState.activeGames.set(channel, gameData);
|
||||
|
||||
bot.say(channel, `🪨📄✂️ ${context.nick} vs ${bot.config.nick}! Use !rock, !paper, or !scissors (30s limit)`);
|
||||
},
|
||||
|
||||
createChallenge(context, bot, channel, target) {
|
||||
const challengeData = {
|
||||
challenger: context.nick,
|
||||
target: target,
|
||||
startTime: Date.now()
|
||||
};
|
||||
|
||||
// Set 60 second timer for challenge acceptance
|
||||
challengeData.timer = setTimeout(() => {
|
||||
this.timeoutChallenge(channel, bot, challengeData);
|
||||
}, 60000);
|
||||
|
||||
this.gameState.challenges.set(channel, challengeData);
|
||||
|
||||
bot.say(channel, `🪨📄✂️ ${context.nick} challenges ${target} to Rock Paper Scissors! Use !accept to accept (60s limit)`);
|
||||
},
|
||||
|
||||
acceptChallenge(context, bot) {
|
||||
const channel = context.channel;
|
||||
if (!channel) {
|
||||
bot.say(context.replyTo, '🪨📄✂️ Challenges can only be accepted in channels!');
|
||||
return;
|
||||
}
|
||||
|
||||
const challenge = this.gameState.challenges.get(channel);
|
||||
if (!challenge) {
|
||||
bot.say(context.replyTo, '🪨📄✂️ No active challenge to accept!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.nick !== challenge.target) {
|
||||
bot.say(context.replyTo, `🪨📄✂️ This challenge is for ${challenge.target}, not you!`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear challenge timer
|
||||
clearTimeout(challenge.timer);
|
||||
this.gameState.challenges.delete(channel);
|
||||
|
||||
// Start player vs player game
|
||||
this.startPlayerGame(bot, channel, challenge.challenger, challenge.target);
|
||||
},
|
||||
|
||||
startPlayerGame(bot, channel, player1, player2) {
|
||||
const gameData = {
|
||||
type: 'player',
|
||||
player1: player1,
|
||||
player2: player2,
|
||||
startTime: Date.now(),
|
||||
moves: new Map(), // nick -> move
|
||||
revealed: false
|
||||
};
|
||||
|
||||
// Set 45 second timer
|
||||
gameData.timer = setTimeout(() => {
|
||||
this.timeoutGame(channel, bot, gameData);
|
||||
}, 45000);
|
||||
|
||||
this.gameState.activeGames.set(channel, gameData);
|
||||
|
||||
bot.say(channel, `🪨📄✂️ ${player1} vs ${player2}! Both players PM the bot your moves: /msg ${bot.config.nick} !rock, !paper, or !scissors (45s limit)`);
|
||||
},
|
||||
|
||||
playMove(context, bot, move) {
|
||||
const channel = context.channel;
|
||||
|
||||
// If in a channel (bot vs player games)
|
||||
if (channel) {
|
||||
const game = this.gameState.activeGames.get(channel);
|
||||
if (!game) {
|
||||
bot.say(context.replyTo, '🪨📄✂️ No active RPS game to play in!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (game.type === 'bot') {
|
||||
this.handleBotMove(context, bot, channel, game, move);
|
||||
} else {
|
||||
this.handlePlayerMove(context, bot, channel, game, move);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If in PM (player vs player games)
|
||||
// Find the game this player is in
|
||||
let foundGame = null;
|
||||
let foundChannel = null;
|
||||
|
||||
for (const [channelName, game] of this.gameState.activeGames) {
|
||||
if (game.type === 'player' && (game.player1 === context.nick || game.player2 === context.nick)) {
|
||||
foundGame = game;
|
||||
foundChannel = channelName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundGame) {
|
||||
bot.say(context.replyTo, '🪨📄✂️ You\'re not in an active player vs player game!');
|
||||
return;
|
||||
}
|
||||
|
||||
this.handlePlayerMove(context, bot, foundChannel, foundGame, move);
|
||||
},
|
||||
|
||||
handleBotMove(context, bot, channel, game, move) {
|
||||
if (context.nick !== game.player) {
|
||||
bot.say(context.replyTo, `🪨📄✂️ This game is for ${game.player}!`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (game.playerMove) {
|
||||
bot.say(context.replyTo, '🪨📄✂️ You already made your move!');
|
||||
return;
|
||||
}
|
||||
|
||||
// Player made their move
|
||||
game.playerMove = move;
|
||||
|
||||
// Bot makes random move
|
||||
const botChoices = ['rock', 'paper', 'scissors'];
|
||||
game.botMove = botChoices[Math.floor(Math.random() * botChoices.length)];
|
||||
|
||||
// Clear timer
|
||||
clearTimeout(game.timer);
|
||||
|
||||
// Determine winner and show result
|
||||
this.resolveGame(channel, bot, game);
|
||||
},
|
||||
|
||||
handlePlayerMove(context, bot, channel, game, move) {
|
||||
if (context.nick !== game.player1 && context.nick !== game.player2) {
|
||||
bot.say(context.replyTo, `🪨📄✂️ This game is for ${game.player1} and ${game.player2}!`);
|
||||
return;
|
||||
}
|
||||
|
||||
// For player vs player games, moves must be made via PM
|
||||
if (context.channel) {
|
||||
bot.say(context.replyTo, `🪨📄✂️ ${context.nick}: Send your move via PM to keep it secret! Use /msg ${bot.config.nick} !rock, !paper, or !scissors`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (game.moves.has(context.nick)) {
|
||||
bot.say(context.replyTo, '🪨📄✂️ You already made your move!');
|
||||
return;
|
||||
}
|
||||
|
||||
// Record move
|
||||
game.moves.set(context.nick, move);
|
||||
|
||||
// Send confirmation via private message
|
||||
bot.say(context.nick, `🪨📄✂️ You played ${this.choices[move].emoji} ${move}!`);
|
||||
|
||||
// Check if both players have moved
|
||||
if (game.moves.size === 2) {
|
||||
// Clear timer
|
||||
clearTimeout(game.timer);
|
||||
|
||||
// Resolve game
|
||||
this.resolveGame(channel, bot, game);
|
||||
} else {
|
||||
// Wait for other player
|
||||
const waitingFor = game.player1 === context.nick ? game.player2 : game.player1;
|
||||
bot.say(channel, `🪨📄✂️ Waiting for ${waitingFor} to make their move via PM...`);
|
||||
}
|
||||
},
|
||||
|
||||
resolveGame(channel, bot, game) {
|
||||
this.gameState.activeGames.delete(channel);
|
||||
|
||||
if (game.type === 'bot') {
|
||||
this.resolveBotGame(channel, bot, game);
|
||||
} else {
|
||||
this.resolvePlayerGame(channel, bot, game);
|
||||
}
|
||||
},
|
||||
|
||||
resolveBotGame(channel, bot, game) {
|
||||
const playerMove = game.playerMove;
|
||||
const botMove = game.botMove;
|
||||
|
||||
const playerChoice = this.choices[playerMove];
|
||||
const botChoice = this.choices[botMove];
|
||||
|
||||
// Show moves
|
||||
bot.say(channel, `🪨📄✂️ ${game.player}: ${playerChoice.emoji} | ${bot.config.nick}: ${botChoice.emoji}`);
|
||||
|
||||
// Determine result
|
||||
let result;
|
||||
if (playerMove === botMove) {
|
||||
result = 'draw';
|
||||
bot.say(channel, `🤝 It's a draw! Both played ${playerMove}!`);
|
||||
} else if (playerChoice.beats === botMove) {
|
||||
result = 'win';
|
||||
bot.say(channel, `🎉 ${game.player} wins! ${playerMove} beats ${botMove}!`);
|
||||
} else {
|
||||
result = 'loss';
|
||||
bot.say(channel, `🤖 ${bot.config.nick} wins! ${botMove} beats ${playerMove}!`);
|
||||
}
|
||||
|
||||
// Update scores
|
||||
this.updatePlayerScore(game.player, result, 'bot');
|
||||
this.saveScores();
|
||||
},
|
||||
|
||||
resolvePlayerGame(channel, bot, game) {
|
||||
const player1Move = game.moves.get(game.player1);
|
||||
const player2Move = game.moves.get(game.player2);
|
||||
|
||||
const player1Choice = this.choices[player1Move];
|
||||
const player2Choice = this.choices[player2Move];
|
||||
|
||||
// Show moves
|
||||
bot.say(channel, `🪨📄✂️ ${game.player1}: ${player1Choice.emoji} | ${game.player2}: ${player2Choice.emoji}`);
|
||||
|
||||
// Determine result
|
||||
if (player1Move === player2Move) {
|
||||
bot.say(channel, `🤝 It's a draw! Both played ${player1Move}!`);
|
||||
this.updatePlayerScore(game.player1, 'draw', 'player');
|
||||
this.updatePlayerScore(game.player2, 'draw', 'player');
|
||||
} else if (player1Choice.beats === player2Move) {
|
||||
bot.say(channel, `🎉 ${game.player1} wins! ${player1Move} beats ${player2Move}!`);
|
||||
this.updatePlayerScore(game.player1, 'win', 'player');
|
||||
this.updatePlayerScore(game.player2, 'loss', 'player');
|
||||
} else {
|
||||
bot.say(channel, `🎉 ${game.player2} wins! ${player2Move} beats ${player1Move}!`);
|
||||
this.updatePlayerScore(game.player2, 'win', 'player');
|
||||
this.updatePlayerScore(game.player1, 'loss', 'player');
|
||||
}
|
||||
|
||||
this.saveScores();
|
||||
},
|
||||
|
||||
timeoutGame(channel, bot, game) {
|
||||
this.gameState.activeGames.delete(channel);
|
||||
|
||||
if (game.type === 'bot') {
|
||||
bot.say(channel, `⏰ ${game.player} took too long to make a move! Game cancelled.`);
|
||||
} else {
|
||||
const missingPlayers = [];
|
||||
if (!game.moves.has(game.player1)) missingPlayers.push(game.player1);
|
||||
if (!game.moves.has(game.player2)) missingPlayers.push(game.player2);
|
||||
|
||||
bot.say(channel, `⏰ Game timed out! ${missingPlayers.join(' and ')} didn't make their moves.`);
|
||||
}
|
||||
},
|
||||
|
||||
timeoutChallenge(channel, bot, challenge) {
|
||||
this.gameState.challenges.delete(channel);
|
||||
bot.say(channel, `⏰ Challenge timed out! ${challenge.target} didn't accept the challenge.`);
|
||||
},
|
||||
|
||||
// Player score management
|
||||
initializePlayerData(nick) {
|
||||
if (!this.gameState.scores.has(nick)) {
|
||||
this.gameState.scores.set(nick, {
|
||||
totalPoints: 0,
|
||||
totalGames: 0,
|
||||
wins: 0,
|
||||
losses: 0,
|
||||
draws: 0,
|
||||
winStreak: 0,
|
||||
longestWinStreak: 0,
|
||||
vsBot: {
|
||||
wins: 0,
|
||||
losses: 0,
|
||||
draws: 0
|
||||
},
|
||||
vsPlayer: {
|
||||
wins: 0,
|
||||
losses: 0,
|
||||
draws: 0
|
||||
},
|
||||
favoriteMove: null,
|
||||
moveStats: {
|
||||
rock: 0,
|
||||
paper: 0,
|
||||
scissors: 0
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
updatePlayerScore(nick, result, gameType) {
|
||||
this.initializePlayerData(nick);
|
||||
const playerData = this.gameState.scores.get(nick);
|
||||
|
||||
// Update totals
|
||||
playerData.totalGames += 1;
|
||||
playerData[result + 's'] += 1;
|
||||
|
||||
// Update game type stats
|
||||
playerData[gameType === 'bot' ? 'vsBot' : 'vsPlayer'][result + 's'] += 1;
|
||||
|
||||
// Update points and streaks
|
||||
if (result === 'win') {
|
||||
playerData.totalPoints += 3;
|
||||
playerData.winStreak += 1;
|
||||
if (playerData.winStreak > playerData.longestWinStreak) {
|
||||
playerData.longestWinStreak = playerData.winStreak;
|
||||
}
|
||||
} else if (result === 'draw') {
|
||||
playerData.totalPoints += 1;
|
||||
playerData.winStreak = 0;
|
||||
} else {
|
||||
playerData.winStreak = 0;
|
||||
}
|
||||
|
||||
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 played any RPS games yet!`);
|
||||
return;
|
||||
}
|
||||
|
||||
const winRate = playerData.totalGames > 0 ? (playerData.wins / playerData.totalGames * 100).toFixed(1) : 0;
|
||||
bot.say(context.replyTo, `🎯 ${context.nick}: ${playerData.totalPoints} points | ${playerData.wins}W-${playerData.losses}L-${playerData.draws}D (${winRate}% win rate)`);
|
||||
},
|
||||
|
||||
showPlayerStats(context, bot) {
|
||||
const playerData = this.gameState.scores.get(context.nick);
|
||||
if (!playerData) {
|
||||
bot.say(context.replyTo, `🪨📄✂️ ${context.nick} hasn't played any RPS games yet!`);
|
||||
return;
|
||||
}
|
||||
|
||||
const winRate = playerData.totalGames > 0 ? (playerData.wins / playerData.totalGames * 100).toFixed(1) : 0;
|
||||
|
||||
bot.say(context.replyTo, `📊 ${context.nick}'s RPS Stats:`);
|
||||
bot.say(context.replyTo, `🎯 Points: ${playerData.totalPoints} | Games: ${playerData.totalGames} | Win Rate: ${winRate}%`);
|
||||
bot.say(context.replyTo, `🏆 Record: ${playerData.wins}W-${playerData.losses}L-${playerData.draws}D | Streak: ${playerData.winStreak} (best: ${playerData.longestWinStreak})`);
|
||||
bot.say(context.replyTo, `🤖 vs Bot: ${playerData.vsBot.wins}W-${playerData.vsBot.losses}L-${playerData.vsBot.draws}D | 👥 vs Players: ${playerData.vsPlayer.wins}W-${playerData.vsPlayer.losses}L-${playerData.vsPlayer.draws}D`);
|
||||
},
|
||||
|
||||
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 played RPS yet!');
|
||||
return;
|
||||
}
|
||||
|
||||
bot.say(context.replyTo, '🏆 TOP RPS PLAYERS 🏆');
|
||||
scores.forEach(([nick, data], index) => {
|
||||
const medal = index === 0 ? '🥇' : index === 1 ? '🥈' : index === 2 ? '🥉' : '🎯';
|
||||
const winRate = data.totalGames > 0 ? (data.wins / data.totalGames * 100).toFixed(1) : 0;
|
||||
bot.say(context.replyTo, `${medal} ${index + 1}. ${nick}: ${data.totalPoints}pts (${data.wins}W-${data.losses}L-${data.draws}D, ${winRate}%)`);
|
||||
});
|
||||
},
|
||||
|
||||
// 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} RPS 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 RPS players:', topPlayers.map(([name, data]) => `${name}(${data.totalPoints}pts)`).join(', '));
|
||||
}
|
||||
} else {
|
||||
console.log(`🪨📄✂️ No existing RPS scores file found, starting fresh`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ Error loading RPS 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} RPS scores to ${this.scoresFile}`);
|
||||
} catch (error) {
|
||||
console.error(`❌ Error saving RPS scores:`, error);
|
||||
}
|
||||
}
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue