const { client, config } = require('..'); const getUser = require('../util/get_user'); /** * @type {Map} */ let sessions = new Map(); module.exports.meta = { name: 'tictactoe', aliases: [], description: 'Play tic tac toe.' } /** * * @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); 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}tictactoe 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 x = message.author; let o = opponent._id; if (Math.random() > 0.5) [x, o] = [o, x]; let turn = Math.random() > 0.5 ? 'X' : 'O'; let winner = null; let text; let msg; const updateMsg = async () => { text = `**X**: <@${x}> \u200b **O**: <@${o}>\n\u200b\n` + `|${winner === false ? '/' : turn}| \u200b \u200b **1** \u200b \u200b | \u200b \u200b **2** \u200b \u200b | \u200b \u200b **3** \u200b \u200b |\n` + `|-|:-:|:-:|:-:|\n` + `|**A**|${field[0][0]}|${field[0][1]}|${field[0][2]}|\n` + `|**B**|${field[1][0]}|${field[1][1]}|${field[1][2]}|\n` + `|**C**|${field[2][0]}|${field[2][1]}|${field[2][2]}|\n` + (winner ? `\n\u200b\n# Winner: <@${winner === 'X' ? x : o}>\n\u200b` : winner === false ? '\n\u200b\n# Draw' :''); 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, msg => { let [ column, row, bruh ] = msg.content?.toLowerCase().split(''); if (column === 'a') column = 0; if (column === 'b') column = 1; if (column === 'c') column = 2; row = Number(row); row--; column = Number(column); // Prevent borking if ((isNaN(row) || isNaN(column)) || bruh) return; if (field[column]?.[row] !== ' ') return client.channels.sendMessage(msg.channel, 'That field is not available.'); let curPlayer = turn === 'X' ? x : o; if (msg.author !== x && msg.author !== o) return client.channels.sendMessage(message.channel, `<@${msg.author}>, you are not part of this game.`); if (msg.author !== curPlayer) return client.channels.sendMessage(message.channel, `<@${msg.author}>, it's <@${curPlayer}>'s turn.`); field[column][row] = turn; turn = turn === 'X' ? 'O' : 'X'; let w = getWinner(field); if (w === false) { // Draw end(); winner = false; updateMsg(); sessions.delete(message.channel); client.channels.sendMessage(message.channel, `Draw: Nobody wins`); } else if (w) { end(); winner = w; updateMsg(); sessions.delete(message.channel); client.channels.sendMessage(message.channel, `<@${w === 'X' ? x : o}> wins!`); } else { 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); } }); return () => { // somehow remove the listener idk how ended = true; } } /** * @param {('X'|'O'|' ')[][]} field * @returns {null|'X'|'O'|boolean} */ const getWinner = field => { let winner = null; ['X', 'O'].forEach(player => { // Horizontal lines for (let i = 0; i < 3; i++) { if (field[i][0] === player && field[i][1] === player && field[i][2] === player) if (!winner) winner = player; } // Vertical lines for (let i = 0; i < 3; i++) { if (field[0][i] === player && field[1][i] === player && field[2][i] === player) if (!winner) winner = player; } // Diagonal lines if(((field[0][0] === player && field[2][2] === player) || (field[2][0] === player && field[0][2] === player)) && (field[1][1] === player)) if (!winner) winner = player; }); // Draw if (!winner) { let draw = true; for (let i = 0; i < 3; i++) for (let j = 0; j < 3; j++) { if (field[i][j] === ' ') draw = false; } if (draw) return false; } return winner; }