revolt-bot/commands/tictactoe.js

191 lines
6.5 KiB
JavaScript

const { client, config } = require('..');
const getUser = require('../util/get_user');
/**
* @type {Map<String, { createdBy: String, opponent: String, messageID: String }>}
*/
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;
}