From ae63a93940a4d07162d136cef3b4516fee65f25f Mon Sep 17 00:00:00 2001 From: ION606 Date: Fri, 1 Jul 2022 15:00:52 +0300 Subject: [PATCH] Added the 'game tictactoe' command --- commands/{db => games}/battle.js | 4 +- .../{db => games}/external_game_functions.js | 2 +- commands/{db => games}/game.js | 68 ++++++-- commands/{db => games}/game_classes.js | 0 commands/games/tictactoe.js | 149 ++++++++++++++++++ commands/interactionhandler.js | 117 ++++++++++++++ main.js | 96 +---------- 7 files changed, 329 insertions(+), 107 deletions(-) rename commands/{db => games}/battle.js (99%) rename commands/{db => games}/external_game_functions.js (98%) rename commands/{db => games}/game.js (81%) rename commands/{db => games}/game_classes.js (100%) create mode 100644 commands/games/tictactoe.js create mode 100644 commands/interactionhandler.js diff --git a/commands/db/battle.js b/commands/games/battle.js similarity index 99% rename from commands/db/battle.js rename to commands/games/battle.js index 92e6487..130be0d 100644 --- a/commands/db/battle.js +++ b/commands/games/battle.js @@ -1,11 +1,11 @@ //@ts-check const { MessageActionRow, MessageButton, MessageSelectMenu, Client, CommandInteractionOptionResolver } = require('discord.js'); -const { STATE } = require('./econ'); +const { STATE } = require('../db/econ'); const { winGame, getCustomEmoji } = require('./external_game_functions.js'); const { changeTurn } = require('../turnManager.js'); const { game_class_battle } = require('./game_classes'); const { MongoClient } = require('mongodb'); -const { convertSnowflakeToDate } = require('./addons/snowflake'); +const { convertSnowflakeToDate } = require('../db/addons/snowflake'); function postActionBar(thread, user_dbo) { diff --git a/commands/db/external_game_functions.js b/commands/games/external_game_functions.js similarity index 98% rename from commands/db/external_game_functions.js rename to commands/games/external_game_functions.js index dcfd254..746e7e5 100644 --- a/commands/db/external_game_functions.js +++ b/commands/games/external_game_functions.js @@ -1,5 +1,5 @@ //@ts-check -const { addxp, STATE, BASE } = require("./econ.js"); +const { addxp, STATE, BASE } = require("../db/econ"); const turnManger = require('../turnManager.js'); diff --git a/commands/db/game.js b/commands/games/game.js similarity index 81% rename from commands/db/game.js rename to commands/games/game.js index 76bbd0c..d9ec899 100644 --- a/commands/db/game.js +++ b/commands/games/game.js @@ -1,9 +1,15 @@ // // @ts-check //Disabled const { MongoClient, ServerApiVersion } = require('mongodb'); -let ecoimport = require("./econ.js"); -let { handle } = require("./battle.js"); //PROBLEM (CIRCULAR DEPENDANCY) -let snowflake = require("./addons/snowflake.js"); +let ecoimport = require("../db/econ.js"); + +//#region Game Imports +const battle = require("./battle.js"); +const ttt = require('./tictactoe.js'); + +//#endregion + +let snowflake = require("../db/addons/snowflake.js"); const STATE = ecoimport.STATE; const BASE = ecoimport.BASE; @@ -11,7 +17,7 @@ const { winGame, loseGame, equipItem } = require('./external_game_functions.js') const { chooseClass, presentClasses } = require('./game_classes.js'); //Has a list of all games (used to change player state) -const allGames = ['battle']; +const allGames = ['battle', 'Tic Tac Toe']; // const { NULL } = require('mysql/lib/protocol/constants/types'); @@ -24,6 +30,7 @@ async function Initialize(bot, user_dbo, command, message, first, second, other_ return new Promise(async function(resolve, reject) { user_dbo.find({"game": {$exists: true}}).toArray(function(err, docs){ let doc = docs[0]; + console.log(command); if (allGames.indexOf(command) != -1) { if (other_dbo != null) { user_dbo.updateOne( { "game": {$exists: true} }, { $set: { game: command, opponent: other_dbo.s.namespace.collection, state: STATE.FIGHTING }}); @@ -192,7 +199,11 @@ function in_game_redirector(bot, interaction, threadname, doc, client, mongouri, const game = docs[0].game switch (game) { - case 'battle': handle(client, dbo, other, bot, thread, interaction.customId.toLowerCase(), mongouri, items, interaction, xp_collection); + case 'battle': battle.handle(client, dbo, other, bot, thread, interaction.customId.toLowerCase(), mongouri, items, interaction, xp_collection); + break; + + case 'Tic Tac Toe': ttt.handle(client, db, dbo, other, bot, thread, null, doc, interaction, xp_collection); + break; } }); } @@ -203,8 +214,6 @@ module.exports ={ description: "Play a game using Selmer Bot!", async execute(bot, message, args, command, Discord, mongouri, items, xp_collection) { - if (!bot.inDebugMode) { return message.reply("This command is currently in development!"); } - //#region Setup const id = message.author.id; @@ -226,7 +235,6 @@ module.exports ={ //Check for a second person and create a second database entry if neccessary if (message.mentions.users.first() != undefined) { -//#TODO //FIX THIS (NOT THE RIGHT CLIENT 100% OF THE TIME!!!!!!!) ecoimport.CreateNewCollection(message, client, server, message.mentions.users.first().id); } @@ -283,15 +291,41 @@ module.exports ={ // message.reply(`${first} [${name_first}], ${second} [${name_second}]`); throw 'ERR'; const threadname = `${name_first.username} VS ${name_second.username} [${newCommand.toUpperCase()}]`; var newObj = {0: id, 1: other_discord.id, turn: 0, thread: threadname}; + + if (newCommand.replaceAll(" ", "").toLowerCase() == 'tictactoe') { newCommand = 'Tic Tac Toe'; } + + if (newCommand === 'Tic Tac Toe') { + //Create the new board + let newboard = ["", "", "", "", "", "", "", "", ""]; + newObj.board = newboard; + let symbols; + + /*DOES NOT WORK + if (msg.content.lastIndexOf('>') == msg.content.lenth) { + symbols = ['X', 'O']; + } else { + symbols = msg.content.substring(msg.content.lastIndexOf('>') + 2).split(' '); + } + */ + newObj.symbols = ['X', 'O']; + } + serverinbotdb.insertOne(newObj); //#endregion + + //Need this for all 2 player games + const result = Initialize(bot, dbo, newCommand, msg, id, other_discord.id, other); + if (newCommand == 'battle') { - const result = Initialize(bot, dbo, newCommand, msg, id, other_discord.id, other); result.then(function (thread) { - handle(client, dbo, other, bot, thread, 'initalize', mongouri, items, null, xp_collection); + battle.handle(client, dbo, other, bot, thread, 'initalize', mongouri, items, null, xp_collection); + }); + } else if (newCommand == 'Tic Tac Toe') { + result.then(function (thread) { + ttt.handle(client, db, dbo, other, bot, thread, 'initalize', mongouri, null, xp_collection); }); } } else if (command == 'quit') { @@ -323,14 +357,26 @@ module.exports ={ //#region game-specific commands else { + //Make change to new name if necessary + if (command.replaceAll(" ", "").toLowerCase() == 'tictactoe') { command = 'Tic Tac Toe'; } + if (game == 'battle' || command == 'battle') { + if (!bot.inDebugMode) { return message.reply("This command is currently in development!"); } + //Handle sending the request and making sure the user exists here let other_discord = message.mentions.users.first(); if (other_discord == undefined) { return message.reply(`${args[1]} is not a valid user!`); } - message.channel.send(`${other_discord}, <@${message.author.id}> has invited you to play "battle", to accept, please reply to this message with _!game accept_`); + message.channel.send(`${other_discord}, <@${message.author.id}> has invited you to play _"battle"_. To accept, please reply to this message with _!game accept_`); + } else if (game == 'Tic Tac Toe' || command == 'Tic Tac Toe') { + let other_discord = message.mentions.users.first(); + if (other_discord == undefined) { + return message.reply(`${args[1]} is not a valid user!`); + } + + message.channel.send(`${other_discord}, <@${message.author.id}> has invited you to play _"Tic Tac Toe"_. To accept, please reply to this message with _!game accept_`); } //Catch statement (invalid command) diff --git a/commands/db/game_classes.js b/commands/games/game_classes.js similarity index 100% rename from commands/db/game_classes.js rename to commands/games/game_classes.js diff --git a/commands/games/tictactoe.js b/commands/games/tictactoe.js new file mode 100644 index 0000000..233d0de --- /dev/null +++ b/commands/games/tictactoe.js @@ -0,0 +1,149 @@ +// @ts-check + +const wait = require('node:timers/promises').setTimeout; +const { MessageActionRow, MessageButton, MessageSelectMenu, Client, CommandInteractionOptionResolver } = require('discord.js'); +const { STATE } = require('../db/econ'); +const { winGame, getCustomEmoji } = require('./external_game_functions.js'); +const { changeTurn } = require('../turnManager.js'); +const { game_class_battle } = require('./game_classes'); +const { MongoClient } = require('mongodb'); +const { convertSnowflakeToDate } = require('../db/addons/snowflake'); + +//This function is blatantly stolen from https://alialaa.com/blog/tic-tac-toe-js +function isTerminal(board) { + //Return False if board in empty + if (board.every(cell => !cell)) return false; + let nums = [0, 0, 0] + + //Checking Horizontal Wins + if (board[0] === board[1] && board[0] === board[2] && board[0]) { + nums = [0, 1, 2]; + return {'winner': board[0], 'direction': 'H', 'row': 1, 'nums': nums}; + } + if (board[3] === board[4] && board[3] === board[5] && board[3]) { + nums = [3, 4, 5]; + return {'winner': board[3], 'direction': 'H', 'row': 2, 'nums': nums}; + } + if (board[6] === board[7] && board[6] === board[8] && board[6]) { + nums = [6, 7, 8]; + return {'winner': board[6], 'direction': 'H', 'row': 3, 'nums': nums}; + } + + //Checking Vertical Wins + if (board[0] === board[3] && board[0] === board[6] && board[0]) { + nums = [0, 3, 6]; + return {'winner': board[0], 'direction': 'V', 'column': 1, 'nums': nums}; + } + if (board[1] === board[4] && board[1] === board[7] && board[1]) { + nums = [1, 4, 7]; + return {'winner': board[1], 'direction': 'V', 'column': 2, 'nums': nums}; + } + if (board[2] === board[5] && board[2] === board[8] && board[2]) { + nums = [2, 5, 8]; + return {'winner': board[2], 'direction': 'V', 'column': 3, 'nums': nums}; + } + + //Checking Diagonal Wins + if (board[0] === board[4] && board[0] === board[8] && board[0]) { + nums = [0, 4, 8]; + return {'winner': board[0], 'direction': 'D', 'diagonal': 'main', 'nums': nums}; + } + if (board[2] === board[4] && board[2] === board[6] && board[2]) { + nums = [2, 4, 6]; + return {'winner': board[2], 'direction': 'D', 'diagonal': 'counter', 'nums': nums}; + } + + //If no winner but the board is full, then it's a draw + if (board.every(cell => cell)) { + return {'winner': 'draw'}; + } + + //return false otherwise + return false; +} + + +//I know it's sloppy, but when 'initial' is true, 'interaction' will actually be 'thread' +function postActionBar(interaction, user_dbo, board, won, initial = false) { + let componentlist = []; + let newRow = new MessageActionRow(); + + for (let i = 0; i < 9; i ++) { + let button; + + if (!won) { + if (!board[i]) { + button = new MessageButton() + .setCustomId(`ttt|${i}`) + .setLabel('-') + .setStyle('SUCCESS') + } else { + button = new MessageButton() + .setCustomId(`ttt|${i}`) + .setLabel(board[i]) + .setStyle('DANGER') + .setDisabled(true); + } + } else { + if (i in won.nums) { + button = new MessageButton() + .setCustomId(`ttt|${i}`) + .setLabel('W') + .setStyle('SUCCESS') + } else { + button = new MessageButton() + .setCustomId(`ttt|${i}`) + .setLabel('F') + .setStyle('DANGER') + .setDisabled(true); + } + } + + newRow.addComponents(button); + + if ((i + 1) % 3 == 0) { + //Add the row to the list of rows + componentlist.push(newRow); + newRow = new MessageActionRow(); + } + } + + console.log(componentlist); + + if (initial) { + interaction.send({ content: `Your turn <@${user_dbo.s.namespace.collection}>!`, components: componentlist }); + } else { + interaction.update({ content: `Your turn <@${user_dbo.s.namespace.collection}>!`, components: componentlist }); + } +} + + +async function handle(client, db, dbo, other, bot, thread, command, doc, interaction, xp_collection) { + + if (command == 'initalize') { + let board = ["", "", "", "", "", "", "", "", ""]; + postActionBar(thread, dbo, board, false,true); + } else { + //Change the board + let square = Number(interaction.customId.split('|')[1]); + let symbol = doc.symbols[doc.turn]; + let board = doc.board; + board[square] = symbol; + client.db('B|S' + bot.user.id).collection(dbo.s.namespace.db.substr(0, dbo.s.namespace.db.length - 6)).updateOne({'board': {$exists: true}}, {$set: {board: board}}); + + //Check if the game is over + let won = isTerminal(board); + + if (!won) { + changeTurn(client, bot, interaction); + postActionBar(interaction, other, board, false); + changeTurn(client, bot, interaction); + } else { + postActionBar(interaction, dbo, board, won); + await wait(7000); + winGame(client, bot, db, dbo, xp_collection, interaction.message); + } + } +} + +module.exports = { handle } \ No newline at end of file diff --git a/commands/interactionhandler.js b/commands/interactionhandler.js new file mode 100644 index 0000000..f63af91 --- /dev/null +++ b/commands/interactionhandler.js @@ -0,0 +1,117 @@ +const { MongoClient, ServerApiVersion } = require('mongodb'); + + +async function handle_interaction(interaction, mongouri, turnManager, bot, STATE, items, xp_collection) { + if (interaction.isButton()) { + const battlecommandlist = ['ATTACK', 'HEAL', 'DEFEND', 'ITEMS', 'ULTIMATE']; + const singleCommandGames = ['ttt']; + + const client = new MongoClient(mongouri, { useNewUrlParser: true, useUnifiedTopology: true, serverApi: ServerApiVersion.v1 }); + client.connect(async (err) => { + + if (battlecommandlist.indexOf(interaction.customId) != -1) { + await interaction.deferReply(); + + let current_user = turnManager.getTurn(client, bot, interaction); + + current_user.then(function (result) { + const id = result[0]; + const doc = result[1]; + const threadname = doc.thread; + const dbo = client.db(interaction.guildId + '[ECON]').collection(id); + + dbo.find({ 'state': {$exists: true} }).toArray(async function (err, docs) { + if (interaction.user.id == id) { + + //Check State + if (docs[0].state != STATE.IDLE) { + //Do turn stuff + bot.commands.get('game').in_game_redirector(bot, interaction, threadname, doc, client, mongouri, items, xp_collection); + } + + //remove the old interation message + await interaction.message.delete(); + + if (interaction.customId.toLowerCase() != 'heal') { + interaction.editReply(`<@${interaction.user.id}> used _${interaction.customId.toLowerCase()}_!`); + } + } else { + console.log("It's not your turn!"); + } + }); + }); + } else if (interaction.customId.split('|')[0] == 'ttt') { + let current_user = turnManager.getTurn(client, bot, interaction); + current_user.then(function (result) { + const id = result[0]; + + if (interaction.user.id == id) { + const doc = result[1]; + const threadname = doc.thread; + + let board = result.board; + + bot.commands.get('game').in_game_redirector(bot, interaction, threadname, doc, client, mongouri, items, xp_collection, board); + } else { + console.log("It's not your turn!"); + } + }); + } //else ifs here + }); + + client.close(); + } + + //Menu Selection + else if (interaction.isSelectMenu()) { + const id = interaction.customId.substring(0, interaction.customId.indexOf('|')) + const command = interaction.customId.substring(interaction.customId.indexOf('|'), interaction.customId.length - interaction.customId.indexOf('|')) + console.log(command); + + if (interaction.customId.toLowerCase().indexOf('|heal') != -1) { + const client = new MongoClient(mongouri, { useNewUrlParser: true, useUnifiedTopology: true, serverApi: ServerApiVersion.v1 }); + client.connect(err => { + console.log(id); + if (id != interaction.user.id) { return; } + + let current_user = turnManager.getTurn(client, bot, interaction); + current_user.then(function(result) { + const doc = result[1]; + const threadname = doc.thread; + const dbo = client.db(interaction.guildId + '[ECON]').collection(id); + + dbo.find({ 'state': {$exists: true} }).toArray(async function (err, docs) { + if (interaction.user.id == id) { + await interaction.deferReply(); + + //Check State + if (docs[0].state == STATE.FIGHTING) { + interaction.customId = 'usepotion'; + //Do turn stuff + bot.commands.get('game').in_game_redirector(bot, interaction, threadname, doc, client, mongouri, items, xp_collection); + } + + interaction.editReply(`<@${interaction.user.id}> used a _${interaction.values[0]}_!`); + + //remove the old interation message + await interaction.message.delete(); + + } else { + console.log("It's not your turn!"); + } + }); + }); + + //Get all chars from after "CUSTOM|" to the end of the str + // let name = item.icon.substr(7, item.icon.length - 6); + }); + } else if (interaction.customId.toLowerCase().indexOf('|item') != -1) { + + } + + //menu else ifs here + } //other selection types here +} + + +module.exports = { handle_interaction } \ No newline at end of file diff --git a/main.js b/main.js index a38b9c4..08441d2 100644 --- a/main.js +++ b/main.js @@ -4,6 +4,7 @@ const { MongoClient, ServerApiVersion } = require('mongodb'); const fs = require('fs'); const turnManager = require('./commands/turnManager.js'); const { welcome } = require('./commands/admin/welcome.js'); +const { handle_interaction } = require('./commands/interactionhandler.js'); const { exit } = require('process'); const BASE_LVL_XP = 20; @@ -106,7 +107,7 @@ fs.readdirSync('./commands') let temp_command = require("./commands/db/econ.js"); const { STATE } = require('./commands/db/econ.js'); bot.commands.set('econ', temp_command); -temp_command = require('./commands/db/game.js'); +temp_command = require('./commands/games/game.js'); bot.commands.set('game', temp_command); // const econFiles = fs.readdirSync('./commands/inventory').filter(file => file.endsWith('.js'));; @@ -162,98 +163,7 @@ bot.on('ready', async () => { //Button Section bot.on('interactionCreate', async interaction => { - - if (interaction.isButton()) { - const battlecommandlist = ['ATTACK', 'HEAL', 'DEFEND', 'ITEMS', 'ULTIMATE']; - - const client = new MongoClient(mongouri, { useNewUrlParser: true, useUnifiedTopology: true, serverApi: ServerApiVersion.v1 }); - client.connect(err => { - - if (battlecommandlist.indexOf(interaction.customId) != -1) { - let current_user = turnManager.getTurn(client, bot, interaction); - - current_user.then(function (result) { - const id = result[0]; - const doc = result[1]; - const threadname = doc.thread; - const dbo = client.db(interaction.guildId + '[ECON]').collection(id); - - dbo.find({ 'state': {$exists: true} }).toArray(async function (err, docs) { - if (interaction.user.id == id) { - await interaction.deferReply(); - - //Check State - if (docs[0].state != STATE.IDLE) { - //Do turn stuff - bot.commands.get('game').in_game_redirector(bot, interaction, threadname, doc, client, mongouri, items, xp_collection); - } - - //remove the old interation message - await interaction.message.delete(); - - if (interaction.customId.toLowerCase() != 'heal') { - interaction.editReply(`<@${interaction.user.id}> used _${interaction.customId.toLowerCase()}_!`); - } - } else { - console.log("It's not your turn!"); - } - }); - }); - }//else ifs here - }); - - client.close(); - } - - //Menu Selection - else if (interaction.isSelectMenu()) { - const id = interaction.customId.substring(0, interaction.customId.indexOf('|')) - const command = interaction.customId.substring(interaction.customId.indexOf('|'), interaction.customId.length - interaction.customId.indexOf('|')) - console.log(command); - - if (interaction.customId.toLowerCase().indexOf('|heal') != -1) { - const client = new MongoClient(mongouri, { useNewUrlParser: true, useUnifiedTopology: true, serverApi: ServerApiVersion.v1 }); - client.connect(err => { - console.log(id); - if (id != interaction.user.id) { return; } - - let current_user = turnManager.getTurn(client, bot, interaction); - current_user.then(function(result) { - const doc = result[1]; - const threadname = doc.thread; - const dbo = client.db(interaction.guildId + '[ECON]').collection(id); - - dbo.find({ 'state': {$exists: true} }).toArray(async function (err, docs) { - if (interaction.user.id == id) { - await interaction.deferReply(); - - //Check State - if (docs[0].state == STATE.FIGHTING) { - interaction.customId = 'usepotion'; - //Do turn stuff - bot.commands.get('game').in_game_redirector(bot, interaction, threadname, doc, client, mongouri, items, xp_collection); - } - - interaction.editReply(`<@${interaction.user.id}> used a _${interaction.values[0]}_!`); - - //remove the old interation message - await interaction.message.delete(); - - } else { - console.log("It's not your turn!"); - } - }); - }); - - //Get all chars from after "CUSTOM|" to the end of the str - // let name = item.icon.substr(7, item.icon.length - 6); - }); - } else if (interaction.customId.toLowerCase().indexOf('|item') != -1) { - - } - - //menu else ifs here - } //other selection types here + handle_interaction(interaction, mongouri, turnManager, bot, STATE, items, xp_collection); });