From b6b9eb66ab34bad0bdde0bb93c9c5ada4c6dafd8 Mon Sep 17 00:00:00 2001 From: Jan Date: Wed, 3 Mar 2021 16:07:46 +0100 Subject: [PATCH] tictactoe --- commands/rank.js | 2 +- commands/tictactoe.js | 174 ++++++++++++++++++++++++++++++++++++++++++ commands/whois.js | 24 +++--- index.js | 6 +- util/friendships.js | 6 +- util/get_user.js | 21 +++++ 6 files changed, 219 insertions(+), 14 deletions(-) create mode 100644 commands/tictactoe.js create mode 100644 util/get_user.js diff --git a/commands/rank.js b/commands/rank.js index 92cbc9e..8bf1f38 100644 --- a/commands/rank.js +++ b/commands/rank.js @@ -15,7 +15,7 @@ module.exports.meta = { */ module.exports.run = async (message, args) => new Promise(async (resolve, reject) => { const channel = await client.channels.fetch(message.channel); - if (channel.channel_type != "Group") return client.channels.sendMessage(message.channel, `You can't earn XP in this channel.`); + if (channel.channel_type !== "Group") return client.channels.sendMessage(message.channel, `You can't earn XP in this channel.`); let groupLevels = levels.get(message.channel); const { xp, level, enabled } = (groupLevels?.[message.author] || {}); diff --git a/commands/tictactoe.js b/commands/tictactoe.js new file mode 100644 index 0000000..1713960 --- /dev/null +++ b/commands/tictactoe.js @@ -0,0 +1,174 @@ +const Revolt = require('revolt.js'); +const { client, logger, config } = require('..'); +const { levels, levelups } = require('../util/levels'); +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') { + 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` + + `|${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` : ''); + + 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); + console.log(w); + 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 + */ +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[0][2] === player) && + (field[2][0] === player || field[2][2] === player) && + (field[1][1] === player)) if (!winner) winner = player; + }); + + return winner; +} diff --git a/commands/whois.js b/commands/whois.js index 3981f2d..36f961b 100644 --- a/commands/whois.js +++ b/commands/whois.js @@ -1,5 +1,7 @@ const Revolt = require('revolt.js'); const { client, logger, config } = require('..'); +const getUser = require('../util/get_user'); +const { levels, levelups } = require('../util/levels'); module.exports.meta = { name: 'whois', @@ -13,18 +15,22 @@ module.exports.meta = { * @param { string[] } args */ module.exports.run = async (message, args) => new Promise(async (resolve, reject) => { - let uid = args[0]; - if (!uid) - return client.channels.sendMessage(message.channel, 'Please <@mention> the target user or type their ID.'); + if (!args[0]) + return client.channels.sendMessage(message.channel, ':x: Please specify the user\'s name or ID.'); + let target = await getUser(args.join(' ')); + if (!target) + return client.channels.sendMessage(message.channel, ':x: I can\'t find that user. Type their name or ID.'); - if (uid.startsWith('<@') && uid.endsWith('>')) uid = uid.substr(2, uid.length - 3); + const avatarURL = `https://api.revolt.chat/users/${target._id}/avatar`; + const { xp, level, enabled } = (levels.get(message.channel)?.[target._id] || {}); - const target = await client.users.fetch(uid) - .catch(e => { return reject(e) }); - - let msgContent = - `${JSON.stringify(target)}` + let msgContent = `> ## \u200b ${target.username}\n` + + `> \u200b\n` + + `> **ID**: \`${target._id}\`\n` + + `> **Online**: \`${target.online ?? 'Unknown'}\`\n` + + `> **XP**: \`${xp ? `${xp} XP, Level ${level}. Level up in ${levelups.find(l => l > xp) - xp} XP` : 'None'}\`\n` + + `> [\\[Avatar\\]](${avatarURL})\n` await client.channels.sendMessage(message.channel, msgContent); }); \ No newline at end of file diff --git a/index.js b/index.js index cef5978..4746981 100644 --- a/index.js +++ b/index.js @@ -22,8 +22,10 @@ const logger = new Logger(config.debug ? LogLevel.Debug : LogLevel.Standard); const client = new Revolt.Client(config.revoltClient); client.on("message", async message => { - const author = await client.users.fetch(message.author); - const channel = await client.channels.fetch(message.channel); + const author = await client.users.fetch(message.author).catch(()=>{}); + const channel = await client.channels.fetch(message.channel).catch(()=>{}); + + if (!author || !channel) return logger.debug(`Received message with unknown author or channel`); if (author._id === client.user._id) return logger.debug(`Sent message -> ${channel.name} => ${message.content}`); diff --git a/util/friendships.js b/util/friendships.js index ae2bfda..b3a02ae 100644 --- a/util/friendships.js +++ b/util/friendships.js @@ -1,10 +1,12 @@ -/** +/**if () { + + } else * Automatically accepts friend requests and * sends requests to users messaging the bot. * Required for users to add it to groups. */ -const { client, logger, config } = require('..'); +const { client, logger } = require('..'); client.on("packet", async packet => { try { diff --git a/util/get_user.js b/util/get_user.js new file mode 100644 index 0000000..03959f2 --- /dev/null +++ b/util/get_user.js @@ -0,0 +1,21 @@ +const { client, logger, config } = require('..'); + +module.exports = async (input) => { + try { + let output; + if (input.startsWith('<@') && input.endsWith('>')) + output = input.substr(2, input.length - 3); + else { + if (input.startsWith('@')) input = input.substr(1); + if (client.users.get(input)) + output = input; + else + output = client.users.toArray().find(user => user.username?.toLowerCase() === input?.toLowerCase())?._id; + } + if (!output) output = input; + return await client.users.fetch(output) + .catch(()=>{}); + } catch(e) { + console.error(e); + } +} \ No newline at end of file