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); sessions.delete(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; }