diff --git a/README.md b/README.md index e5e154f6..255379be 100644 --- a/README.md +++ b/README.md @@ -261,7 +261,7 @@ in the appropriate channel's topic to use it. ## Commands -Total: 581 +Total: 582 ### Utility: @@ -620,6 +620,7 @@ Total: 581 * **island:** Who will be the final two left on the island after a series of vote-kicks? * **jenga:** Play a game of Jenga with another user or the AI. * **lie-swatter:** Players are given a fact and must quickly decide if it's True or a Lie. +* **nim:** Play a game of nim with another user. * **pick-a-number:** Two players pick a number between 1 and 10. Whoever's closer wins. * **poker:** Play poker with up to 5 other users. * **quiz-duel:** Answer a series of quiz questions against other opponents. diff --git a/assets/json/nim.json b/assets/json/nim.json new file mode 100644 index 00000000..9e714cbc --- /dev/null +++ b/assets/json/nim.json @@ -0,0 +1,29 @@ +[ + "🕯️", + "🍎", + "🍑", + "🍆", + "🧅", + "🍕", + "📀", + "🔋", + "💵", + "🪙", + "💎", + "🦠", + "🪥", + "🧸", + "📎", + "📏", + "📌", + "🖍️", + "🖊️", + "🖋️", + "🖌️", + "✏️", + "🏳️‍🌈", + "🪱", + "🐌", + "🥢", + "🥄" +] diff --git a/commands/games-mp/nim.js b/commands/games-mp/nim.js new file mode 100644 index 00000000..ce484856 --- /dev/null +++ b/commands/games-mp/nim.js @@ -0,0 +1,153 @@ +const Command = require('../../structures/Command'); +const { stripIndents } = require('common-tags'); +const { verify } = require('../../util/Util'); +const emoji = require('../../assets/json/nim'); +const nums = ['1️⃣', '2️⃣', '3️⃣', '4️⃣', '5️⃣', '6️⃣', '7️⃣', '8️⃣', '9️⃣', '🔟']; + +module.exports = class NimCommand extends Command { + constructor(client) { + super(client, { + name: 'nim', + group: 'games-mp', + memberName: 'nim', + description: 'Play a game of nim with another user.', + args: [ + { + key: 'opponent', + prompt: 'What user would you like to challenge?', + type: 'user' + }, + { + key: 'rows', + prompt: 'How many rows do you want to have?', + type: 'integer', + default: 5, + min: 1, + max: 10 + } + ] + }); + } + + async run(msg, { opponent, rows }) { + if (opponent.id === msg.author.id) return msg.reply('You may not play against yourself.'); + if (opponent.bot) return msg.reply('Bots may not be played against.'); + const current = this.client.games.get(msg.channel.id); + if (current) return msg.reply(`Please wait until the current game of \`${current.name}\` is finished.`); + this.client.games.set(msg.channel.id, { name: this.name }); + try { + await msg.say(`${opponent}, do you accept this challenge?`); + const verification = await verify(msg.channel, opponent); + if (!verification) { + this.client.games.delete(msg.channel.id); + return msg.say('Looks like they declined...'); + } + const board = this.generateBoard(rows); + let userTurn = true; + let winner = null; + let lastTurnTimeout = false; + let firstTurn = true; + const objectEmoji = emoji[Math.floor(Math.random() * emoji.length)]; + while (!winner) { + const user = userTurn ? msg.author : opponent; + await msg.say(stripIndents` + ${user}, from which row do you want to remove from? Type \`end\` to forefeit. + After this step, you will decide how many ${objectEmoji} to remove from that row. + + ${this.displayBoard(board)} + + ${firstTurn ? '_In Nim, you win by forcing the opponent to take the last object._' : ''} + `); + const pickFilter = res => { + if (res.author.id !== user.id) return false; + const choice = res.content; + if (choice.toLowerCase() === 'end') return true; + const i = Number.parseInt(choice, 10) - 1; + return board[i] && board[i] > 0; + }; + const turn = await msg.channel.awaitMessages(pickFilter, { + max: 1, + time: 60000 + }); + if (!turn.size) { + if (lastTurnTimeout) { + await msg.say('Game ended due to inactivity.'); + winner = 'time'; + break; + } else { + await msg.say('Sorry, time is up!'); + lastTurnTimeout = true; + continue; + } + } + const choice = turn.first().content; + const picked = Number.parseInt(choice, 10); + if (choice.toLowerCase() === 'end') { + winner = userTurn ? opponent : msg.author; + break; + } + const row = board[picked - 1]; + await msg.say(stripIndents` + How many ${objectEmoji} do you want to remove from row ${picked}? Type \`end\` to forefeit. + If you want to go back, type \`back\`. + + ${nums[picked - 1]}${objectEmoji.repeat(row)} + `); + const rowFilter = res => { + if (res.author.id !== user.id) return false; + const choice = res.content; + if (choice.toLowerCase() === 'end' || choice.toLowerCase() === 'back') return true; + const i = Number.parseInt(choice, 10) - 1; + return row >= i && row > 0; + }; + const rowTurn = await msg.channel.awaitMessages(rowFilter, { + max: 1, + time: 60000 + }); + if (!rowTurn.size) { + if (lastTurnTimeout) { + await msg.say('Game ended due to inactivity.'); + winner = 'time'; + break; + } else { + await msg.say('Sorry, time is up!'); + lastTurnTimeout = true; + continue; + } + } + const rowChoice = turn.first().content; + const rowPicked = Number.parseInt(choice, 10); + if (rowChoice.toLowerCase() === 'end') { + winner = userTurn ? opponent : msg.author; + break; + } + if (rowChoice.toLowerCase() === 'back') continue; + board[picked - 1] -= rowPicked; + if (!userTurn && firstTurn) firstTurn = false; + if (!board.some(row => row !== 0)) { + winner = userTurn ? opponent : msg.author; + break; + } + userTurn = !userTurn; + } + this.client.games.delete(msg.channel.id); + if (winner === 'time') return msg.say('Game ended due to inactivity.'); + return msg.say(`Congrats, ${winner}! You forced your opponent to pick the final object!`); + } catch (err) { + this.client.games.delete(msg.channel.id); + throw err; + } + } + + displayBoard(board, objectEmoji) { + return board.map((amount, i) => `${nums[i]}${objectEmoji.repeat(amount)}`).join('\n'); + } + + generateBoard(rows) { + const board = []; + for (let i = 0; i < rows; i++) { + board.push(i + 1); + } + return board; + } +}; diff --git a/package.json b/package.json index ef293157..3a2ad1a5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xiao", - "version": "126.9.0", + "version": "126.10.0", "description": "Your personal server companion.", "main": "Xiao.js", "scripts": {