From f6fe3ca2e95b6cdf72ae0fc37477a522b85e5dd2 Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 7 Mar 2021 00:04:30 +0100 Subject: [PATCH] more gaming --- commands/connect4.js | 249 ++++++++++++++++++++++++++++++++++++++++++ commands/tictactoe.js | 10 ++ 2 files changed, 259 insertions(+) create mode 100644 commands/connect4.js diff --git a/commands/connect4.js b/commands/connect4.js new file mode 100644 index 0000000..0f4c059 --- /dev/null +++ b/commands/connect4.js @@ -0,0 +1,249 @@ +const Revolt = require('revolt.js'); +const { client, config } = require('..'); +const getUser = require('../util/get_user'); + +/** + * @type {Map} + */ +let sessions = new Map(); +const gameEndEmitter = new (require('events')); +let playerSymbol = 'เถž'; +let invisColor = '242424'; +// Note to myself: https://katex.org/docs/supported.html + +module.exports.meta = { + name: 'connect4', + aliases: [ 'connect-4' ], + description: 'Play connect 4.' +} + +/** + * + * @param { Revolt.Message } message + * @param { string[] } args + */ +module.exports.run = async (message, args) => new Promise(async (resolve, reject) => { + if ((await client.channels.fetch(message.channel)).channel_type !== 'Group') + return client.channels.sendMessage(message.channel, ':x: You can\'t play here.'); + + if (args[0]?.toLowerCase() === 'stop' || args[0]?.toLowerCase() === 'end') { + if (!sessions.get(message.channel)?.messageID) + return client.channels.sendMessage(message.channel, `:x: No ongoing match found.`); + + sessions.delete(message.channel); + gameEndEmitter.emit(message.channel); + await client.channels.sendMessage(message.channel, `:white_check_mark: Game stopped`); + return; + } + + if (sessions.get(message.channel)) + return client.channels.sendMessage(message.channel, `:x: There's an ongoing match ` + + `[here](https://app.revolt.chat/channel/${message.channel}/${sessions.get(message.channel).messageID}), ` + + `please wait for it to finish or use \`${config.prefix}connect4 stop\`.`); + + if (!args[0]) + return client.channels.sendMessage(message.channel, ':x: Please specify the ID or username of your opponent.'); + + let opponent = await getUser(args.join(' ')); + if (!opponent) + return client.channels.sendMessage(message.channel, 'I can\'t find that user.'); + if (opponent._id === client.user._id) + return client.channels.sendMessage(message.channel, ':x: You can\'t play against me. (yet)'); + if (opponent._id === message.author) + return client.channels.sendMessage(message.channel, ':x: You can\'t play against yourself, dumbus'); + if (client.channels.get(message.channel).recipients?.indexOf(opponent._id) === -1) + return client.channels.sendMessage(message.channel, ':x: That user is not in this group.'); + + let field = [ + [' ', ' ', ' ', ' ', ' ', ' ', ' '], + [' ', ' ', ' ', ' ', ' ', ' ', ' '], + [' ', ' ', ' ', ' ', ' ', ' ', ' '], + [' ', ' ', ' ', ' ', ' ', ' ', ' '], + [' ', ' ', ' ', ' ', ' ', ' ', ' '], + [' ', ' ', ' ', ' ', ' ', ' ', ' '], + ]; + + let red = await client.users.fetch(message.author); + let blue = opponent; + let currentPlayer = 'R'; + let winner = null; + if (Math.random() > 0.5) [red, blue] = [blue, red]; + if (Math.random() > 0.5) currentPlayer = 'B'; + + let text; + let msg; + let resendTip = true; + const updateMsg = async () => { + text = `Blue: <@${blue._id}> \u200b Red: <@${red._id}>\n` + + `${winner === null ? `**${currentPlayer === 'R' ? 'Red' : 'Blue'}'s turn**` : ''}\n\u200b\n` + + '| 1 | 2 | 3 | 4 | 5 | 6 | 7 |\n' + + '|:-:|:-:|:-:|:-:|:-:|:-:|:-:|\n'; + + field.forEach(row => { + row.forEach(p => { + let color + if (p !== ' ') + color = p === 'B' ? 'blue' : 'red'; + else + color = invisColor; + + text += `| $\\color{${color}}\\huge\\text{\u200b ${playerSymbol} \u200b}$`; + }); + text += '|\n'; + }); + + if (winner === false) { + text += '\n\u200b\n# No winner' + } else if (winner !== null) { + if (Math.random() > 0.8) { + text += `\n\u200b\n# $\\color{${winner === 'B' ? 'red' : 'blue'}}\\huge\\text{๐“€}$ ` + + `$\\color{${winner === 'B' ? 'blue' : 'red'}}\\huge\\text{๐“‚บเถž}$\n` + + `# <@${(winner === 'R' ? blue : red)._id}> got fucked by <@${(winner === 'R' ? red : blue)._id}>!`; + } else { + text += `\n\u200b\n# <@${(winner === 'R' ? red : blue)._id}> wins`; + } + } else if (resendTip) { + text += `\n\u200b\n##### Tip: Use \`${config.prefix}resend\` to move this message to the bottom`; + } + + + if (!msg) { + msg = await client.channels.sendMessage(message.channel, text); + sessions.set(message.channel, { messageID: msg._id, createdBy: message.author, opponent: opponent._id }); + } else + await client.channels.editMessage(message.channel, msg._id, { content: text }); + } + + await updateMsg(); + + const end = awaitMessages(message.channel, mesg => { + if (mesg.content?.toLowerCase() === `${config.prefix}resend` && (mesg.author === red._id || mesg.author === blue._id)) { + let oldID = msg?._id; + msg = null; + resendTip = false; + updateMsg(); + client.channels.deleteMessage(message.channel, oldID) + .catch(console.warn); + return; + } + + let [num, bruh] = mesg.content.split(' '); + num = Number(num) - 1; + if (bruh || isNaN(num) || num > 6 || num < 0) return; + + if (mesg.author !== red._id && mesg.author !== blue._id) + return client.channels.sendMessage(message.channel, `You are not part of this game, <@${mesg.author}>`); + + if ((currentPlayer === 'R' ? red : blue)._id !== mesg.author) + return client.channels.sendMessage(message.channel, `It's not your turn, <@${mesg.author}>`); + + if (field[0][num] !== ' ') + return client.channels.sendMessage(message.channel, 'That row is occupied.'); + + let i; + for (i = 5; field[i][num] !== ' '; i--) {} + + field[i][num] = currentPlayer; + currentPlayer = currentPlayer === 'R' ? 'B' : 'R'; + + let w = getWinner(field); + if (w === false) { + winner = false; + client.channels.sendMessage(message.channel, 'Draw: Nobody wins'); + end(); + } + if (w) { + winner = w; + client.channels.sendMessage(message.channel, `<@${w === 'R' ? red._id : blue._id}> wins`); + end(); + } + + updateMsg(); + }); +}); + +/** + * + * @param {String} channel + * @param {function} callback + */ + +const emitter = new (require('events')); +client.on('message', message => { + emitter.emit('message', message); +}); + +function awaitMessages(channel, callback) { + let ended = false; + const listener = emitter.on('message', message => { + if (message.channel === channel && !ended) { + callback(message); + } + }); + + gameEndEmitter.on(channel, () => ended = true); + return () => { + // somehow remove the listener idk how + gameEndEmitter.emit(channel); + } +} + +/** + * @param {('R'|'B'|' ')[][]} field + * @returns {null|'R'|'B'|boolean} + */ +const getWinner = field => { + // Horizontal + for (let i = 0; i < 6; i++) { + for (let j = 0; j < 4; j++) { + if (field[i][j] !== ' ' && + field[i][j + 1] === field[i][j] && + field[i][j + 2] === field[i][j] && + field[i][j + 3] === field[i][j] + ) return field[i][j]; + } + } + + // Vertical + for (let i = 0; i < 3; i++) { + for (let j = 0; j < 7; j++) { + if (field[i][j] !== ' ' && + field[i + 1][j] === field[i][j] && + field[i + 2][j] === field[i][j] && + field[i + 3][j] === field[i][j] + ) return field[i][j]; + } + } + + // Diagonal (Top left - bottom right) + for (let i = 0; i < 6; i++) { + for (let j = 0; j < 7; j++) { + if (field[i][j] !== ' ' && + field[i + 1]?.[j + 1] === field[i][j] && + field[i + 2]?.[j + 2] === field[i][j] && + field[i + 3]?.[j + 3] === field[i][j] + ) return field[i][j]; + } + } + + // Diagonal (Top right - bottom left) + for (let i = 0; i < 6; i++) { + for (let j = 0; j < 7; j++) { + if (field[i][j] !== ' ' && + field[i - 1]?.[j + 1] === field[i][j] && + field[i - 2]?.[j + 2] === field[i][j] && + field[i - 3]?.[j + 3] === field[i][j] + ) return field[i][j]; + } + } + + // Draw + let draw = true; + for (let i = 0; i < 6; i++) { + for (let j = 0; j < 7; j++) { + if (field[i][j] === ' ') draw = false; + } + } + if (draw) return false; + return null; +} diff --git a/commands/tictactoe.js b/commands/tictactoe.js index c9d39ad..f43d6e0 100644 --- a/commands/tictactoe.js +++ b/commands/tictactoe.js @@ -1,3 +1,4 @@ +const Revolt = require('revolt.js'); const { client, config } = require('..'); const getUser = require('../util/get_user'); @@ -5,6 +6,7 @@ const getUser = require('../util/get_user'); * @type {Map} */ let sessions = new Map(); +const gameEndEmitter = new (require('events')); module.exports.meta = { name: 'tictactoe', @@ -26,6 +28,7 @@ module.exports.run = async (message, args) => new Promise(async (resolve, reject return client.channels.sendMessage(message.channel, `:x: No ongoing match found.`); sessions.delete(message.channel); + gameEndEmitter.emit(message.channel); await client.channels.sendMessage(message.channel, `:white_check_mark: Game stopped`); return; } @@ -142,6 +145,13 @@ function awaitMessages(channel, callback) { callback(message); } }); + + gameEndEmitter.on(channel, () => ended = true); + return () => { + // somehow remove the listener idk how + gameEndEmitter.emit(channel); + } + return () => { // somehow remove the listener idk how ended = true;