diff --git a/Gwendolyn.py b/Gwendolyn.py index 47e7977..0154046 100644 --- a/Gwendolyn.py +++ b/Gwendolyn.py @@ -4,7 +4,7 @@ from discord.ext import commands from discord_slash import SlashCommand from pymongo import MongoClient from funcs import Money, StarWars, Games, Other, LookupFuncs -from utils import Options, Credentials, logThis, makeFiles, databaseFuncs +from utils import Options, Credentials, logThis, makeFiles, databaseFuncs, EventHandler, ErrorHandler class Gwendolyn(commands.Bot): def __init__(self): @@ -25,6 +25,8 @@ class Gwendolyn(commands.Bot): self.games = Games(self) self.money = Money(self) self.databaseFuncs = databaseFuncs(self) + self.eventHandler = EventHandler(self) + self.errorHandler = ErrorHandler(self) intents = discord.Intents.default() intents.members = True @@ -34,6 +36,20 @@ class Gwendolyn(commands.Bot): def log(self, messages, channel : str = "", level : int = 20): logThis(messages, channel, level) + async def stop(self, ctx): + if f"#{ctx.author.id}" in self.options.admins: + await ctx.send("Pulling git repo and restarting...") + + await self.change_presence(status = discord.Status.offline) + + self.databaseFuncs.stopServer() + + self.log("Logging out", level = 25) + await self.close() + else: + self.log(f"{ctx.author.display_name} tried to stop me! (error code 201)",str(ctx.channel_id)) + await ctx.send(f"I don't think I will, {ctx.author.display_name} (error code 201)") + if __name__ == "__main__": diff --git a/cogs/EventCog.py b/cogs/EventCog.py index d732ccd..7b72f6b 100644 --- a/cogs/EventCog.py +++ b/cogs/EventCog.py @@ -1,57 +1,34 @@ -import discord, traceback from discord.ext import commands class EventCog(commands.Cog): def __init__(self, bot): self.bot = bot + self.bot.on_error = self.on_error # Syncs commands, sets the game, and logs when the bot logs in @commands.Cog.listener() async def on_ready(self): - await self.bot.databaseFuncs.syncCommands() - self.bot.log("Logged in as "+self.bot.user.name+", "+str(self.bot.user.id), level = 25) - game = discord.Game("Use /help for commands") - await self.bot.change_presence(activity=game, status = discord.Status.online) - - @commands.Cog.listener() - async def on_disconnect(self): - await self.bot.change_presence(status = discord.Status.offline) + await self.bot.eventHandler.on_ready() # Logs when user sends a command @commands.Cog.listener() async def on_slash_command(self, ctx): - self.bot.log(f"{ctx.author.display_name} ran /{ctx.name}", str(ctx.channel_id), level = 25) + await self.bot.eventHandler.on_slash_command(ctx) # Logs if a command experiences an error @commands.Cog.listener() async def on_slash_command_error(self, ctx, error): - if isinstance(error, commands.CommandNotFound): - await ctx.send("That's not a command (error code 001)") - elif isinstance(error,commands.errors.MissingRequiredArgument): - self.bot.log(f"{error}",str(ctx.channel_id)) - await ctx.send("Missing command parameters (error code 002). Try using `!help [command]` to find out how to use the command.") - else: - exception = traceback.format_exception(type(error), error, error.__traceback__) - stopAt = "\nThe above exception was the direct cause of the following exception:\n\n" - if stopAt in exception: - index = exception.index(stopAt) - exception = exception[:index] + await self.bot.errorHandler.on_slash_command_error(ctx, error) - exceptionString = "".join(exception) - self.bot.log([f"exception in /{ctx.name}", f"{exceptionString}"],str(ctx.channel_id), 40) - await ctx.send("Something went wrong (error code 000)") + # Logs if on error occurs + async def on_error(self, method, *args, **kwargs): + await self.bot.errorHandler.on_error(method) - # Logs if an error occurs + # If someone reacted to a message, checks if it's a reaction it's + # Gwendolyn has been waiting for, and then does something @commands.Cog.listener() - async def on_error(self, method): - exception = traceback.format_exc() - stopAt = "\nThe above exception was the direct cause of the following exception:\n\n" - if stopAt in exception: - index = exception.index(stopAt) - exception = exception[:index] - - exceptionString = "".join(exception) - self.bot.log([f"exception in /{method}", f"{exceptionString}"], level = 40) + async def on_reaction_add(self, reaction, user): + await self.bot.eventHandler.on_reaction_add(reaction, user) def setup(bot): bot.add_cog(EventCog(bot)) diff --git a/cogs/GameCogs.py b/cogs/GameCogs.py index ed0ca78..4714437 100644 --- a/cogs/GameCogs.py +++ b/cogs/GameCogs.py @@ -1,7 +1,5 @@ -import discord, asyncio, json from discord.ext import commands from discord_slash import cog_ext -from discord_slash import SlashCommandOptionType as scot from utils import getParams @@ -15,73 +13,22 @@ class GamesCog(commands.Cog): # Checks user balance @cog_ext.cog_slash(**params["balance"]) async def balance(self, ctx): - await ctx.defer() - response = self.bot.money.checkBalance("#"+str(ctx.author.id)) - if response == 1: - new_message = ctx.author.display_name + " has " + str(response) + " GwendoBuck" - else: - new_message = ctx.author.display_name + " has " + str(response) + " GwendoBucks" - await ctx.send(new_message) + await self.bot.money.sendBalance(ctx) # Gives another user an amount of GwendoBucks @cog_ext.cog_slash(**params["give"]) async def give(self, ctx, user, amount): - await ctx.defer() - username = user.display_name - if self.bot.databaseFuncs.getID(username) == None: - async for member in ctx.guild.fetch_members(limit=None): - if member.display_name.lower() == username.lower(): - username = member.display_name - userID = "#" + str(member.id) - self.bot.database["users"].insert_one({"_id":userID,"user name":username,"money":0}) - response = self.bot.money.giveMoney("#"+str(ctx.author.id),username,amount) - await ctx.send(response) + await self.bot.money.giveMoney(ctx, user, amount) # Invest GwendoBucks in the stock market @cog_ext.cog_slash(**params["invest"]) async def invest(self, ctx, parameters = "check"): - await ctx.defer() - response = self.bot.games.invest.parseInvest(parameters,"#"+str(ctx.author.id)) - if response.startswith("**"): - responses = response.split("\n") - em = discord.Embed(title=responses[0],description="\n".join(responses[1:]),colour=0x00FF00) - await ctx.send(embed=em) - else: - await ctx.send(response) + await self.bot.games.invest.parseInvest(ctx, parameters) # Runs a game of trivia @cog_ext.cog_slash(**params["trivia"]) async def trivia(self, ctx, answer = ""): - await ctx.defer() - if answer == "": - question, options, correctAnswer = self.bot.games.trivia.triviaStart(str(ctx.channel_id)) - if options != "": - results = "**"+question+"**\n" - for x, option in enumerate(options): - results += chr(x+97) + ") "+option+"\n" - - await ctx.send(results) - - await asyncio.sleep(60) - - self.bot.games.trivia.triviaCountPoints(str(ctx.channel_id)) - - self.bot.databaseFuncs.deleteGame("trivia questions",str(ctx.channel_id)) - - self.bot.log("Time's up for the trivia question",str(ctx.channel_id)) - await ctx.send("Time's up The answer was \""+chr(correctAnswer)+") "+options[correctAnswer-97]+"\". Anyone who answered that has gotten 1 GwendoBuck") - else: - await ctx.send(question, hidden=True) - - elif answer in ["a","b","c","d"]: - response = self.bot.games.trivia.triviaAnswer("#"+str(ctx.author.id),str(ctx.channel_id),answer) - if response.startswith("Locked in "): - await ctx.send(f"{ctx.author.display_name} answered {answer}") - else: - await ctx.send(response) - else: - self.bot.log("I didn't understand that (error code 1101)",str(ctx.channel_id)) - await ctx.send("I didn't understand that (error code 1101)") + await self.bot.games.trivia.triviaParse(ctx, answer) class BlackjackCog(commands.Cog): @@ -92,103 +39,113 @@ class BlackjackCog(commands.Cog): # Starts a game of blackjack @cog_ext.cog_subcommand(**params["blackjackStart"]) async def blackjackStart(self, ctx): - await ctx.defer() - await self.bot.games.blackjack.parseBlackjack("", ctx) + await self.bot.games.blackjack.start(ctx) @cog_ext.cog_subcommand(**params["blackjackBet"]) async def blackjackBet(self, ctx, bet): - await ctx.defer() - await self.bot.games.blackjack.parseBlackjack(f"bet {bet}", ctx) + await self.bot.games.blackjack.playerDrawHand(ctx, bet) @cog_ext.cog_subcommand(**params["blackjackStand"]) async def blackjackStand(self, ctx, hand = ""): - await ctx.defer() - await self.bot.games.blackjack.parseBlackjack(f"stand {hand}", ctx) + await self.bot.games.blackjack.stand(ctx, hand) @cog_ext.cog_subcommand(**params["blackjackHit"]) - async def blackjackHit(self, ctx, hand = ""): - await ctx.defer() - await self.bot.games.blackjack.parseBlackjack(f"hit {hand}", ctx) + async def blackjackHit(self, ctx, hand = 0): + await self.bot.games.blackjack.hit(ctx, hand) + + @cog_ext.cog_subcommand(**params["blackjackDouble"]) + async def blackjackDouble(self, ctx, hand = 0): + await self.bot.games.blackjack.double(ctx, hand) + + @cog_ext.cog_subcommand(**params["blackjackSplit"]) + async def blackjackSplit(self, ctx, hand = 0): + await self.bot.games.blackjack.split(ctx, hand) + + @cog_ext.cog_subcommand(**params["blackjackHilo"]) + async def blackjackHilo(self, ctx): + await self.bot.games.blackjack.hilo(ctx) + + @cog_ext.cog_subcommand(**params["blackjackShuffle"]) + async def blackjackShuffle(self, ctx): + await self.bot.games.blackjack.shuffle(ctx) + + @cog_ext.cog_subcommand(**params["blackjackCards"]) + async def blackjackCards(self, ctx): + await self.bot.games.blackjack.cards(ctx) class ConnectFourCog(commands.Cog): def __init__(self,bot): """Runs game stuff.""" self.bot = bot + # Start a game of connect four against a user @cog_ext.cog_subcommand(**params["connectFourStartUser"]) async def connectFourStartUser(self, ctx, user): - await ctx.defer() - await self.bot.games.gameLoops.connectFour(ctx, "start "+user.display_name) + await self.bot.games.connectFour.start(ctx, user) # Start a game of connect four against gwendolyn @cog_ext.cog_subcommand(**params["connectFourStartGwendolyn"]) async def connectFourStartGwendolyn(self, ctx, difficulty = 3): - await ctx.defer() - await self.bot.games.gameLoops.connectFour(ctx, "start "+str(difficulty)) + await self.bot.games.connectFour.start(ctx, difficulty) # Stop the current game of connect four - @cog_ext.cog_subcommand(**params["connectFourStop"]) - async def connectFourStop(self, ctx): - await self.bot.games.gameLoops.connectFour(ctx, "stop") - - # Place a piece in the current game of connect four - @cog_ext.cog_subcommand(**params["connectFourPlace"]) - async def connectFourPlace(self, ctx, column): - await self.bot.games.gameLoops.connectFour(ctx, "place "+str(column)) + @cog_ext.cog_subcommand(**params["connectFourSurrender"]) + async def connectFourSurrender(self, ctx): + await self.bot.games.connectFour.surrender(ctx) class HangmanCog(commands.Cog): def __init__(self,bot): """Runs game stuff.""" self.bot = bot + # Starts a game of Hangman @cog_ext.cog_subcommand(**params["hangmanStart"]) async def hangmanStart(self, ctx): - await ctx.defer() - await self.bot.games.gameLoops.runHangman(ctx.channel,"#"+str(ctx.author.id),"start", ctx) + await self.bot.games.hangman.start(ctx) # Stops a game of Hangman @cog_ext.cog_subcommand(**params["hangmanStop"]) async def hangmanStop(self, ctx): - await self.bot.games.gameLoops.runHangman(ctx.channel,"#"+str(ctx.author.id),"stop", ctx) + await self.bot.games.hangman.stop(ctx) class HexCog(commands.Cog): def __init__(self,bot): """Runs game stuff.""" self.bot = bot + # Start a game of Hex against another user @cog_ext.cog_subcommand(**params["hexStartUser"]) async def hexStartUser(self, ctx, user): - await ctx.defer() - await self.bot.games.gameLoops.runHex(ctx, "start "+user.display_name, "#"+str(ctx.author.id)) + await self.bot.games.hex.start(ctx, user) # Start a game of Hex against Gwendolyn @cog_ext.cog_subcommand(**params["hexStartGwendolyn"]) async def hexStartGwendolyn(self, ctx, difficulty = 2): - await ctx.defer() - await self.bot.games.gameLoops.runHex(ctx, "start "+str(difficulty), "#"+str(ctx.author.id)) - - # Undo your last hex move - @cog_ext.cog_subcommand(**params["hexUndo"]) - async def hexUndo(self, ctx): - await self.bot.games.gameLoops.runHex(ctx, "undo", "#"+str(ctx.author.id)) - - # Perform a hex swap - @cog_ext.cog_subcommand(**params["hexSwap"]) - async def hexSwap(self, ctx): - await self.bot.games.gameLoops.runHex(ctx, "swap", "#"+str(ctx.author.id)) - - # Surrender the hex game - @cog_ext.cog_subcommand(**params["hexSurrender"]) - async def hexSurrender(self, ctx): - await self.bot.games.gameLoops.runHex(ctx, "surrender", "#"+str(ctx.author.id)) + await self.bot.games.hex.start(ctx, difficulty) # Place a piece in the hex game @cog_ext.cog_subcommand(**params["hexPlace"]) async def hexPlace(self, ctx, coordinates): - await self.bot.games.gameLoops.runHex(ctx, "place "+coordinates, "#"+str(ctx.author.id)) + await self.bot.games.hex.placeHex(ctx, coordinates, f"#{ctx.author.id}") + + # Undo your last hex move + @cog_ext.cog_subcommand(**params["hexUndo"]) + async def hexUndo(self, ctx): + await self.bot.games.hex.undo(ctx) + + # Perform a hex swap + @cog_ext.cog_subcommand(**params["hexSwap"]) + async def hexSwap(self, ctx): + await self.bot.games.hex.swap(ctx) + + # Surrender the hex game + @cog_ext.cog_subcommand(**params["hexSurrender"]) + async def hexSurrender(self, ctx): + await self.bot.games.hex.surrender(ctx) + def setup(bot): bot.add_cog(GamesCog(bot)) diff --git a/cogs/LookupCog.py b/cogs/LookupCog.py index aa1366e..1b23cd1 100644 --- a/cogs/LookupCog.py +++ b/cogs/LookupCog.py @@ -1,9 +1,7 @@ -import discord, json from discord.ext import commands from discord_slash import cog_ext -from discord_slash import SlashCommandOptionType as scot -from utils import getParams, cap +from utils import getParams params = getParams() @@ -15,58 +13,12 @@ class LookupCog(commands.Cog): # Looks up a spell @cog_ext.cog_slash(**params["spell"]) async def spell(self, ctx, query): - spell = self.bot.lookupFuncs.spellFunc(cap(query)) - if len(spell) > 2000: - await ctx.send(spell[:2000]) - await ctx.send(spell[2000:]) - else: - await ctx.send(spell) + await self.bot.lookupFuncs.spellFunc(ctx, query) # Looks up a monster @cog_ext.cog_slash(**params["monster"]) async def monster(self, ctx, query): - title, text1, text2, text3, text4, text5 = self.bot.lookupFuncs.monsterFunc(cap(query)) - em1 = discord.Embed(title = title, description = text1, colour=0xDEADBF) - - # Sends the received information. Separates into separate messages if - # there is too much text - await ctx.send(embed = em1) - if text2 != "": - if len(text2) < 2048: - em2 = discord.Embed(title = "Special Abilities", description = text2, colour=0xDEADBF) - await ctx.send(embed = em2) - else: - em2 = discord.Embed(title = "Special Abilities", description = text2[:2048], colour=0xDEADBF) - await ctx.send(embed = em2) - em2_2 = discord.Embed(title = "", description = text2[2048:], colour=0xDEADBF) - await ctx.send(embed = em2_2) - if text3 != "": - if len(text3) < 2048: - em3 = discord.Embed(title = "Actions", description = text3, colour=0xDEADBF) - await ctx.send(embed = em3) - else: - em3 = discord.Embed(title = "Actions", description = text3[:2048], colour=0xDEADBF) - await ctx.send(embed = em3) - em3_2 = discord.Embed(title = "", description = text3[2048:], colour=0xDEADBF) - await ctx.send(embed = em3_2) - if text4 != "": - if len(text4) < 2048: - em4 = discord.Embed(title = "Reactions", description = text4, colour=0xDEADBF) - await ctx.send(embed = em4) - else: - em4 = discord.Embed(title = "Reactions", description = text4[:2048], colour=0xDEADBF) - await ctx.send(embed = em4) - em4_2 = discord.Embed(title = "", description = text4[2048:], colour=0xDEADBF) - await ctx.send(embed = em4_2) - if text5 != "": - if len(text5) < 2048: - em5 = discord.Embed(title = "Legendary Actions", description = text5, colour=0xDEADBF) - await ctx.send(embed = em5) - else: - em5 = discord.Embed(title = "Legendary Actions", description = text5[:2048], colour=0xDEADBF) - await ctx.send(embed = em5) - em5_2 = discord.Embed(title = "", description = text5[2048:], colour=0xDEADBF) - await ctx.send(embed = em5_2) + await self.bot.lookupFuncs.monsterFunc(ctx, query) def setup(bot): bot.add_cog(LookupCog(bot)) \ No newline at end of file diff --git a/cogs/MiscCog.py b/cogs/MiscCog.py index 7ac34bf..7b4d940 100644 --- a/cogs/MiscCog.py +++ b/cogs/MiscCog.py @@ -2,16 +2,9 @@ import discord, codecs, string, json from discord.ext import commands from discord_slash import cog_ext -from utils import Options +from utils import getParams -with open("resources/slashParameters.json", "r") as f: - params = json.load(f) - -options = Options() - -if options.testing: - for p in params: - params[p]["guild_ids"] = options.guildIds +params = getParams() class MiscCog(commands.Cog): def __init__(self, bot): @@ -30,31 +23,12 @@ class MiscCog(commands.Cog): # Restarts the bot @cog_ext.cog_slash(**params["stop"]) async def stop(self, ctx): - if "#"+str(ctx.author.id) in self.bot.options.admins: - await ctx.send("Pulling git repo and restarting...") - - self.bot.databaseFuncs.stopServer() - - self.bot.log("Logging out.") - await self.bot.logout() - else: - self.bot.log(f"{ctx.author.display_name} tried to stop me! (error code 201)",str(ctx.channel_id)) - await ctx.send(f"I don't think I will, {ctx.author.display_name} (error code 201)") + await self.bot.stop(ctx) # Gets help for specific command @cog_ext.cog_slash(**params["help"]) async def helpCommand(self, ctx, command = ""): - if command == "": - with codecs.open("resources/help/help.txt",encoding="utf-8") as f: - text = f.read() - em = discord.Embed(title = "Help", description = text,colour = 0x59f442) - await ctx.send(embed = em) - else: - self.bot.log(f"Looking for help-{command}.txt",str(ctx.channel_id)) - with codecs.open(f"resources/help/help-{command}.txt",encoding="utf-8") as f: - text = f.read() - em = discord.Embed(title = command.capitalize(), description = text,colour = 0x59f442) - await ctx.send(embed = em) + await self.bot.other.helpFunc(ctx, command) # Lets you thank the bot @cog_ext.cog_slash(**params["thank"]) @@ -64,67 +38,51 @@ class MiscCog(commands.Cog): # Sends a friendly message @cog_ext.cog_slash(**params["hello"]) async def hello(self, ctx): - await ctx.send(self.bot.other.helloFunc(ctx.author.display_name)) + await self.bot.other.helloFunc(ctx) # Rolls dice @cog_ext.cog_slash(**params["roll"]) async def roll(self, ctx, dice = "1d20"): - await ctx.send(self.bot.other.rollDice(ctx.author.display_name, dice)) + await self.bot.other.rollDice(ctx, dice) # Sends a random image @cog_ext.cog_slash(**params["image"]) async def image(self, ctx): - await ctx.defer() - await ctx.send(self.bot.other.imageFunc()) + await self.bot.other.imageFunc(ctx) # Finds a random movie @cog_ext.cog_slash(**params["movie"]) - async def movie(self,ctx): + async def movie(self, ctx): await self.bot.other.movieFunc(ctx) # Generates a random name @cog_ext.cog_slash(**params["name"]) async def name(self, ctx): - await ctx.send(self.generators.nameGen()) + await self.generators.nameGen(ctx) # Generates a random tavern name @cog_ext.cog_slash(**params["tavern"]) async def tavern(self, ctx): - await ctx.send(self.generators.tavernGen()) + await self.generators.tavernGen(ctx) # Finds a page on the Senkulpa wiki @cog_ext.cog_slash(**params["wiki"]) async def wiki(self, ctx, wikiPage): - await ctx.defer() - command = string.capwords(wikiPage) - title, content, thumbnail = self.bot.otherfindWikiPage(command) - if title != "": - self.bot.log("Sending the embedded message",str(ctx.channel_id)) - content += "\n[Læs mere](https://senkulpa.fandom.com/da/wiki/"+title.replace(" ","_")+")" - embed = discord.Embed(title = title, description = content, colour=0xDEADBF) - if thumbnail != "": - embed.set_thumbnail(url=thumbnail) - - await ctx.send(embed = embed) - else: - await ctx.send(content) + await self.bot.other.findWikiPage(ctx, wikiPage) #Searches for movie and adds it to Bedre Netflix @cog_ext.cog_slash(**params["addMovie"]) async def addMovie(self, ctx, movie): - await ctx.defer() await self.bedreNetflix.requestMovie(ctx, movie) #Searches for show and adds it to Bedre Netflix @cog_ext.cog_slash(**params["addShow"]) async def addShow(self, ctx, show): - await ctx.defer() await self.bedreNetflix.requestShow(ctx, show) #Returns currently downloading torrents @cog_ext.cog_slash(**params["downloading"]) async def downloading(self, ctx, parameters = "-d"): - await ctx.defer() await self.bedreNetflix.downloading(ctx, parameters) #Looks up on Wolfram Alpha diff --git a/cogs/ReactionCog.py b/cogs/ReactionCog.py deleted file mode 100644 index 14b4cc0..0000000 --- a/cogs/ReactionCog.py +++ /dev/null @@ -1,46 +0,0 @@ -from discord.ext import commands - -from utils import emojiToCommand - -class ReactionCog(commands.Cog): - def __init__(self, bot): - """Listens for reactions.""" - self.bot = bot - - @commands.Cog.listener() - async def on_reaction_add(self, reaction, user): - if user.bot == False: - message = reaction.message - channel = message.channel - self.bot.log(f"{user.display_name} reacted to a message",str(channel.id)) - try: - connectFourTheirTurn, piece = self.bot.databaseFuncs.connectFourReactionTest(channel,message,"#"+str(user.id)) - except: - connectFourTheirTurn = False - - bedreNetflixMessage, addMovie, imdbIds = self.bot.databaseFuncs.bedreNetflixReactionTest(channel, message) - - if connectFourTheirTurn: - place = emojiToCommand(reaction.emoji) - await self.bot.games.gameLoops.connectFour(message,"place "+str(piece)+" "+str(place),user.id, str(message.channel.id)) - elif bedreNetflixMessage and addMovie: - moviePick = emojiToCommand(reaction.emoji) - await message.delete() - if moviePick == "none": - imdbID = None - else: - imdbID = imdbIds[moviePick-1] - await self.bot.other.bedreNetflix.addMovie(channel,imdbID) - elif bedreNetflixMessage and not addMovie: - showPick = emojiToCommand(reaction.emoji) - await message.delete() - if showPick == "none": - imdbName = None - else: - imdbName = imdbIds[showPick-1] - await self.bot.other.bedreNetflix.addShow(channel,imdbName) - elif self.bot.databaseFuncs.hangmanReactionTest(channel,message) and ord(reaction.emoji) in range(127462,127488): - guess = chr(ord(reaction.emoji)-127397) - await self.bot.games.gameLoops.runHangman(channel,"#"+str(user.id),command="guess "+guess) -def setup(bot): - bot.add_cog(ReactionCog(bot)) diff --git a/cogs/StarWarsCog.py b/cogs/StarWarsCog.py index 850e4f2..57e39f0 100644 --- a/cogs/StarWarsCog.py +++ b/cogs/StarWarsCog.py @@ -2,16 +2,9 @@ import discord, string, json from discord.ext import commands from discord_slash import cog_ext -from utils import Options, cap +from utils import getParams -with open("resources/slashParameters.json", "r") as f: - params = json.load(f) - -options = Options() - -if options.testing: - for p in params: - params[p]["guild_ids"] = options.guildIds +params = getParams() class starWarsCog(commands.Cog): @@ -22,46 +15,23 @@ class starWarsCog(commands.Cog): # Rolls star wars dice @cog_ext.cog_slash(**params["starWarsRoll"]) async def starWarsRoll(self, ctx, dice = ""): - command = cap(dice) - newMessage = self.bot.starWars.roll.parseRoll("#"+str(ctx.author.id),command) - messageList = newMessage.split("\n") - await ctx.send(messageList[0]) - if len(messageList) > 1: - for messageItem in messageList[1:]: - await ctx.channel.send(messageItem) + await self.bot.starWars.roll.parseRoll(ctx, dice) # Controls destiny points @cog_ext.cog_slash(**params["starWarsDestiny"]) async def starWarsDestiny(self, ctx, parameters = ""): - newMessage = self.bot.starWars.destiny.parseDestiny("#"+str(ctx.author.id),parameters) - messageList = newMessage.split("\n") - await ctx.send(messageList[0]) - if len(messageList) > 1: - for messageItem in messageList[1:]: - await ctx.channel.send(messageItem) + await self.bot.starWars.destiny.parseDestiny(ctx, parameters) # Rolls for critical injuries @cog_ext.cog_slash(**params["starWarsCrit"]) async def starWarsCrit(self, ctx, severity : int = 0): - newMessage = self.bot.starWars.roll.critRoll(int(severity)) - - messageList = newMessage.split("\n") - await ctx.send(messageList[0]) - if len(messageList) > 1: - for messageItem in messageList[1:]: - await ctx.channel.send(messageItem) + await self.bot.starWars.roll.critRoll(ctx, severity) # Accesses and changes character sheet data with the parseChar function # from funcs/starWarsFuncs/starWarsCharacter.py @cog_ext.cog_slash(**params["starWarsCharacter"]) async def starWarsCharacter(self, ctx, parameters = ""): - command = string.capwords(parameters.replace("+","+ ").replace("-","- ").replace(",",", ")) - title, desc = self.bot.starWars.character.parseChar("#"+str(ctx.author.id),command) - if title != "": - em1 = discord.Embed(title = title, description = desc, colour=0xDEADBF) - await ctx.send(embed = em1) - else: - await ctx.send(desc) + await self.bot.starWars.character.parseChar(ctx, parameters) def setup(bot): bot.add_cog(starWarsCog(bot)) \ No newline at end of file diff --git a/funcs/games/__init__.py b/funcs/games/__init__.py index b1839ad..8d19ead 100644 --- a/funcs/games/__init__.py +++ b/funcs/games/__init__.py @@ -3,4 +3,4 @@ __all__ = ["Money", "Games"] from .money import Money -from .games import Games +from .gamesContainer import Games diff --git a/funcs/games/blackjack.py b/funcs/games/blackjack.py index 7fb9c02..e111f10 100644 --- a/funcs/games/blackjack.py +++ b/funcs/games/blackjack.py @@ -18,7 +18,7 @@ class Blackjack(): def blackjackShuffle(self, decks, channel): self.bot.log("Shuffling the blackjack deck") - with open("resources/games/deckofCards.txt","r") as f: + with open("resources/games/deckOfCards.txt","r") as f: deck = f.read() allDecks = deck.split("\n") * decks @@ -170,175 +170,223 @@ class Blackjack(): return hand, allStanding, preAllStanding - # When players try to hit - def blackjackHit(self,channel,user,handNumber = 0): + async def hit(self, ctx, handNumber = 0): + try: + await ctx.defer() + except: + self.bot.log("Defer failed") + channel = str(ctx.channel_id) + user = f"#{ctx.author.id}" + roundDone = False + game = self.bot.database["blackjack games"].find_one({"_id":channel}) if user in game["user hands"]: - hand, handNumber = self.getHandNumber(game["user hands"][user],handNumber) + userHands = game["user hands"][user] + hand, handNumber = self.getHandNumber(userHands, handNumber) - if hand != None: - if game["round"] > 0: - if hand["hit"] == False: - if hand["standing"] == False: - hand["hand"].append(self.drawCard(channel)) - hand["hit"] = True - - handValue = self.calcHandValue(hand["hand"]) - - if handValue > 21: - hand["busted"] = True - - if handNumber == 2: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".other hand":hand}}) - elif handNumber == 3: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".third hand":hand}}) - elif handNumber == 4: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".fourth hand":hand}}) - else: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user:hand}}) - - response = "accept" - roundDone = self.isRoundDone(self.bot.database["blackjack games"].find_one({"_id":channel})) - - return response + str(roundDone)[0] + str(game["round"]) - else: - self.bot.log(user+" is already standing") - return "You can't hit when you're standing" - else: - self.bot.log(user+" has already hit this round") - return "You've already hit this round" - else: - self.bot.log(user+" tried to hit on the 0th round") - return "You can't hit before you see your cards" + if hand == None: + logMessage = "They didn't specify a hand" + sendMessage = "You need to specify a hand" + elif game["round"] <= 0: + logMessage = "They tried to hit on the 0th round" + sendMessage = "You can't hit before you see your cards" + elif hand["hit"]: + logMessage = "They've already hit this round" + sendMessage = "You've already hit this round" + elif hand["standing"]: + logMessage = "They're already standing" + sendMessage = "You can't hit when you're standing" else: - self.bot.log(user+" didn't specify a hand") - return "You need to specify a hand" - else: - self.bot.log(user+" tried to hit without being in the game") - return "You have to enter the game before you can hit" + hand["hand"].append(self.drawCard(channel)) + hand["hit"] = True + handValue = self.calcHandValue(hand["hand"]) + + if handValue > 21: + hand["busted"] = True + + if handNumber == 2: + self.bot.database["blackjack games"].update_one({"_id":channel}, + {"$set":{"user hands."+user+".other hand":hand}}) + elif handNumber == 3: + self.bot.database["blackjack games"].update_one({"_id":channel}, + {"$set":{"user hands."+user+".third hand":hand}}) + elif handNumber == 4: + self.bot.database["blackjack games"].update_one({"_id":channel}, + {"$set":{"user hands."+user+".fourth hand":hand}}) + else: + self.bot.database["blackjack games"].update_one({"_id":channel}, + {"$set":{"user hands."+user:hand}}) + + roundDone = self.isRoundDone(self.bot.database["blackjack games"].find_one({"_id":channel})) + + sendMessage = f"{ctx.author.display_name} hit" + logMessage = "They succeeded" + else: + logMessage = "They tried to hit without being in the game" + sendMessage = "You have to enter the game before you can hit" + + await ctx.send(sendMessage) + self.bot.log(logMessage) + + if roundDone: + gameID = game["gameID"] + self.bot.log("Hit calling self.blackjackLoop()", channel) + await self.blackjackLoop(ctx.channel, game["round"]+1, gameID) # When players try to double down - def blackjackDouble(self,channel,user,handNumber = 0): + async def double(self, ctx, handNumber = 0): + try: + await ctx.defer() + except: + self.bot.log("Defer failed") + channel = str(ctx.channel_id) + user = f"#{ctx.author.id}" + roundDone = False + game = self.bot.database["blackjack games"].find_one({"_id":channel}) if user in game["user hands"]: hand, handNumber = self.getHandNumber(game["user hands"][user],handNumber) - if hand != None: - if game["round"] > 0: - if hand["hit"] == False: - if hand["standing"] == False: - if len(hand["hand"]) == 2: - bet = hand["bet"] - if self.bot.money.checkBalance(user) >= bet: - self.bot.money.addMoney(user,-1 * bet) - - hand["hand"].append(self.drawCard(channel)) - hand["hit"] = True - hand["doubled"] = True - hand["bet"] += bet - - handValue = self.calcHandValue(hand["hand"]) - - - if handValue > 21: - hand["busted"] = True - - if handNumber == 2: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".other hand":hand}}) - elif handNumber == 3: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".third hand":hand}}) - elif handNumber == 4: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".fourth hand":hand}}) - else: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user:hand}}) - - roundDone = self.isRoundDone(self.bot.database["blackjack games"].find_one({"_id":channel})) - - return "Adding another "+str(bet)+" GwendoBucks to "+self.bot.databaseFuncs.getName(user)+"'s bet and drawing another card.",str(roundDone)[0] + str(game["round"]) - else: - self.bot.log(user+" doesn't have enough GwendoBucks") - return "You don't have enough GwendoBucks","" - else: - self.bot.log(user+" tried to double on round "+str(game["round"])) - return "You can only double down on the first round","" - else: - self.bot.log(user+" is already standing") - return "You can't double when you're standing","" - else: - self.bot.log(user+" has already hit this round") - return "You've already hit this round","" - else: - self.bot.log(user+" tried to double on the 0th round") - return "You can't double down before you see your cards","" + if hand == None: + logMessage = "They didn't specify a hand" + sendMessage = "You need to specify a hand" + elif game["round"] <= 0: + logMessage = "They tried to hit on the 0th round" + sendMessage = "You can't hit before you see your cards" + elif hand["hit"]: + logMessage = "They've already hit this round" + sendMessage = "You've already hit this round" + elif hand["standing"]: + logMessage = "They're already standing" + sendMessage = "You can't hit when you're standing" + elif len(hand["hand"]) != 2: + logMessage = "They tried to double after round 1" + sendMessage = "You can only double on the first round" else: - self.bot.log(user+" didn't specify a hand") - return "You need to specify which hand" + bet = hand["bet"] + if self.bot.money.checkBalance(user) < bet: + logMessage = "They tried to double without being in the game" + sendMessage = "You can't double when you're not in the game" + else: + self.bot.money.addMoney(user,-1 * bet) + + hand["hand"].append(self.drawCard(channel)) + hand["hit"] = True + hand["doubled"] = True + hand["bet"] += bet + + handValue = self.calcHandValue(hand["hand"]) + + + if handValue > 21: + hand["busted"] = True + + if handNumber == 2: + self.bot.database["blackjack games"].update_one({"_id":channel}, + {"$set":{"user hands."+user+".other hand":hand}}) + elif handNumber == 3: + self.bot.database["blackjack games"].update_one({"_id":channel}, + {"$set":{"user hands."+user+".third hand":hand}}) + elif handNumber == 4: + self.bot.database["blackjack games"].update_one({"_id":channel}, + {"$set":{"user hands."+user+".fourth hand":hand}}) + else: + self.bot.database["blackjack games"].update_one({"_id":channel}, + {"$set":{"user hands."+user:hand}}) + + roundDone = self.isRoundDone(self.bot.database["blackjack games"].find_one({"_id":channel})) + + sendMessage = f"Adding another {bet} GwendoBucks to {self.bot.databaseFuncs.getName(user)}'s bet and drawing another card." + logMessage = "They succeeded" else: - self.bot.log(user+" tried to double without being in the game") - return "You can't double when you're not in the game","" + logMessage = "They tried to double without being in the game" + sendMessage = "You can't double when you're not in the game" + + await ctx.send(sendMessage) + self.bot.log(logMessage) + + if roundDone: + gameID = game["gameID"] + self.bot.log("Double calling self.blackjackLoop()", channel) + await self.blackjackLoop(ctx.channel, game["round"]+1, gameID) # When players try to stand - def blackjackStand(self,channel,user,handNumber = 0): + async def stand(self, ctx, handNumber = 0): + try: + await ctx.defer() + except: + self.bot.log("Defer failed") + channel = str(ctx.channel_id) + user = f"#{ctx.author.id}" + roundDone = False + game = self.bot.database["blackjack games"].find_one({"_id":channel}) if user in game["user hands"]: hand, handNumber = self.getHandNumber(game["user hands"][user],handNumber) - if hand != None: - if game["round"] > 0: - if hand["hit"] == False: - if hand["standing"] == False: - hand["standing"] = True - - if handNumber == 2: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".other hand":hand}}) - elif handNumber == 3: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".third hand":hand}}) - elif handNumber == 4: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".fourth hand":hand}}) - else: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user:hand}}) - - response = "accept" - roundDone = self.isRoundDone(self.bot.database["blackjack games"].find_one({"_id":channel})) - - return response + str(roundDone)[0] + str(game["round"]) - else: - self.bot.log(user+" is already standing") - return "You're already standing" - else: - self.bot.log(user+" has already hit this round") - return "You've already hit this round" - else: - self.bot.log(user+" tried to stand on the first round") - return "You can't stand before you see your cards" + if hand == None: + sendMessage = "You need to specify which hand" + logMessage = "They didn't specify a hand" + elif game["round"] <= 0: + sendMessage = "You can't stand before you see your cards" + logMessage = "They tried to stand on round 0" + elif hand["hit"]: + sendMessage = "You've already hit this round" + logMessage = "They'd already hit this round" + elif hand["standing"]: + sendMessage = "You're already standing" + logMessage = "They're already standing" else: - self.bot.log(user+" didn't specify a hand") - return "You need to specify which hand" + hand["standing"] = True + + if handNumber == 2: + self.bot.database["blackjack games"].update_one({"_id":channel}, + {"$set":{"user hands."+user+".other hand":hand}}) + elif handNumber == 3: + self.bot.database["blackjack games"].update_one({"_id":channel}, + {"$set":{"user hands."+user+".third hand":hand}}) + elif handNumber == 4: + self.bot.database["blackjack games"].update_one({"_id":channel}, + {"$set":{"user hands."+user+".fourth hand":hand}}) + else: + self.bot.database["blackjack games"].update_one({"_id":channel}, + {"$set":{"user hands."+user:hand}}) + + roundDone = self.isRoundDone(self.bot.database["blackjack games"].find_one({"_id":channel})) + + sendMessage = f"{ctx.author.display_name} is standing" + logMessage = "They succeeded" + else: - self.bot.log(user+" tried to stand without being in the game") - return "You have to enter the game before you can stand" + logMessage = "They tried to stand without being in the game" + sendMessage = "You have to enter the game before you can stand" + + await ctx.send(sendMessage) + self.bot.log(logMessage) + + if roundDone: + gameID = game["gameID"] + self.bot.log("Stand calling self.blackjackLoop()", channel) + await self.blackjackLoop(ctx.channel, game["round"]+1, gameID) # When players try to split - def blackjackSplit(self,channel,user,handNumber = 0): + async def split(self, ctx, handNumber = 0): + try: + await ctx.defer() + except: + self.bot.log("Defer failed") + channel = str(ctx.channel_id) + user = f"#{ctx.author.id}" + roundDone = False + handNumberError = False + game = self.bot.database["blackjack games"].find_one({"_id":channel}) if game["user hands"][user]["split"] == 0: @@ -347,166 +395,200 @@ class Blackjack(): handNumber = 0 otherHand = 2 else: - if handNumber != 0: - if handNumber == 1: - hand = game["user hands"][user] - elif handNumber == 2: - hand = game["user hands"][user]["other hand"] - elif handNumber == 3: - hand = game["user hands"][user]["third hand"] - else: - self.bot.log(user+" tried to hit without specifying which hand") - return "You have to specify the hand you're hitting with." - - if game["user hands"][user]["split"] == 1: - newHand = game["user hands"][user]["third hand"] - otherHand = 3 - else: - newHand = game["user hands"][user]["fourth hand"] - otherHand = 4 + if handNumber == 1: + hand = game["user hands"][user] + elif handNumber == 2: + hand = game["user hands"][user]["other hand"] + elif handNumber == 3: + hand = game["user hands"][user]["third hand"] else: - self.bot.log(user+" tried to split without specifying which hand") - return "You have to specify the hand you're splitting.","" + handNumberError = True - if game["user hands"][user]["split"] < 3: - if game["round"] != 0: - if hand["hit"] == False: - if hand["standing"] == False: - if len(hand["hand"]) == 2: - firstCard = self.calcHandValue([hand["hand"][0]]) - secondCard = self.calcHandValue([hand["hand"][1]]) - if firstCard == secondCard: - bet = hand["bet"] - if self.bot.money.checkBalance(user) >= bet: - self.bot.money.addMoney(user,-1 * bet) - - hand["hit"] = True - newHand["hit"] = True - - newHand = { - "hand":[],"bet":0,"standing":False,"busted":False, - "blackjack":False,"hit":True,"doubled":False} - - newHand["bet"] = hand["bet"] - - newHand["hand"].append(hand["hand"].pop(1)) - newHand["hand"].append(self.drawCard(channel)) - hand["hand"].append(self.drawCard(channel)) - - handValue = self.calcHandValue(hand["hand"]) - otherHandValue = self.calcHandValue(newHand["hand"]) - if handValue > 21: - hand["busted"] = True - elif handValue == 21: - hand["blackjack"] = True - - if otherHandValue > 21: - newHand["busted"] = True - elif otherHandValue == 21: - newHand["blackjack"] = True - - if handNumber == 2: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".other hand":hand}}) - elif handNumber == 3: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".third hand":hand}}) - else: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user:hand}}) - - if otherHand == 3: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".third hand":newHand}}) - elif otherHand == 4: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".fourth hand":newHand}}) - else: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".other hand":newHand}}) - - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$inc":{"user hands."+user+".split":1}}) - - roundDone = self.isRoundDone(self.bot.database["blackjack games"].find_one({"_id":channel})) - - return "Splitting "+self.bot.databaseFuncs.getName(user)+"'s hand into 2. Adding their original bet to the second hand. You can use \"/blackjack hit/stand/double 1\" and \"/blackjack hit/stand/double 2\" to play the different hands.",str(roundDone)[0] + str(game["round"]) - else: - self.bot.log(user+" doesn't have enough GwendoBucks") - return "You don't have enough GwendoBucks","" - else: - self.bot.log(user+" tried to split 2 different cards") - return "Your cards need to have the same value to split","" - else: - self.bot.log(user+" tried to split later than they could") - return "You can only split on the first round","" - else: - self.bot.log(user+" is already standing") - return "You can't split when you're standing","" - else: - self.bot.log(user+" has already hit this round") - return "You've already hit this round","" + if game["user hands"][user]["split"] == 1: + newHand = game["user hands"][user]["third hand"] + otherHand = 3 else: - self.bot.log(user+" tried to split on the 0th round") - return "You can't split before you see your cards","" + newHand = game["user hands"][user]["fourth hand"] + otherHand = 4 + + if handNumberError: + logMessage = "They didn't specify a hand" + sendMessage = "You have to specify the hand you're hitting with" + elif game["round"] == 0: + logMessage = "They tried to split on round 0" + sendMessage = "You can't split before you see your cards" + elif game["user hands"][user]["split"] > 3: + logMessage = "They tried to split more than three times" + sendMessage = "You can only split 3 times" + elif hand["hit"]: + logMessage = "They've already hit" + sendMessage = "You've already hit" + elif hand["standing"]: + logMessage = "They're already standing" + sendMessage = "You're already standing" + elif len(hand["hand"]) != 2: + logMessage = "They tried to split after the first round" + sendMessage = "You can only split on the first round" else: - self.bot.log(user+" tried to split more than three times") - return "You can only split 3 times","" + firstCard = self.calcHandValue([hand["hand"][0]]) + secondCard = self.calcHandValue([hand["hand"][1]]) + if firstCard != secondCard: + logMessage = "They tried to split two different cards" + sendMessage = "You can only split if your cards have the same value" + else: + bet = hand["bet"] + if self.bot.money.checkBalance(user) < bet: + logMessage = "They didn't have enough GwendoBucks" + sendMessage = "You don't have enough GwendoBucks" + else: + self.bot.money.addMoney(user,-1 * bet) + + hand["hit"] = True + newHand["hit"] = True + + newHand = { + "hand":[],"bet":0,"standing":False,"busted":False, + "blackjack":False,"hit":True,"doubled":False} + + newHand["bet"] = hand["bet"] + + newHand["hand"].append(hand["hand"].pop(1)) + newHand["hand"].append(self.drawCard(channel)) + hand["hand"].append(self.drawCard(channel)) + + handValue = self.calcHandValue(hand["hand"]) + otherHandValue = self.calcHandValue(newHand["hand"]) + if handValue > 21: + hand["busted"] = True + elif handValue == 21: + hand["blackjack"] = True + + if otherHandValue > 21: + newHand["busted"] = True + elif otherHandValue == 21: + newHand["blackjack"] = True + + if handNumber == 2: + self.bot.database["blackjack games"].update_one({"_id":channel}, + {"$set":{"user hands."+user+".other hand":hand}}) + elif handNumber == 3: + self.bot.database["blackjack games"].update_one({"_id":channel}, + {"$set":{"user hands."+user+".third hand":hand}}) + else: + self.bot.database["blackjack games"].update_one({"_id":channel}, + {"$set":{"user hands."+user:hand}}) + + if otherHand == 3: + self.bot.database["blackjack games"].update_one({"_id":channel}, + {"$set":{"user hands."+user+".third hand":newHand}}) + elif otherHand == 4: + self.bot.database["blackjack games"].update_one({"_id":channel}, + {"$set":{"user hands."+user+".fourth hand":newHand}}) + else: + self.bot.database["blackjack games"].update_one({"_id":channel}, + {"$set":{"user hands."+user+".other hand":newHand}}) + + self.bot.database["blackjack games"].update_one({"_id":channel}, + {"$inc":{"user hands."+user+".split":1}}) + + roundDone = self.isRoundDone(self.bot.database["blackjack games"].find_one({"_id":channel})) + + sendMessage = f"Splitting {self.bot.databaseFuncs.getName(user)}'s hand into 2. Adding their original bet to the second hand. You can use \"/blackjack hit/stand/double 1\" and \"/blackjack hit/stand/double 2\" to play the different hands." + logMessage = "They succeeded" + + await ctx.send(sendMessage) + self.bot.log(logMessage) + + if roundDone: + gameID = game["gameID"] + self.bot.log("Stand calling self.blackjackLoop()", channel) + await self.blackjackLoop(ctx.channel, game["round"]+1, gameID) # Player enters the game and draws a hand - def blackjackPlayerDrawHand(self,channel,user,bet): - game = self.bot.database["blackjack games"].find_one({"_id":channel}) + async def playerDrawHand(self, ctx, bet : int): + try: + await ctx.defer() + except: + self.bot.log("Defer failed") + channel = str(ctx.channel_id) + user = f"#{ctx.author.id}" + collection = self.bot.database["blackjack games"] + game = collection.find_one({"_id":channel}) + userName = self.bot.databaseFuncs.getName(user) - self.bot.log(self.bot.databaseFuncs.getName(user)+" is trying to join the game in "+channel) + self.bot.log(f"{userName} is trying to join the Blackjack game") - if game != None: - if user not in game["user hands"]: - if len(game["user hands"]) < 5: - if game["round"] == 0: - if bet >= 0: - if self.bot.money.checkBalance(user) >= bet: - self.bot.money.addMoney(user,-1 * bet) - playerHand = [self.drawCard(channel),self.drawCard(channel)] - - handValue = self.calcHandValue(playerHand) - - if handValue == 21: - blackjackHand = True - else: - blackjackHand = False - - newHand = {"hand":playerHand, - "bet":bet,"standing":False,"busted":False,"blackjack":blackjackHand,"hit":True, - "doubled":False,"split":0,"other hand":{},"third hand":{},"fourth hand":{}} - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user:newHand}}) - - self.bot.log(f"{self.bot.databaseFuncs.getName(user)} entered the game with a bet of {bet}") - return f"{self.bot.databaseFuncs.getName(user)} entered the game with a bet of {bet}" - else: - self.bot.log(user+" doesn't have enough GwendoBucks") - return "You don't have enough GwendoBucks to place that bet" - else: - self.bot.log(user+" tried to bet a negative amount") - return "You can't bet a negative amount" - else: - self.bot.log("The table is no longer open for bets") - return "The table is no longer open for bets" - else: - self.bot.log("There are already 5 players in the game.") - return "There's already a maximum of players at the table." - else: - self.bot.log(user+" is already in the game") - return "You've already entered this game" + if game == None: + sendMessage = "There is no game going on in this channel" + logMessage = sendMessage + elif user in game["user hands"]: + sendMessage = "You're already in the game!" + logMessage = "They're already in the game" + elif len(game["user hands"]) >= 5: + sendMessage = "There can't be more than 5 players in a game" + logMessage = "There were already 5 players in the game" + elif game["round"] != 0: + sendMessage = "The table is no longer taking bets" + logMessage = "They tried to join after the game begun" + elif bet < 0: + sendMessage = "You can't bet a negative amount" + logMessage = "They tried to bet a negative amount" + elif self.bot.money.checkBalance(user) < bet: + sendMessage = "You don't have enough GwendoBucks" + logMessage = "They didn't have enough GwendoBucks" else: - self.bot.log("There is no game going on in "+channel) - return "There is no game going on in this channel" + self.bot.money.addMoney(user,-1 * bet) + playerHand = [self.drawCard(channel) for _ in range(2)] + + handValue = self.calcHandValue(playerHand) + + if handValue == 21: + blackjackHand = True + else: + blackjackHand = False + + newHand = {"hand":playerHand, "bet":bet, "standing":False, + "busted":False, "blackjack":blackjackHand, "hit":True, + "doubled":False, "split":0, "other hand":{}, + "third hand":{}, "fourth hand":{}} + + function = {"$set":{f"user hands.{user}":newHand}} + collection.update_one({"_id":channel}, function) + + enterGameText = "entered the game with a bet of" + betText = f"{bet} GwendoBucks" + sendMessage = f"{userName} {enterGameText} {betText}" + logMessage = sendMessage + + self.bot.log(sendMessage) + await ctx.send(logMessage) # Starts a game of blackjack - def blackjackStart(self,channel:str): + async def start(self, ctx): + try: + await ctx.defer() + except: + self.bot.log("Defer failed") + channel = str(ctx.channel_id) + blackjackMinCards = 50 + blackjackDecks = 4 + + await ctx.send("Starting a new game of blackjack") + cardsLeft = 0 + cards = self.bot.database["blackjack cards"].find_one({"_id":channel}) + if cards != None: + cardsLeft = len(cards["cards"]) + + # Shuffles if not enough cards + if cardsLeft < blackjackMinCards: + self.blackjackShuffle(blackjackDecks, channel) + self.bot.log("Shuffling the blackjack deck...", channel) + await ctx.channel.send("Shuffling the deck...") + game = self.bot.database["blackjack games"].find_one({"_id":channel}) self.bot.log("Trying to start a blackjack game in "+channel) + gameStarted = False if game == None: @@ -522,10 +604,39 @@ class Blackjack(): copyfile("resources/games/blackjackTable.png","resources/games/blackjackTables/blackjackTable"+channel+".png") - return "started" + gameStarted = True + + if gameStarted: + sendMessage = "Blackjack game started. Use \"/blackjack bet [amount]\" to enter the game within the next 30 seconds." + await ctx.channel.send(sendMessage) + filePath = f"resources/games/blackjackTables/blackjackTable{channel}.png" + + oldImage = await ctx.channel.send(file = discord.File(filePath)) + + with open("resources/games/oldImages/blackjack"+channel, "w") as f: + f.write(str(oldImage.id)) + + await asyncio.sleep(30) + + gamedone = False + + game = self.bot.database["blackjack games"].find_one({"_id":str(channel)}) + + if len(game["user hands"]) == 0: + gamedone = True + await ctx.channel.send("No one entered the game. Ending the game.") + gameID = game["gameID"] + + # Loop of game rounds + if gamedone == False: + self.bot.log("start() calling blackjackLoop()", channel) + await self.blackjackLoop(ctx.channel,1,gameID) + else: + new_message = self.blackjackFinish(channel) + await ctx.channel.send(new_message) else: - self.bot.log("There is already a blackjack game going on in "+channel) - return "There's already a blackjack game going on. Try again in a few minutes." + await ctx.channel.send("There's already a blackjack game going on. Try again in a few minutes.") + self.bot.log("There was already a game going on") # Ends the game and calculates winnings def blackjackFinish(self,channel): @@ -562,7 +673,6 @@ class Blackjack(): return finalWinnings - def calcWinnings(self,hand, dealerValue, topLevel, dealerBlackjack, dealerBusted): self.bot.log("Calculating winnings") reason = "" @@ -703,178 +813,34 @@ class Blackjack(): else: self.bot.log("Ending loop on round "+str(gameRound),str(channel.id)) - async def parseBlackjack(self,content, ctx): - # Blackjack shuffle variables - blackjackMinCards = 50 - blackjackDecks = 4 - +# Returning current hi-lo value + async def hilo(self, ctx): channel = ctx.channel_id - # Starts the game - if content == "": - await ctx.send("Starting a new game of blackjack") - cardsLeft = 0 - cards = self.bot.database["blackjack cards"].find_one({"_id":str(channel)}) - if cards != None: - cardsLeft = len(cards["cards"]) - - # Shuffles if not enough cards - if cardsLeft < blackjackMinCards: - self.blackjackShuffle(blackjackDecks,str(channel)) - self.bot.log("Shuffling the blackjack deck...",str(channel)) - await ctx.channel.send("Shuffling the deck...") - - new_message = self.blackjackStart(str(channel)) - if new_message == "started": - - new_message = "Blackjack game started. Use \"/blackjack bet [amount]\" to enter the game within the next 30 seconds." - await ctx.channel.send(new_message) - oldImage = await ctx.channel.send(file = discord.File("resources/games/blackjackTables/blackjackTable"+str(channel)+".png")) - - with open("resources/games/oldImages/blackjack"+str(channel), "w") as f: - f.write(str(oldImage.id)) - - await asyncio.sleep(30) - - gamedone = False - - game = self.bot.database["blackjack games"].find_one({"_id":str(channel)}) - - if len(game["user hands"]) == 0: - gamedone = True - await ctx.channel.send("No one entered the game. Ending the game.") - gameID = game["gameID"] - - # Loop of game rounds - if gamedone == False: - self.bot.log("/blackjack calling self.blackjackLoop()",str(channel)) - await self.blackjackLoop(ctx.channel,1,gameID) - else: - new_message = self.blackjackFinish(str(channel)) - await ctx.channel.send(new_message) - else: - await ctx.channel.send(new_message) - - # Entering game and placing bet - elif content.startswith("bet"): - commands = content.split(" ") - amount = int(commands[1]) - response = self.blackjackPlayerDrawHand(str(channel),"#"+str(ctx.author.id),amount) - await ctx.send(response) - - # Hitting - elif content.startswith("hit"): - if content == "hit": - response = self.blackjackHit(str(channel),"#"+str(ctx.author.id)) - else: - commands = content.split(" ") - try: - handNumber = int(commands[1]) - except: - handNumber = 0 - response = self.blackjackHit(str(channel),"#"+str(ctx.author.id),handNumber) - - if response.startswith("accept"): - await ctx.send(f"{ctx.author.display_name} hit") - #try: - if response[6] == "T": - gameID = self.bot.database["blackjack games"].find_one({"_id":str(channel)})["gameID"] - self.bot.log("Hit calling self.blackjackLoop()",str(channel)) - await self.blackjackLoop(ctx.channel,int(response[7:])+1,gameID) - #except: - # self.bot.log("Something fucked up (error code 1320)",str(channel)) - else: - await ctx.send(response) - - - # Standing - elif content.startswith("stand"): - if content == "hit": - response = self.blackjackStand(str(channel),"#"+str(ctx.author.id)) - else: - commands = content.split(" ") - try: - handNumber = int(commands[1]) - except: - handNumber = 0 - response = self.blackjackStand(str(channel),"#"+str(ctx.author.id),handNumber) - - if response.startswith("accept"): - await ctx.send(f"{ctx.author.display_name} is standing") - #try: - if response[6] == "T": - gameID = self.bot.database["blackjack games"].find_one({"_id":str(channel)})["gameID"] - self.bot.log("Stand calling self.blackjackLoop()",str(channel)) - await self.blackjackLoop(ctx.channel,int(response[7:])+1,gameID) - #except: - # self.bot.log("Something fucked up (error code 1320)",str(channel)) - else: - await ctx.send(response) - - # Doubling bet - elif content.startswith("double"): - commands = content.split(" ") - try: - handNumber = int(commands[1]) - except: - handNumber = 0 - response, roundDone = self.blackjackDouble(str(channel),"#"+str(ctx.author.id),handNumber) - - await ctx.send(response) - - try: - if roundDone[0] == "T": - gameID = self.bot.database["blackjack games"].find_one({"_id":str(channel)})["gameID"] - self.bot.log("Double calling self.blackjackLoop()",str(channel)) - await self.blackjackLoop(ctx.channel,int(roundDone[1:])+1,gameID) - except: - self.bot.log("Something fucked up (error code 1320)",str(channel)) - - # Splitting hand - elif content.startswith("split"): - commands = content.split(" ") - try: - handNumber = int(commands[1]) - except: - handNumber = 0 - response, roundDone = self.blackjackSplit(str(channel),"#"+str(ctx.author.id),handNumber) - - await ctx.send(response) - - try: - if roundDone[0] == "T": - gameID = self.bot.database["blackjack games"].find_one({"_id":str(channel)})["gameID"] - self.bot.log("Split calling self.blackjackLoop()",str(channel)) - await self.blackjackLoop(ctx.channel,int(roundDone[1:])+1,gameID) - except: - self.bot.log("Something fucked up (error code 1320)") - - # Returning current hi-lo value - elif content.startswith("hilo"): - data = self.bot.database["hilo"].find_one({"_id":str(channel)}) - if data != None: - hilo = str(data["hilo"]) - else: - hilo = "0" - await ctx.send(hilo, hidden=True) - - # Shuffles the blackjack deck - elif content.startswith("shuffle"): - self.blackjackShuffle(blackjackDecks,str(channel)) - self.bot.log("Shuffling the blackjack deck...",str(channel)) - await ctx.send("Shuffling the deck...") - - - # Tells you the amount of cards left - elif content.startswith("cards"): - cardsLeft = 0 - cards = self.bot.database["blackjack cards"].find_one({"_id":str(channel)}) - if cards != None: - cardsLeft = len(cards["cards"]) - - decksLeft = round(cardsLeft/52,1) - await ctx.send(str(cardsLeft)+" cards, "+str(decksLeft)+" decks", hidden=True) - + data = self.bot.database["hilo"].find_one({"_id":str(channel)}) + if data != None: + hilo = str(data["hilo"]) else: - self.bot.log("Not a command (error code 1301)") - await ctx.send("I didn't quite understand that (error code 1301)") + hilo = "0" + await ctx.send(f"Hi-lo value: {hilo}", hidden=True) + + # Shuffles the blackjack deck + async def shuffle(self, ctx): + blackjackDecks = 4 + channel = ctx.channel_id + self.blackjackShuffle(blackjackDecks,str(channel)) + self.bot.log("Shuffling the blackjack deck...",str(channel)) + await ctx.send("Shuffling the deck...") + + + # Tells you the amount of cards left + async def cards(self, ctx): + channel = ctx.channel_id + cardsLeft = 0 + cards = self.bot.database["blackjack cards"].find_one({"_id":str(channel)}) + if cards != None: + cardsLeft = len(cards["cards"]) + + decksLeft = round(cardsLeft/52,1) + await ctx.send(f"Cards left:\n{cardsLeft} cards, {decksLeft} decks", hidden=True) + diff --git a/funcs/games/connectFour.py b/funcs/games/connectFour.py index d655c62..d6fdf9f 100644 --- a/funcs/games/connectFour.py +++ b/funcs/games/connectFour.py @@ -1,10 +1,11 @@ import random import copy import math +import discord from .connectFourDraw import drawConnectFour -AIScores = { +AISCORES = { "middle": 3, "two in a row": 10, "three in a row": 50, @@ -15,75 +16,127 @@ AIScores = { "avoid losing": 100 } -rowCount = 6 -columnCount = 7 -easy = True +ROWCOUNT = 6 +COLUMNCOUNT = 7 -class connectFour(): +class ConnectFour(): def __init__(self,bot): self.bot = bot self.draw = drawConnectFour(bot) # Starts the game - def connectFourStart(self, channel, user, opponent): + async def start(self, ctx, opponent): + try: + await ctx.defer() + except: + self.bot.log("Defer failed") + user = f"#{ctx.author.id}" + channel = str(ctx.channel_id) game = self.bot.database["connect 4 games"].find_one({"_id":channel}) - if game == None: + startedGame = False + canStart = True - if opponent in ["1","2","3","4","5"]: - difficulty = int(opponent) - diffText = " with difficulty "+opponent - opponent = "Gwendolyn" - elif opponent.lower() == "gwendolyn": - difficulty = 3 - diffText = " with difficulty 3" - opponent = "Gwendolyn" - else: - try: - int(opponent) - return "That difficulty doesn't exist", False, False, False, False - except: + if game != None: + sendMessage = "There's already a connect 4 game going on in this channel" + logMessage = "There was already a game going on" + canStart = False + else: + if type(opponent) == int: + # Opponent is Gwendolyn + if opponent in range(1, 6): + difficulty = int(opponent) + diffText = f" with difficulty {difficulty}" + opponent = f"#{self.bot.user.id}" + else: + sendMessage = "Difficulty doesn't exist" + logMessage = "They tried to play against a difficulty that doesn't exist" + canStart = False + + elif type(opponent) == discord.member.Member: + if opponent.bot: + # User has challenged a bot + if opponent == self.bot.user: + # It was Gwendolyn + difficulty = 3 + diffText = f" with difficulty {difficulty}" + opponent = f"#{self.bot.user.id}" + else: + sendMessage = "You can't challenge a bot!" + logMessage = "They tried to challenge a bot" + canStart = False + else: # Opponent is another player - opponent = self.bot.databaseFuncs.getID(opponent) - if opponent != None: + if ctx.author != opponent: + opponent = f"#{opponent.id}" difficulty = 5 diffText = "" else: - return "I can't find that user", False, False, False, False + sendMessage = "You can't play against yourself" + logMessage = "They tried to play against themself" + canStart = False - if user == opponent: - return "You can't play against yourself", False, False, False, False - - board = [ [ 0 for i in range(columnCount) ] for j in range(rowCount) ] - players = [user,opponent] + if canStart: + board = [[0 for _ in range(COLUMNCOUNT)] for _ in range(ROWCOUNT)] + players = [user, opponent] random.shuffle(players) - newGame = {"_id":channel,"board": board,"winner":0,"win direction":"", - "win coordinates":[0,0],"players":players,"turn":0,"difficulty":difficulty} + newGame = {"_id":channel, "board": board, "winner":0, + "win direction":"", "win coordinates":[0, 0], + "players":players, "turn":0, "difficulty":difficulty} self.bot.database["connect 4 games"].insert_one(newGame) self.draw.drawImage(channel) - gwendoTurn = False + gwendoTurn = (players[0] == f"#{self.bot.user.id}") + startedGame = True - if players[0] == "Gwendolyn": - gwendoTurn = True + opponentName = self.bot.databaseFuncs.getName(opponent) + turnName = self.bot.databaseFuncs.getName(players[0]) - return "Started game against "+self.bot.databaseFuncs.getName(opponent)+diffText+". It's "+self.bot.databaseFuncs.getName(players[0])+"'s turn", True, False, False, gwendoTurn - else: - return "There's already a connect 4 game going on in this channel", False, False, False, False + startedText = f"Started game against {opponentName}{diffText}." + turnText = f"It's {turnName}'s turn" + sendMessage = f"{startedText} {turnText}" + logMessage = "They started a game" + + self.bot.log(logMessage) + await ctx.send(sendMessage) + + # Sets the whole game in motion + if startedGame: + filePath = f"resources/games/connect4Boards/board{ctx.channel_id}.png" + oldImage = await ctx.channel.send(file = discord.File(filePath)) + + with open(f"resources/games/oldImages/connectFour{ctx.channel_id}", "w") as f: + f.write(str(oldImage.id)) + + if gwendoTurn: + await self.connectFourAI(ctx) + else: + reactions = ["1️⃣","2️⃣","3️⃣","4️⃣","5️⃣","6️⃣","7️⃣"] + for reaction in reactions: + await oldImage.add_reaction(reaction) # Places a piece at the lowest available point in a specific column - def placePiece(self, channel : str,player : int,column : int): + async def placePiece(self, ctx, user, column): + channel = str(ctx.channel.id) game = self.bot.database["connect 4 games"].find_one({"_id":channel}) + playerNumber = game["players"].index(user)+1 + userName = self.bot.databaseFuncs.getName(user) + placedPiece = False - if game != None: + if game is None: + sendMessage = "There's no game in this channel" + logMessage = "There was no game in the channel" + else: board = game["board"] + board = self.placeOnBoard(board, playerNumber, column) - board = self.placeOnBoard(board,player,column) - - if board != None: + if board is None: + sendMessage = "There isn't any room in that column" + logMessage = "There wasn't any room in the column" + else: self.bot.database["connect 4 games"].update_one({"_id":channel},{"$set":{"board":board}}) turn = (game["turn"]+1)%2 self.bot.database["connect 4 games"].update_one({"_id":channel},{"$set":{"turn":turn}}) @@ -98,82 +151,115 @@ class connectFour(): self.bot.database["connect 4 games"].update_one({"_id":channel}, {"$set":{"win coordinates":winCoordinates}}) - message = self.bot.databaseFuncs.getName(game["players"][won-1])+" placed a piece in column "+str(column+1)+" and won." + sendMessage = f"{userName} placed a piece in column {column+1} and won." + logMessage = f"{userName} won" winAmount = int(game["difficulty"])**2+5 - if game["players"][won-1] != "Gwendolyn": - message += " Adding "+str(winAmount)+" GwendoBucks to their account." + if game["players"][won-1] != f"#{self.bot.user.id}": + sendMessage += " Adding "+str(winAmount)+" GwendoBucks to their account" elif 0 not in board[0]: gameWon = True - message = "It's a draw!" + sendMessage = "It's a draw!" + logMessage = "The game ended in a draw" else: gameWon = False - message = self.bot.databaseFuncs.getName(game["players"][player-1])+" placed a piece in column "+str(column+1)+". It's now "+self.bot.databaseFuncs.getName(game["players"][turn])+"'s turn." + otherUserName = self.bot.databaseFuncs.getName(game["players"][turn]) + sendMessage = f"{userName} placed a piece in column {column+1}. It's now {otherUserName}'s turn" + logMessage = "They placed the piece" - gwendoTurn = False + gwendoTurn = (game["players"][turn] == f"#{self.bot.user.id}") - if game["players"][turn] == "Gwendolyn": - self.bot.log("It's Gwendolyn's turn") - gwendoTurn = True + placedPiece = True - self.draw.drawImage(channel) - return message, True, True, gameWon, gwendoTurn + await ctx.channel.send(sendMessage) + self.bot.log(logMessage) + + if placedPiece: + self.draw.drawImage(channel) + + with open(f"resources/games/oldImages/connectFour{channel}", "r") as f: + oldImage = await ctx.channel.fetch_message(int(f.read())) + + if oldImage is not None: + await oldImage.delete() else: - return "There isn't any room in that column", True, True, False, False - else: - return "There's no game in this channel", False, False, False, False + self.bot.log("The old image was already deleted") + + filePath = f"resources/games/connect4Boards/board{channel}.png" + oldImage = await ctx.channel.send(file = discord.File(filePath)) + + if gameWon: + self.endGame(channel) + else: + with open(f"resources/games/oldImages/connectFour{channel}", "w") as f: + f.write(str(oldImage.id)) + if gwendoTurn: + await self.connectFourAI(ctx) + else: + reactions = ["1️⃣","2️⃣","3️⃣","4️⃣","5️⃣","6️⃣","7️⃣"] + for reaction in reactions: + await oldImage.add_reaction(reaction) # Returns a board where a piece has been placed in the column - def placeOnBoard(self,board,player,column): - placementx, placementy = -1, column + def placeOnBoard(self, board, player, column): + placementX, placementY = -1, column for x, line in enumerate(board): if line[column] == 0: - placementx = x + placementX = x - board[placementx][placementy] = player + board[placementX][placementY] = player - if placementx == -1: + if placementX == -1: return None else: return board + def endGame(self, channel): + game = self.bot.database["connect 4 games"].find_one({"_id":channel}) + + winner = game["winner"] + if winner != 0: + if game["players"][winner-1] != f"#{self.bot.user.id}": + difficulty = int(game["difficulty"]) + reward = difficulty**2 + 5 + self.bot.money.addMoney(game["players"][winner-1], reward) + + self.bot.databaseFuncs.deleteGame("connect 4 games", channel) + # Parses command - def parseconnectFour(self, command, channel, user): - commands = command.split() - if command == "" or command == " ": - return "I didn't get that. Use \"/connectFour start [opponent]\" to start a game. To play against the computer, use difficulty 1 through 5 as the [opponent].", False, False, False, False - elif commands[0] == "start": - # Starting a game - if len(commands) == 1: # if the commands is "/connectFour start", the opponent is Gwendolyn - commands.append("3") - return self.connectFourStart(channel,user,commands[1]) # commands[1] is the opponent + async def surrender(self, ctx): + try: + await ctx.defer() + except: + self.bot.log("Defer failed") + channel = str(ctx.channel_id) + game = self.bot.database["connect 4 games"].find_one({"_id":channel}) - # Stopping the game - elif commands[0] == "stop": - game = self.bot.database["connect 4 games"].find_one({"_id":channel}) + if f"#{ctx.author.id}" in game["players"]: + loserIndex = game["players"].index(f"#{ctx.author.id}") + winnerIndex = (loserIndex+1)%2 + winnerID = game["players"][winnerIndex] + winnerName = self.bot.databaseFuncs.getName(winnerID) - if user in game["players"]: - return "Ending game.", False, False, True, False + sendMessage = f"{ctx.author.display_name} surrenders." + sendMessage += f" This means {winnerName} is the winner." + if winnerID != f"#{self.bot.user.id}": + difficulty = int(game["difficulty"]) + reward = difficulty**2 + 5 + sendMessage += f" Adding {reward} to their account" + + await ctx.send(sendMessage) + with open(f"resources/games/oldImages/connectFour{channel}", "r") as f: + oldImage = await ctx.channel.fetch_message(int(f.read())) + + if oldImage is not None: + await oldImage.delete() else: - return "You can't end a game where you're not a player.", False, False, False, False + self.bot.log("The old image was already deleted") - # Placing manually - elif commands[0] == "place": - if len(commands) == 2: - game = self.bot.database["connect 4 games"].find_one({"_id":channel}) - turn = game["turn"] - if user == game["players"][turn]: - piece = turn + 1 - else: - self.bot.log("It wasn't their turn") - return "It's not your turn!", False, False, False, False - column = int(commands[1])-1 - else: - column = int(commands[2])-1 - piece = int(commands[1]) - return self.placePiece(channel, piece, column) + self.endGame(channel) else: - return "I didn't get that. Use \"/connectFour start [opponent]\" to start a game. To play against the computer, use difficulty 1 through 5 as the [opponent].", False, False, False, False + await ctx.send("You can't surrender when you're not a player") # Checks if someone has won the game and returns the winner def isWon(self, board): @@ -181,13 +267,13 @@ class connectFour(): winDirection = "" winCoordinates = [0,0] - for row in range(rowCount): - for place in range(columnCount): + for row in range(ROWCOUNT): + for place in range(COLUMNCOUNT): if won == 0: piecePlayer = board[row][place] if piecePlayer != 0: # Checks horizontal - if place <= columnCount-4: + if place <= COLUMNCOUNT-4: pieces = [board[row][place+1],board[row][place+2],board[row][place+3]] else: pieces = [0] @@ -198,7 +284,7 @@ class connectFour(): winCoordinates = [row,place] # Checks vertical - if row <= rowCount-4: + if row <= ROWCOUNT-4: pieces = [board[row+1][place],board[row+2][place],board[row+3][place]] else: pieces = [0] @@ -209,7 +295,7 @@ class connectFour(): winCoordinates = [row,place] # Checks right diagonal - if row <= rowCount-4 and place <= columnCount-4: + if row <= ROWCOUNT-4 and place <= COLUMNCOUNT-4: pieces = [board[row+1][place+1],board[row+2][place+2],board[row+3][place+3]] else: pieces = [0] @@ -220,7 +306,7 @@ class connectFour(): winCoordinates = [row,place] # Checks left diagonal - if row <= rowCount-4 and place >= 3: + if row <= ROWCOUNT-4 and place >= 3: pieces = [board[row+1][place-1],board[row+2][place-2],board[row+3][place-3]] else: pieces = [0] @@ -234,21 +320,23 @@ class connectFour(): return won, winDirection, winCoordinates # Plays as the AI - async def connectFourAI(self, channel): + async def connectFourAI(self, ctx): + channel = str(ctx.channel.id) + self.bot.log("Figuring out best move") game = self.bot.database["connect 4 games"].find_one({"_id":channel}) board = game["board"] - player = game["players"].index("Gwendolyn")+1 + player = game["players"].index(f"#{self.bot.user.id}")+1 difficulty = game["difficulty"] - scores = [-math.inf,-math.inf,-math.inf,-math.inf,-math.inf,-math.inf,-math.inf] - for column in range(0,columnCount): + scores = [-math.inf for _ in range(COLUMNCOUNT)] + for column in range(COLUMNCOUNT): testBoard = copy.deepcopy(board) testBoard = self.placeOnBoard(testBoard,player,column) if testBoard != None: scores[column] = await self.minimax(testBoard,difficulty,player%2+1,player,-math.inf,math.inf,False) - self.bot.log("Best score for column "+str(column)+" is "+str(scores[column])) + self.bot.log(f"Best score for column {column} is {scores[column]}") possibleScores = scores.copy() @@ -257,9 +345,10 @@ class connectFour(): highest_score = random.choice(possibleScores) - indices = [i for i, x in enumerate(scores) if x == highest_score] - placement = random.choice(indices) - return self.placePiece(channel,player,placement) + bestColumns = [i for i, x in enumerate(scores) if x == highest_score] + placement = random.choice(bestColumns) + + await self.placePiece(ctx, f"#{self.bot.user.id}", placement) # Calculates points for a board def AICalcPoints(self,board,player): @@ -268,28 +357,28 @@ class connectFour(): # Adds points for middle placement # Checks horizontal - for row in range(rowCount): + for row in range(ROWCOUNT): if board[row][3] == player: - score += AIScores["middle"] + score += AISCORES["middle"] rowArray = [int(i) for i in list(board[row])] - for place in range(columnCount-3): + for place in range(COLUMNCOUNT-3): window = rowArray[place:place+4] score += self.evaluateWindow(window,player,otherPlayer) # Checks Vertical - for column in range(columnCount): + for column in range(COLUMNCOUNT): columnArray = [int(i[column]) for i in list(board)] - for place in range(rowCount-3): + for place in range(ROWCOUNT-3): window = columnArray[place:place+4] score += self.evaluateWindow(window,player,otherPlayer) # Checks right diagonal - for row in range(rowCount-3): - for place in range(columnCount-3): + for row in range(ROWCOUNT-3): + for place in range(COLUMNCOUNT-3): window = [board[row][place],board[row+1][place+1],board[row+2][place+2],board[row+3][place+3]] score += self.evaluateWindow(window,player,otherPlayer) - for place in range(3,columnCount): + for place in range(3,COLUMNCOUNT): window = [board[row][place],board[row+1][place-1],board[row+2][place-2],board[row+3][place-3]] score += self.evaluateWindow(window,player,otherPlayer) @@ -299,20 +388,19 @@ class connectFour(): ## Add points if AI wins #if won == player: - # score += AIScores["win"] + # score += AISCORES["win"] return score - def evaluateWindow(self, window,player,otherPlayer): if window.count(player) == 4: - return AIScores["win"] + return AISCORES["win"] elif window.count(player) == 3 and window.count(0) == 1: - return AIScores["three in a row"] + return AISCORES["three in a row"] elif window.count(player) == 2 and window.count(0) == 2: - return AIScores["two in a row"] + return AISCORES["two in a row"] elif window.count(otherPlayer) == 4: - return AIScores["enemy win"] + return AISCORES["enemy win"] else: return 0 @@ -325,24 +413,24 @@ class connectFour(): return points if maximizingPlayer: value = -math.inf - for column in range(0,columnCount): + for column in range(0,COLUMNCOUNT): testBoard = copy.deepcopy(board) testBoard = self.placeOnBoard(testBoard,player,column) if testBoard != None: evaluation = await self.minimax(testBoard,depth-1,player%2+1,originalPlayer,alpha,beta,False) - if evaluation < -9000: evaluation += AIScores["avoid losing"] + if evaluation < -9000: evaluation += AISCORES["avoid losing"] value = max(value,evaluation) alpha = max(alpha,evaluation) if beta <= alpha: break return value else: value = math.inf - for column in range(0,columnCount): + for column in range(0,COLUMNCOUNT): testBoard = copy.deepcopy(board) testBoard = self.placeOnBoard(testBoard,player,column) if testBoard != None: evaluation = await self.minimax(testBoard,depth-1,player%2+1,originalPlayer,alpha,beta,True) - if evaluation < -9000: evaluation += AIScores["avoid losing"] + if evaluation < -9000: evaluation += AISCORES["avoid losing"] value = min(value,evaluation) beta = min(beta,evaluation) if beta <= alpha: break diff --git a/funcs/games/gameLoops.py b/funcs/games/gameLoops.py deleted file mode 100644 index f680558..0000000 --- a/funcs/games/gameLoops.py +++ /dev/null @@ -1,172 +0,0 @@ -import asyncio -import discord - -class GameLoops(): - def __init__(self,bot): - self.bot = bot - - - # Deletes a message - async def deleteMessage(self, imageLocation,channel): - try: - with open("resources/games/oldImages/"+imageLocation, "r") as f: - messages = f.read().splitlines() - - for message in messages: - oldMessage = await channel.fetch_message(int(message)) - self.bot.log("Deleting old message") - await oldMessage.delete() - except: - oldMessage = "" - - return oldMessage - - # Runs connect four - async def connectFour(self, ctx, command, user = None, channelId = None): - if user is None: - user = "#"+str(ctx.author.id) - - if channelId is None: - channelId = str(ctx.channel_id) - - response, showImage, deleteImage, gameDone, gwendoTurn = self.bot.games.connectFour.parseconnectFour(command,channelId, user) - - if hasattr(ctx, "send"): - await ctx.send(response) - else: - await ctx.channel.send(response) - self.bot.log(response, channelId) - if showImage: - if deleteImage: - oldImage = await self.deleteMessage("connectFour"+channelId,ctx.channel) - oldImage = await ctx.channel.send(file = discord.File("resources/games/connect4Boards/board"+channelId+".png")) - if gameDone == False: - if gwendoTurn: - response, showImage, deleteImage, gameDone, gwendoTurn = await self.bot.games.connectFour.connectFourAI(channelId) - await ctx.channel.send(response) - self.bot.log(response,channelId) - if showImage: - if deleteImage: - await oldImage.delete() - oldImage = await ctx.channel.send(file = discord.File("resources/games/connect4Boards/board"+channelId+".png")) - if gameDone == False: - with open("resources/games/oldImages/connectFour"+channelId, "w") as f: - f.write(str(oldImage.id)) - try: - reactions = ["1️⃣","2️⃣","3️⃣","4️⃣","5️⃣","6️⃣","7️⃣"] - for reaction in reactions: - await oldImage.add_reaction(reaction) - - except: - self.bot.log("Image deleted before I could react to all of them") - - else: - with open("resources/games/oldImages/connectFour"+channelId, "w") as f: - f.write(str(oldImage.id)) - try: - reactions = ["1️⃣","2️⃣","3️⃣","4️⃣","5️⃣","6️⃣","7️⃣"] - for reaction in reactions: - await oldImage.add_reaction(reaction) - except: - self.bot.log("Image deleted before I could react to all of them") - - if gameDone: - game = self.bot.database["connect 4 games"].find_one({"_id":channelId}) - - try: - with open("resources/games/oldImages/connectFour"+channelId, "r") as f: - oldImage = await channel.fetch_message(int(f.read())) - - await oldImage.delete() - except: - self.bot.log("The old image was already deleted") - - winner = game["winner"] - difficulty = int(game["difficulty"]) - reward = difficulty**2 + 5 - if winner != 0: - if game["players"][winner-1].lower() != "gwendolyn": - self.bot.money.addMoney(game["players"][winner-1].lower(),reward) - - self.bot.databaseFuncs.deleteGame("connect 4 games",channelId) - - async def runHangman(self,channel,user,command = "start", ctx = None): - try: - response, showImage, deleteImage, remainingLetters = self.bot.games.hangman.parseHangman(str(channel.id),user,command) - except: - self.bot.log("Error parsing command (error code 1701)") - if response != "": - if ctx is None: - await channel.send(response) - else: - await ctx.send(response) - self.bot.log(response,str(channel.id)) - if showImage: - if deleteImage: - await self.deleteMessage("hangman"+str(channel.id),channel) - oldImage = await channel.send(file = discord.File("resources/games/hangmanBoards/hangmanBoard"+str(channel.id)+".png")) - - if len(remainingLetters) > 15: - otherMessage = await channel.send("_ _") - reactionMessages = {oldImage : remainingLetters[:15],otherMessage : remainingLetters[15:]} - else: - otherMessage = "" - reactionMessages = {oldImage : remainingLetters} - - oldMessages = str(oldImage.id) - if otherMessage != "": - oldMessages += "\n"+str(otherMessage.id) - with open("resources/games/oldImages/hangman"+str(channel.id), "w") as f: - f.write(oldMessages) - - try: - for message, letters in reactionMessages.items(): - for letter in letters: - emoji = chr(ord(letter)+127397) - await message.add_reaction(emoji) - except: - self.bot.log("Image deleted before adding all reactions") - - # Runs Hex - async def runHex(self,ctx,command,user): - channelId = ctx.channel_id - try: - response, showImage, deleteImage, gameDone, gwendoTurn = self.bot.games.hex.parseHex(command,str(channelId),user) - except: - self.bot.log("Error parsing command (error code 1510)") - - await ctx.send(response) - - self.bot.log(response,str(channelId)) - if showImage: - if deleteImage: - try: - oldImage = await self.deleteMessage("hex"+str(channelId),ctx.channel) - except: - self.bot.log("Error deleting old image (error code 1501)") - oldImage = await ctx.channel.send(file = discord.File("resources/games/hexBoards/board"+str(channelId)+".png")) - if gwendoTurn and not gameDone: - try: - response, showImage, deleteImage, gameDone, gwendoTurn = self.bot.games.hex.hexAI(str(channelId)) - except: - response, showImage, deleteImage, gameDone, gwendoTurn = "An AI error ocurred",False,False,False,False - self.bot.log("AI error (error code 1520)") - await ctx.channel.send(response) - self.bot.log(response,str(channelId)) - if showImage: - if deleteImage: - await oldImage.delete() - oldImage = await ctx.channel.send(file = discord.File("resources/games/hexBoards/board"+str(channelId)+".png")) - if not gameDone: - with open("resources/games/oldImages/hex"+str(channelId), "w") as f: - f.write(str(oldImage.id)) - - if gameDone: - game = self.bot.database["hex games"].find_one({"_id":str(channelId)}) - - winner = game["winner"] - if winner != 0 and game["players"][0] != game["players"][1]: # player1 != player2 - winnings = game["difficulty"]*10 - self.bot.money.addMoney(game["players"][winner-1].lower(),winnings) - - self.bot.databaseFuncs.deleteGame("hex games",str(channelId)) diff --git a/funcs/games/games.py b/funcs/games/gamesContainer.py similarity index 71% rename from funcs/games/games.py rename to funcs/games/gamesContainer.py index 1afee45..203209a 100644 --- a/funcs/games/games.py +++ b/funcs/games/gamesContainer.py @@ -1,8 +1,7 @@ from .invest import Invest from .trivia import Trivia from .blackjack import Blackjack -from .connectFour import connectFour -from .gameLoops import GameLoops +from .connectFour import ConnectFour from .hangman import Hangman from .hex import HexGame @@ -13,7 +12,6 @@ class Games(): self.invest = Invest(bot) self.trivia = Trivia(bot) self.blackjack = Blackjack(bot) - self.connectFour = connectFour(bot) - self.gameLoops = GameLoops(bot) + self.connectFour = ConnectFour(bot) self.hangman = Hangman(bot) self.hex = HexGame(bot) diff --git a/funcs/games/hangman.py b/funcs/games/hangman.py index dcd50df..0bafdbf 100644 --- a/funcs/games/hangman.py +++ b/funcs/games/hangman.py @@ -1,4 +1,4 @@ -import json, urllib, datetime, string +import json, urllib, datetime, string, discord from .hangmanDraw import DrawHangman @@ -9,8 +9,13 @@ class Hangman(): self.bot = bot self.draw = DrawHangman(bot) - def hangmanStart(self,channel,user): + async def start(self, ctx): + await ctx.defer() + channel = str(ctx.channel_id) + user = f"#{ctx.author.id}" game = self.bot.database["hangman games"].find_one({"_id":channel}) + userName = self.bot.databaseFuncs.getName(user) + startedGame = False if game == None: apiKey = self.bot.credentials.wordnikKey @@ -26,86 +31,135 @@ class Hangman(): remainingLetters = list(string.ascii_uppercase) - try: - self.draw.drawImage(channel) - except: - self.bot.log("Error drawing image (error code 1710)") - return f"{self.bot.databaseFuncs.getName(user)} started game of hangman.", True, False, remainingLetters + self.draw.drawImage(channel) + + logMessage = "Game started" + sendMessage = f"{userName} started game of hangman." + startedGame = True else: - return "There's already a Hangman game going on in the channel", False, False, [] + logMessage = "There was already a game going on" + sendMessage = "There's already a Hangman game going on in the channel" - def hangmanStop(self,channel): - self.bot.database["hangman games"].delete_one({"_id":channel}) + self.bot.log(logMessage) + await ctx.send(sendMessage) - return "Game stopped.", False, False, [] + if startedGame: + filePath = f"resources/games/hangmanBoards/hangmanBoard{channel}.png" + newImage = await ctx.channel.send(file = discord.File(filePath)) - def hangmanGuess(self,channel,user,guess): + blankMessage = await ctx.channel.send("_ _") + reactionMessages = {newImage : remainingLetters[:15], blankMessage : remainingLetters[15:]} + + oldMessages = f"{newImage.id}\n{blankMessage.id}" + + with open(f"resources/games/oldImages/hangman{channel}", "w") as f: + f.write(oldMessages) + + for message, letters in reactionMessages.items(): + for letter in letters: + emoji = chr(ord(letter)+127397) + await message.add_reaction(emoji) + + async def stop(self, ctx): + channel = str(ctx.channel.id) + game = self.bot.database["hangman games"].find_one({"_id": channel}) + + if game is None: + await ctx.send("There's no game going on") + elif f"#{ctx.author.id}" != game["player"]: + await ctx.send("You can't end a game you're not in") + else: + self.bot.database["hangman games"].delete_one({"_id":channel}) + + with open(f"resources/games/oldImages/hangman{channel}", "r") as f: + messages = f.read().splitlines() + + for message in messages: + oldMessage = await ctx.channel.fetch_message(int(message)) + self.bot.log("Deleting old message") + await oldMessage.delete() + + await ctx.send("Game stopped") + + async def guess(self, message, user, guess): + channel = str(message.channel.id) game = self.bot.database["hangman games"].find_one({"_id":channel}) - if game != None: - if user == game["player"]: - if len(guess) == 1 and guess in list(string.ascii_uppercase): - if guess not in game["guessed letters"]: - correctGuess = 0 + gameExists = (game != None) + singleLetter = (len(guess) == 1 and guess.isalpha()) + newGuess = (guess not in game["guessed letters"]) + validGuess = (gameExists and singleLetter and newGuess) - for x, letter in enumerate(game["word"]): - if guess == letter: - correctGuess += 1 - self.bot.database["hangman games"].update_one({"_id":channel},{"$set":{"guessed."+str(x):True}}) + if validGuess: + self.bot.log("Guessed the letter") + correctGuess = 0 - if correctGuess == 0: - self.bot.database["hangman games"].update_one({"_id":channel},{"$inc":{"misses":1}}) + for x, letter in enumerate(game["word"]): + if guess == letter: + correctGuess += 1 + self.bot.database["hangman games"].update_one({"_id":channel},{"$set":{"guessed."+str(x):True}}) - self.bot.database["hangman games"].update_one({"_id":channel},{"$push":{"guessed letters":guess}}) + if correctGuess == 0: + self.bot.database["hangman games"].update_one({"_id":channel},{"$inc":{"misses":1}}) - remainingLetters = list(string.ascii_uppercase) + self.bot.database["hangman games"].update_one({"_id":channel},{"$push":{"guessed letters":guess}}) - game = self.bot.database["hangman games"].find_one({"_id":channel}) + remainingLetters = list(string.ascii_uppercase) - for letter in game["guessed letters"]: - remainingLetters.remove(letter) + game = self.bot.database["hangman games"].find_one({"_id":channel}) - if correctGuess == 1: - message = f"Guessed {guess}. There was 1 {guess} in the word." - else: - message = f"Guessed {guess}. There were {correctGuess} {guess}s in the word." + for letter in game["guessed letters"]: + remainingLetters.remove(letter) - try: - self.draw.drawImage(channel) - except: - self.bot.log("Error drawing image (error code 1710)") - - if game["misses"] == 6: - self.hangmanStop(channel) - return message+" You've guessed wrong six times and have lost the game.", True, True, [] - elif all(i == True for i in game["guessed"]): - self.hangmanStop(channel) - self.bot.money.addMoney(user,15) - return message+" You've guessed the word! Congratulations! Adding 15 GwendoBucks to your account", True, True, [] - else: - return message, True, True, remainingLetters - else: - return f"You've already guessed {guess}", False, False, [] - else: - return "", False, False, [] + if correctGuess == 1: + sendMessage = f"Guessed {guess}. There was 1 {guess} in the word." else: - return "", False, False, [] - else: - return "There's no Hangman game going on in this channel", False, False, [] + sendMessage = f"Guessed {guess}. There were {correctGuess} {guess}s in the word." + + self.draw.drawImage(channel) + + if game["misses"] == 6: + self.bot.database["hangman games"].delete_one({"_id":channel}) + sendMessage += " You've guessed wrong six times and have lost the game." + remainingLetters = [] + elif all(i == True for i in game["guessed"]): + self.bot.database["hangman games"].delete_one({"_id":channel}) + self.bot.money.addMoney(user,15) + sendMessage += " You've guessed the word! Congratulations! Adding 15 GwendoBucks to your account" + remainingLetters = [] + + await message.channel.send(sendMessage) + + with open(f"resources/games/oldImages/hangman{channel}", "r") as f: + oldMessageIDs = f.read().splitlines() + + for oldID in oldMessageIDs: + oldMessage = await message.channel.fetch_message(int(oldID)) + self.bot.log("Deleting old message") + await oldMessage.delete() + + filePath = f"resources/games/hangmanBoards/hangmanBoard{channel}.png" + newImage = await message.channel.send(file = discord.File(filePath)) + + if len(remainingLetters) > 0: + if len(remainingLetters) > 15: + blankMessage = await message.channel.send("_ _") + reactionMessages = {newImage : remainingLetters[:15], blankMessage : remainingLetters[15:]} + else: + blankMessage = "" + reactionMessages = {newImage : remainingLetters} + + if blankMessage != "": + oldMessages = f"{newImage.id}\n{blankMessage.id}" + else: + oldMessages = str(newImage.id) + + with open(f"resources/games/oldImages/hangman{channel}", "w") as f: + f.write(oldMessages) + + for message, letters in reactionMessages.items(): + for letter in letters: + emoji = chr(ord(letter)+127397) + await message.add_reaction(emoji) + - def parseHangman(self,channel,user,command): - if command == "start": - try: - return self.hangmanStart(channel,user) - except: - self.bot.log("Error starting game (error code 1730)") - elif command == "stop": - return self.hangmanStop(channel) - elif command.startswith("guess "): - guess = command[6:].upper() - try: - return self.hangmanGuess(channel,user,guess) - except: - self.bot.log("Error in guessing (Error Code 1720)") - else: - return "I didn't understand that", False, False, [] diff --git a/funcs/games/hex.py b/funcs/games/hex.py index 3af263f..6711670 100644 --- a/funcs/games/hex.py +++ b/funcs/games/hex.py @@ -1,6 +1,7 @@ import random import copy import math +import discord from .hexDraw import DrawHex @@ -13,102 +14,146 @@ for position in ALL_POSITIONS: HEX_DIRECTIONS = [(0,1),(-1,1),(-1,0),(0,-1),(1,-1),(1,0)] class HexGame(): - def __init__(self,bot): + def __init__(self, bot): self.bot = bot self.draw = DrawHex(bot) - # Parses command - def parseHex(self, command, channel, user): - commands = command.lower().split() + async def surrender(self, ctx): + channel = str(ctx.channel_id) game = self.bot.database["hex games"].find_one({"_id":channel}) - if command == "" or command == " ": - return "I didn't get that. Use \"/hex start [opponent]\" to start a game.", False, False, False, False - - elif commands[0] == "start": - # Starting a game - if len(commands) == 1: # if the commands is "/hex start", the opponent is Gwendolyn at difficulty 2 - commands.append("2") - self.bot.log("Starting a hex game with hexStart(). "+str(user)+" challenged "+commands[1]) - return self.hexStart(channel,user,commands[1]) # commands[1] is the opponent - - # If using a command with no game, return error - elif game == None: - return "There's no game in this channel", False, False, False, False - - # Stopping the game - elif commands[0] == "stop": - if user in game["players"]: - return "Ending game.", False, False, True, False - else: - return "You can't end a game where you're not a player.", False, False, False, False - - # Placing a piece - elif commands[0] == "place": - try: - return self.placeHex(channel,commands[1], user) - except: - return "I didn't get that. To place a piece use \"/hex place [position]\". A valid position is e.g. \"E2\".", False, False, False, False - - # Undo - elif commands[0] == "undo": - return self.undoHex(channel, user) - - # Surrender - elif commands[0] == "surrender": - players = game["players"] - if user in players: - opponent = (players.index(user) + 1) % 2 - self.bot.database["hex games"].update_one({"_id":channel},{"$set":{"winner":opponent + 1}}) - return "{} surrendered. That means {} won! Adding 30 Gwendobucks to their account.".format(self.bot.databaseFuncs.getName(user),self.bot.databaseFuncs.getName(players[opponent])), False, False, True, False - else: - return "You can't surrender when you're not a player.", False, False, False, False - - # Swap - elif commands[0] == "swap": - if len(game["gameHistory"]) == 1: # Only after the first move - self.bot.database["hex games"].update_one({"_id":channel}, - {"$set":{"players":game["players"][::-1]}}) # Swaps their player-number - - # Swaps the color of the hexes on the board drawing: - self.draw.drawSwap(channel) - player2 = game["players"][1] - gwendoTurn = (player2 == "Gwendolyn") - return "The color of both players were swapped. It is now {}'s turn".format(player2), True, True, False, gwendoTurn - else: - return "You can only swap as the second player after the very first move.", False, False, False, False + user = f"#{ctx.author.id}" + players = game["players"] + if user not in players: + await ctx.send("You can't surrender when you're not a player.") else: - return "I didn't get that. Use \"/hex start [opponent]\" to start a game, \"/hex place [position]\" to place a piece, \"/hex undo\" to undo your last move or \"/hex stop\" to stop a current game.", False, False, False, False + opponent = (players.index(user) + 1) % 2 + opponentName = self.bot.databaseFuncs.getName(players[opponent]) + self.bot.database["hex games"].update_one({"_id":channel},{"$set":{"winner":opponent + 1}}) + await ctx.send(f"{ctx.author.display_name} surrendered. That means {opponentName} won! Adding 30 Gwendobucks to their account") + + with open(f"resources/games/oldImages/hex{channel}", "r") as f: + oldImage = await ctx.channel.fetch_message(int(f.read())) + + if oldImage is not None: + await oldImage.delete() + else: + self.bot.log("The old image was already deleted") + + self.bot.log("Sending the image") + filePath = f"resources/games/hexBoards/board{channel}.png" + oldImage = await ctx.channel.send(file = discord.File(filePath)) + + with open(f"resources/games/oldImages/hex{channel}", "w") as f: + f.write(str(oldImage.id)) + + # Swap + async def swap(self, ctx): + channel = str(ctx.channel_id) + game = self.bot.database["hex games"].find_one({"_id":channel}) + user = f"#{ctx.author.id}" + + if game is None: + await ctx.send("You can't swap nothing") + elif user not in game["players"]: + await ctx.send("You're not in the game") + elif len(game["gameHistory"]) != 1: # Only after the first move + await ctx.send("You can only swap as the second player after the very first move.") + elif user != game["players"][game["turn"]-1]: + await ctx.send("You can only swap after your opponent has placed their piece") + else: + self.bot.database["hex games"].update_one({"_id":channel}, + {"$set":{"players":game["players"][::-1]}}) # Swaps their player-number + + # Swaps the color of the hexes on the board drawing: + self.draw.drawSwap(channel) + + opponent = game["players"][::-1][game["turn"]-1] + gwendoTurn = (opponent == f"#{self.bot.user.id}") + opponentName = self.bot.databaseFuncs.getName(opponent) + await ctx.send(f"The color of the players were swapped. It is now {opponentName}'s turn") + + with open(f"resources/games/oldImages/hex{channel}", "r") as f: + oldImage = await ctx.channel.fetch_message(int(f.read())) + + if oldImage is not None: + await oldImage.delete() + else: + self.bot.log("The old image was already deleted") + + self.bot.log("Sending the image") + filePath = f"resources/games/hexBoards/board{channel}.png" + oldImage = await ctx.channel.send(file = discord.File(filePath)) + + with open(f"resources/games/oldImages/hex{channel}", "w") as f: + f.write(str(oldImage.id)) + + if gwendoTurn: + await self.hexAI(ctx) # Starts the game - def hexStart(self, channel, user, opponent): + async def start(self, ctx, opponent): + try: + await ctx.defer() + except: + self.bot.log("Defer failed") + user = f"#{ctx.author.id}" + channel = str(ctx.channel_id) game = self.bot.database["hex games"].find_one({"_id":channel}) - if game == None: - if opponent in ["1","2","3","4","5"]: - difficulty = int(opponent) - diffText = " with difficulty "+opponent - opponent = "Gwendolyn" - elif opponent.lower() == "gwendolyn": - difficulty = 2 - diffText = " with difficulty 2" - opponent = "Gwendolyn" - else: - try: - int(opponent) - return "That difficulty doesn't exist", False, False, False, False - except: - opponent = self.bot.databaseFuncs.getID(opponent) - if opponent == None: - return "I can't find that user", False, False, False, False - else: - # Opponent is another player - difficulty = 3 - diffText = "" + startedGame = False + canStart = True + if game != None: + sendMessage = "There's already a hex game going on in this channel" + logMessage = "There was already a game going on" + canStart = False + else: + if type(opponent) == int: + # Opponent is Gwendolyn + if opponent in range(1, 6): + opponentName = "Gwendolyn" + difficulty = int(opponent) + diffText = f" with difficulty {difficulty}" + opponent = f"#{self.bot.user.id}" + else: + sendMessage = "Difficulty doesn't exist" + logMessage = "They tried to play against a difficulty that doesn't exist" + canStart = False + + elif type(opponent) == discord.member.Member: + if opponent.bot: + # User has challenged a bot + if opponent == self.bot.user: + # It was Gwendolyn + opponentName = "Gwendolyn" + difficulty = 2 + diffText = f" with difficulty {difficulty}" + opponent = f"#{self.bot.user.id}" + else: + sendMessage = "You can't challenge a bot!" + logMessage = "They tried to challenge a bot" + canStart = False + else: + # Opponent is another player + if ctx.author != opponent: + opponentName = opponent.display_name + opponent = f"#{opponent.id}" + difficulty = 5 + diffText = "" + else: + sendMessage = "You can't play against yourself" + logMessage = "They tried to play against themself" + canStart = False + else: + canStart = False + logMessage = f"Opponent was neither int or member. It was {type(opponent)}" + sendMessage = "Something went wrong" + + if canStart: # board is 11x11 - board = [ [ 0 for i in range(BOARDWIDTH) ] for j in range(BOARDWIDTH) ] - players = [user,opponent] + board = [[0 for i in range(BOARDWIDTH)] for j in range(BOARDWIDTH)] + players = [user, opponent] random.shuffle(players) # random starting player gameHistory = [] @@ -120,137 +165,196 @@ class HexGame(): # draw the board self.draw.drawBoard(channel) - gwendoTurn = True if players[0] == "Gwendolyn" else False - showImage = True - return "Started Hex game against "+self.bot.databaseFuncs.getName(opponent)+ diffText+". It's "+self.bot.databaseFuncs.getName(players[0])+"'s turn", showImage, False, False, gwendoTurn - else: - return "There's already a hex game going on in this channel", False, False, False, False + gwendoTurn = (players[0] == f"#{self.bot.user.id}") + startedGame = True + + turnName = self.bot.databaseFuncs.getName(players[0]) + sendMessage = f"Started Hex game against {opponentName}{diffText}. It's {turnName}'s turn" + logMessage = "Game started" + + await ctx.send(sendMessage) + self.bot.log(logMessage) + + if startedGame: + filePath = f"resources/games/hexBoards/board{ctx.channel_id}.png" + newImage = await ctx.channel.send(file = discord.File(filePath)) + + with open(f"resources/games/oldImages/hex{ctx.channel_id}", "w") as f: + f.write(str(newImage.id)) + + if gwendoTurn: + await self.hexAI(ctx) # Places a piece at the given location and checks things afterwards - def placeHex(self, channel : str,position : str, user): + async def placeHex(self, ctx, position : str, user): + channel = str(ctx.channel_id) game = self.bot.database["hex games"].find_one({"_id":channel}) + placedPiece = False - if game != None: - players = game["players"] - if user in players: - turn = game["turn"] - if players[0] == players[1]: - player = turn - else: - player = players.index(user)+1 - - if player == turn: - board = game["board"] - - self.bot.log("Placing a piece on the board with placeHex()") - # Places on board - board = self.placeOnHexBoard(board,player,position) - - if isinstance(board, list): - # If the move is valid: - self.bot.database["hex games"].update_one({"_id":channel},{"$set":{"board":board}}) - turn = 1 if turn == 2 else 2 - self.bot.database["hex games"].update_one({"_id":channel},{"$set":{"turn":turn}}) - - # Checking for a win - self.bot.log("Checking for win") - winner = self.evaluateBoard(game["board"])[1] - - if winner == 0: # Continue with the game. - gameWon = False - message = self.bot.databaseFuncs.getName(game["players"][player-1])+" placed at "+position.upper()+". It's now "+self.bot.databaseFuncs.getName(game["players"][turn-1])+"'s turn."# The score is "+str(score) - - else: # Congratulations! - gameWon = True - self.bot.database["hex games"].update_one({"_id":channel},{"$set":{"winner":winner}}) - message = self.bot.databaseFuncs.getName(game["players"][player-1])+" placed at "+position.upper()+" and won!" - if game["players"][winner-1] != "Gwendolyn": - winAmount = game["difficulty"]*10 - message += " Adding "+str(winAmount)+" GwendoBucks to their account." - - self.bot.database["hex games"].update_one({"_id":channel}, - {"$push":{"gameHistory":(int(position[1])-1, ord(position[0])-97)}}) - - # Is it now Gwendolyn's turn? - gwendoTurn = False - if game["players"][turn-1] == "Gwendolyn": - self.bot.log("It's Gwendolyn's turn") - gwendoTurn = True - - # Update the board - self.draw.drawHexPlacement(channel,player,position) - - return message, True, True, gameWon, gwendoTurn - else: - # Invalid move. "board" is the error message - message = board - return message, False, False, False, False - else: - # Move out of turn - message = "It isn't your turn, it is "+self.bot.databaseFuncs.getName(game["players"][turn-1])+"'s turn." - return message, False, False, False, False - else: - message = "You can't place when you're not in the game. The game's players are: "+self.bot.databaseFuncs.getName(game["players"][0])+" and "+self.bot.databaseFuncs.getName(game["players"][1])+"." - return message, False, False, False, False + if game == None: + sendMessage = "There's no game in this channel" + self.bot.log("There was no game going on") + elif not (position[0].isalpha() and position[1:].isnumeric() and len(position) in [2, 3]): + sendMessage = "The position must be a letter followed by a number." + self.bot.log(f"The position was not valid, {position}") else: - return "There's no game in this channel", False, False, False, False + players = game["players"] + if user not in players: + sendMessage = f"You can't place when you're not in the game. The game's players are: {self.bot.databaseFuncs.getName(game['players'][0])} and {self.bot.databaseFuncs.getName(game['players'][1])}." + self.bot.log("They aren't in the game") + elif players[game["turn"]-1] != user: + sendMessage = "It's not your turn" + self.bot.log("It wasn't their turn") + else: + player = game["turn"] + turn = game["turn"] + board = game["board"] + + self.bot.log("Placing a piece on the board with placeHex()") + # Places on board + board = self.placeOnHexBoard(board,player,position) + + if board is None: + self.bot.log("It was an invalid position") + sendMessage = ("That's an invalid position. You must place your piece on an empty field.") + else: + # If the move is valid: + self.bot.database["hex games"].update_one({"_id":channel},{"$set":{"board":board}}) + turn = (turn % 2) + 1 + self.bot.database["hex games"].update_one({"_id":channel},{"$set":{"turn":turn}}) + + # Checking for a win + self.bot.log("Checking for win") + winner = self.evaluateBoard(game["board"])[1] + + if winner == 0: # Continue with the game. + gameWon = False + sendMessage = self.bot.databaseFuncs.getName(game["players"][player-1])+" placed at "+position.upper()+". It's now "+self.bot.databaseFuncs.getName(game["players"][turn-1])+"'s turn."# The score is "+str(score) + + else: # Congratulations! + gameWon = True + self.bot.database["hex games"].update_one({"_id":channel},{"$set":{"winner":winner}}) + sendMessage = self.bot.databaseFuncs.getName(game["players"][player-1])+" placed at "+position.upper()+" and won!" + if game["players"][winner-1] != f"#{self.bot.user.id}": + winAmount = game["difficulty"]*10 + sendMessage += " Adding "+str(winAmount)+" GwendoBucks to their account." + + self.bot.database["hex games"].update_one({"_id":channel}, + {"$push":{"gameHistory":(int(position[1])-1, ord(position[0])-97)}}) + + # Is it now Gwendolyn's turn? + gwendoTurn = False + if game["players"][turn-1] == f"#{self.bot.user.id}": + self.bot.log("It's Gwendolyn's turn") + gwendoTurn = True + + placedPiece = True + + if user == f"#{self.bot.user.id}": + await ctx.channel.send(sendMessage) + else: + await ctx.send(sendMessage) + + if placedPiece: + # Update the board + self.draw.drawHexPlacement(channel,player, position) + + with open(f"resources/games/oldImages/hex{channel}", "r") as f: + oldImage = await ctx.channel.fetch_message(int(f.read())) + + if oldImage is not None: + await oldImage.delete() + else: + self.bot.log("The old image was already deleted") + + self.bot.log("Sending the image") + filePath = f"resources/games/hexBoards/board{channel}.png" + oldImage = await ctx.channel.send(file = discord.File(filePath)) + + if gameWon: + self.bot.log("Dealing with the winning player") + game = self.bot.database["hex games"].find_one({"_id":channel}) + + winner = game["winner"] + if game["players"][winner-1] != f"#{self.bot.user.id}": + winnings = game["difficulty"]*10 + self.bot.money.addMoney(game["players"][winner-1].lower(),winnings) + else: + with open(f"resources/games/oldImages/hex{channel}", "w") as f: + f.write(str(oldImage.id)) + + if gwendoTurn: + await self.hexAI(ctx) # Returns a board where the placement has ocurred def placeOnHexBoard(self, board,player,position): # Translates the position position = position.lower() # Error handling - try: - column = ord(position[0]) - 97 # ord() translates from letter to number - row = int(position[1:]) - 1 - if column not in range(BOARDWIDTH) or row not in range(BOARDWIDTH): - self.bot.log("Position out of bounds (error code 1533)") - return "Error. That position is out of bounds." - except: - self.bot.log("Invalid position (error code 1531)") - return "Error. The position should be a letter followed by a number, e.g. \"e2\"." + column = ord(position[0]) - 97 # ord() translates from letter to number + row = int(position[1:]) - 1 + if column not in range(BOARDWIDTH) or row not in range(BOARDWIDTH): + self.bot.log("Position out of bounds") + return None # Place at the position if board[row][column] == 0: board[row][column] = player return board else: self.bot.log("Cannot place on existing piece (error code 1532)") - return "Error. You must place on an empty space." + return None # After your move, you have the option to undo get your turn back #TimeTravel - def undoHex(self, channel, user): + async def undo(self, ctx): + channel = str(ctx.channel_id) + user = f"#{ctx.author.id}" + undid = False + game = self.bot.database["hex games"].find_one({"_id":channel}) - if user in game["players"]: - if len(game["gameHistory"]): - turn = game["turn"] - # You can only undo after your turn, which is the opponent's turn. - if user == game["players"][(turn % 2)]: # If it's not your turn - self.bot.log("Undoing {}'s last move".format(self.bot.databaseFuncs.getName(user))) - - lastMove = game["gameHistory"].pop() - game["board"][lastMove[0]][lastMove[1]] = 0 - self.bot.database["hex games"].update_one({"_id":channel}, - {"$set":{"board":game["board"]}}) - self.bot.database["hex games"].update_one({"_id":channel}, - {"$set":{"turn":turn%2 + 1}}) - - # Update the board - self.draw.drawHexPlacement(channel,0,"abcdefghijk"[lastMove[1]]+str(lastMove[0]+1)) # The zero makes the hex disappear - return "You undid your last move at {}".format(lastMove), True, True, False, False - else: - # Sassy - return "Nice try. You can't undo your opponent's move. ", False, False, False, False - else: - # Sassy - return "Really? You undo right at the start of the game?", False, False, False, False - + if user not in game["players"]: + sendMessage = "You're not a player in the game" + elif len(game["gameHistory"]) == 0: + sendMessage = "You can't undo nothing" + elif user != game["players"][(game["turn"] % 2)]: # If it's not your turn + sendMessage = "It's not your turn" else: - return "You're not a player in the game", False, False, False, False + turn = game["turn"] + self.bot.log("Undoing {}'s last move".format(self.bot.databaseFuncs.getName(user))) + + lastMove = game["gameHistory"].pop() + game["board"][lastMove[0]][lastMove[1]] = 0 + self.bot.database["hex games"].update_one({"_id":channel}, + {"$set":{"board":game["board"]}}) + self.bot.database["hex games"].update_one({"_id":channel}, + {"$set":{"turn":turn%2 + 1}}) + + # Update the board + self.draw.drawHexPlacement(channel,0,"abcdefghijk"[lastMove[1]]+str(lastMove[0]+1)) # The zero makes the hex disappear + sendMessage = f"You undid your last move at {lastMove}" + undid = True + + await ctx.send(sendMessage) + if undid: + with open(f"resources/games/oldImages/hex{channel}", "r") as f: + oldImage = await ctx.channel.fetch_message(int(f.read())) + + if oldImage is not None: + await oldImage.delete() + else: + self.bot.log("The old image was already deleted") + + self.bot.log("Sending the image") + filePath = f"resources/games/hexBoards/board{channel}.png" + oldImage = await ctx.channel.send(file = discord.File(filePath)) + + with open(f"resources/games/oldImages/hex{channel}", "w") as f: + f.write(str(oldImage.id)) # Plays as the AI - def hexAI(self, channel): + async def hexAI(self, ctx): + channel = str(ctx.channel_id) self.bot.log("Figuring out best move") game = self.bot.database["hex games"].find_one({"_id":channel}) board = game["board"] @@ -269,32 +373,10 @@ class HexGame(): moves.remove(move) chosenMove = random.choice(moves) - """ - GwenColor = data[channel]["players"].index("Gwendolyn") + 1 # either 1 or 2 - red or blue - if len(data[channel]["gameHistory"]) == 0: - return placeHex(channel,"F6", "Gwendolyn") # If starting, start in the middle - board = data[channel]["board"] - difficulty = data[channel]["difficulty"] - possiblePlaces = [i for i,v in enumerate(sum(board,[])) if v == 0] - judgements = [float('nan')]*len(possiblePlaces) # All possible moves are yet to be judged - - current_score = evaluateBoard(board)[0] - for i in possiblePlaces: - testBoard = copy.deepcopy(board) - testBoard[i // BOARDWIDTH][i % BOARDWIDTH] = GwenColor - if evaluateBoard(testBoard)[0] != current_score: # only think about a move if it improves the score (it's impossible to get worse) - # Testing a move and evaluating it - judgements[i] = minimaxHex(testBoard,difficulty,-math.inf,math.inf,GwenColor==2) - self.bot.log("Best score for place {} is {}".format((i // BOARDWIDTH,i % BOARDWIDTH),judgements[i])) - - bestScore = max(judgements) if (GwenColor == 1) else min(judgements) # this line has an error - indices = [i for i, x in enumerate(judgements) if x == bestScore] # which moves got that score? - i = random.choice(indices) - chosenMove = (i // BOARDWIDTH , i % BOARDWIDTH) - """ placement = "abcdefghijk"[chosenMove[1]]+str(chosenMove[0]+1) - self.bot.log("ChosenMove is {} at {}".format(chosenMove,placement)) - return self.placeHex(channel,placement, "Gwendolyn") + self.bot.log(f"ChosenMove is {chosenMove} at {placement}") + + await self.placeHex(ctx, placement, f"#{self.bot.user.id}") def evaluateBoard(self, board): diff --git a/funcs/games/hexDraw.py b/funcs/games/hexDraw.py index 44a064c..8dcd7d0 100644 --- a/funcs/games/hexDraw.py +++ b/funcs/games/hexDraw.py @@ -122,7 +122,7 @@ class DrawHex(): def drawHexPlacement(self, channel,player,position): FILEPATH = "resources/games/hexBoards/board"+channel+".png" - self.bot.log("Drawing a newly placed hex. Filepath: "+FILEPATH) + self.bot.log(f"Drawing a newly placed hex. Filename: board{channel}.png") # Translates position # We don't need to error-check, because the position is already checked in placeOnHexBoard() diff --git a/funcs/games/invest.py b/funcs/games/invest.py index 04a80dc..c34310a 100644 --- a/funcs/games/invest.py +++ b/funcs/games/invest.py @@ -1,3 +1,5 @@ +import discord + class Invest(): def __init__(self, bot): self.bot = bot @@ -103,35 +105,48 @@ class Invest(): else: return "no" - def parseInvest(self, content: str, user : str): - if content.startswith("check"): - commands = content.split(" ") + async def parseInvest(self, ctx, parameters): + try: + await ctx.defer() + except: + self.bot.log("Defer failed") + user = f"#{ctx.author.id}" + + if parameters.startswith("check"): + commands = parameters.split(" ") if len(commands) == 1: - return self.getPortfolio(user) + response = self.getPortfolio(user) else: price = self.getPrice(commands[1]) if price == 0: - return f"{commands[1].upper()} is not traded on the american market." + response = f"{commands[1].upper()} is not traded on the american market." else: price = f"{price:,}".replace(",",".") - return f"The current {commands[1].upper()} stock is valued at **{price}** GwendoBucks" + response = f"The current {commands[1].upper()} stock is valued at **{price}** GwendoBucks" - elif content.startswith("buy"): - commands = content.split(" ") + elif parameters.startswith("buy"): + commands = parameters.split(" ") if len(commands) == 3: - #try: - return self.buyStock(user,commands[1],int(commands[2])) - #except: - # return "The command must be given as \"/invest buy [stock] [amount of GwendoBucks to purchase with]\"" + response = self.buyStock(user,commands[1],int(commands[2])) else: - return "You must give both a stock name and an amount of gwendobucks you wish to spend." + response = "You must give both a stock name and an amount of gwendobucks you wish to spend." - elif content.startswith("sell"): - commands = content.split(" ") + elif parameters.startswith("sell"): + commands = parameters.split(" ") if len(commands) == 3: try: - return self.sellStock(user,commands[1],int(commands[2])) + response = self.sellStock(user,commands[1],int(commands[2])) except: - return "The command must be given as \"/invest sell [stock] [amount of GwendoBucks to sell stocks for]\"" + response = "The command must be given as \"/invest sell [stock] [amount of GwendoBucks to sell stocks for]\"" else: - return "You must give both a stock name and an amount of GwendoBucks you wish to sell stocks for." + response = "You must give both a stock name and an amount of GwendoBucks you wish to sell stocks for." + + else: + response = "Incorrect parameters" + + if response.startswith("**"): + responses = response.split("\n") + em = discord.Embed(title=responses[0],description="\n".join(responses[1:]),colour=0x00FF00) + await ctx.send(embed=em) + else: + await ctx.send(response) diff --git a/funcs/games/money.py b/funcs/games/money.py index f4d28e3..ef9458f 100644 --- a/funcs/games/money.py +++ b/funcs/games/money.py @@ -14,6 +14,15 @@ class Money(): return userData["money"] else: return 0 + async def sendBalance(self, ctx): + await ctx.defer() + response = self.checkBalance("#"+str(ctx.author.id)) + if response == 1: + new_message = ctx.author.display_name + " has " + str(response) + " GwendoBuck" + else: + new_message = ctx.author.display_name + " has " + str(response) + " GwendoBucks" + await ctx.send(new_message) + # Adds money to the account of a user def addMoney(self,user,amount): self.bot.log("adding "+str(amount)+" to "+user+"'s account") @@ -26,26 +35,39 @@ class Money(): self.database["users"].insert_one({"_id":user,"user name":self.bot.databaseFuncs.getName(user),"money":amount}) # Transfers money from one user to another - def giveMoney(self,user,targetUser,amount): - userData = self.database["users"].find_one({"_id":user}) - targetUser = self.bot.databaseFuncs.getID(targetUser) + async def giveMoney(self, ctx, user, amount): + try: + await ctx.defer() + except: + self.bot.log("Defer failed") + username = user.display_name + if self.bot.databaseFuncs.getID(username) == None: + async for member in ctx.guild.fetch_members(limit=None): + if member.display_name.lower() == username.lower(): + username = member.display_name + userID = "#" + str(member.id) + newUser = {"_id":userID,"user name":username,"money":0} + self.bot.database["users"].insert_one(newUser) + + userData = self.database["users"].find_one({"_id":f"#{ctx.author.id}"}) + targetUser = self.bot.databaseFuncs.getID(username) if amount > 0: if targetUser != None: if userData != None: if userData["money"] >= amount: - self.addMoney(user,-1 * amount) + self.addMoney(f"#{ctx.author.id}",-1 * amount) self.addMoney(targetUser,amount) - return "Transferred "+str(amount)+" GwendoBucks to "+self.bot.databaseFuncs.getName(targetUser) + await ctx.send(f"Transferred {amount} GwendoBucks to {username}") else: - self.bot.log("They didn't have enough GwendoBucks (error code 1223b)") - return "You don't have that many GwendoBucks (error code 1223b)" + self.bot.log("They didn't have enough GwendoBucks") + await ctx.send("You don't have that many GwendoBucks") else: - self.bot.log("They didn't have enough GwendoBucks (error code 1223a)") - return "You don't have that many GwendoBucks (error code 1223a)" + self.bot.log("They didn't have enough GwendoBucks") + await ctx.send("You don't have that many GwendoBuck") else: self.bot.log("They weren't in the system") - return "The target doesn't exist" + await ctx.send("The target doesn't exist") else: self.bot.log("They tried to steal") - return "Yeah, no. You can't do that" + await ctx.send("Yeah, no. You can't do that") diff --git a/funcs/games/trivia.py b/funcs/games/trivia.py index 3b75cdf..f8d17c0 100644 --- a/funcs/games/trivia.py +++ b/funcs/games/trivia.py @@ -1,6 +1,7 @@ import json import urllib import random +import asyncio class Trivia(): def __init__(self, bot): @@ -83,3 +84,38 @@ class Trivia(): self.bot.log("Couldn't find the question (error code 1102)") return None + + async def triviaParse(self, ctx, answer): + try: + await ctx.defer() + except: + self.bot.log("defer failed") + if answer == "": + question, options, correctAnswer = self.triviaStart(str(ctx.channel_id)) + if options != "": + results = "**"+question+"**\n" + for x, option in enumerate(options): + results += chr(x+97) + ") "+option+"\n" + + await ctx.send(results) + + await asyncio.sleep(60) + + self.triviaCountPoints(str(ctx.channel_id)) + + self.bot.databaseFuncs.deleteGame("trivia questions",str(ctx.channel_id)) + + self.bot.log("Time's up for the trivia question",str(ctx.channel_id)) + await ctx.send("Time's up The answer was \"*"+chr(correctAnswer)+") "+options[correctAnswer-97]+"*\". Anyone who answered that has gotten 1 GwendoBuck") + else: + await ctx.send(question, hidden=True) + + elif answer in ["a","b","c","d"]: + response = self.triviaAnswer("#"+str(ctx.author.id), str(ctx.channel_id), answer) + if response.startswith("Locked in "): + await ctx.send(f"{ctx.author.display_name} answered **{answer}**") + else: + await ctx.send(response) + else: + self.bot.log("I didn't understand that (error code 1101)",str(ctx.channel_id)) + await ctx.send("I didn't understand that (error code 1101)") diff --git a/funcs/lookup/lookupFuncs.py b/funcs/lookup/lookupFuncs.py index 3cfe9aa..7d0654c 100644 --- a/funcs/lookup/lookupFuncs.py +++ b/funcs/lookup/lookupFuncs.py @@ -1,5 +1,6 @@ import math import json +import discord from utils import cap @@ -18,27 +19,28 @@ class LookupFuncs(): return(str(mods)) # Looks up a monster - def monsterFunc(self, command): - self.bot.log("Looking up "+command) + async def monsterFunc(self, ctx, query): + query = cap(query) + self.bot.log("Looking up "+query) # 1-letter monsters don't exist - if len(command) < 2: - self.bot.log("Monster name too short (error code 601)") - return("I don't know that monster... (error code 601)","","","","","") + if len(query) < 2: + self.bot.log("Monster name too short") + await ctx.send("I don't know that monster...") else: # Opens "monsters.json" data = json.load(open('resources/lookup/monsters.json', encoding = "utf8")) for monster in data: - if "name" in monster and str(command) == monster["name"]: + if "name" in monster and str(query) == monster["name"]: self.bot.log("Found it!") # Looks at the information about the monster and returns that information # in separate variables, allowing Gwendolyn to know where to separate # the messages if monster["subtype"] != "": - typs = (monster["type"]+" ("+monster["subtype"]+")") + types = (monster["type"]+" ("+monster["subtype"]+")") else: - typs = monster["type"] + types = monster["type"] con_mod = math.floor((monster["constitution"]-10)/2) hit_dice = monster["hit_dice"] @@ -80,10 +82,10 @@ class LookupFuncs(): if c_immunities != "": c_immunities = "\n**Condition Immunities** "+c_immunities - spec_ab = "" + specialAbilities = "" if "special_abilities" in monster: for ability in monster["special_abilities"]: - spec_ab += "\n\n***"+ability["name"]+".*** "+ability["desc"] + specialAbilities += "\n\n***"+ability["name"]+".*** "+ability["desc"] act = "" if "actions" in monster: @@ -95,10 +97,10 @@ class LookupFuncs(): for reaction in monster["reactions"]: react += "\n\n***"+reaction["name"]+".*** "+reaction["desc"] - leg_act = "" + legendaryActions = "" if "legendary_actions" in monster: for action in monster["legendary_actions"]: - leg_act += "\n\n***"+action["name"]+".*** "+action["desc"] + legendaryActions += "\n\n***"+action["name"]+".*** "+action["desc"] if con_mod < 0: hit_dice += (" - "+str(con_mod * int(monster["hit_dice"].replace("d"," ").split()[0])*(-1))) @@ -107,33 +109,56 @@ class LookupFuncs(): new_part = "\n--------------------" - monster_type = monster["size"]+" "+typs+", "+monster["alignment"]+"*" + monster_type = monster["size"]+" "+types+", "+monster["alignment"]+"*" basic_info = "\n**Armor Class** "+str(monster["armor_class"])+"\n**Hit Points** "+str(monster["hit_points"])+" ("+hit_dice+")\n**Speed **"+monster["speed"]+new_part+"\n" - text1 = (monster_type+new_part+basic_info+stats+new_part+saving_throws+skills+vulnerabilities+resistances+immunities+c_immunities+"\n**Senses** "+monster["senses"]+"\n**Languages** "+monster["languages"]+"\n**Challenge** "+monster["challenge_rating"]) + info = (monster_type+new_part+basic_info+stats+new_part+saving_throws+skills+vulnerabilities+resistances+immunities+c_immunities+"\n**Senses** "+monster["senses"]+"\n**Languages** "+monster["languages"]+"\n**Challenge** "+monster["challenge_rating"]) - text2 = (spec_ab) - text3 = (act) - text4 = (react) - text5 = (leg_act) + monsterInfo = [(info, query), + (specialAbilities, "Special Abilities"), + (act, "Actions"), + (react, "Reactions"), + (legendaryActions, "Legendary Actions")] self.bot.log("Returning monster information") - return(str(command),text1,text2,text3,text4,text5) - self.bot.log("Monster not in database (error code 602)") - return("I don't know that monster... (error code 602)","","","","","") + + # Sends the received information. Separates into separate messages if + # there is too much text + await ctx.send(f"Result for \"{query}\"") + for text, title in monsterInfo: + if text != "": + if len(text) < 2000: + em = discord.Embed(title = title, description = text, colour=0xDEADBF) + await ctx.channel.send(embed = em) + else: + index = text[:2000].rfind(".")+1 + em1 = discord.Embed(title = title, description = text[:index], colour=0xDEADBF) + await ctx.channel.send(embed = em1) + em2 = discord.Embed(title = "", description = text[index+1:], colour=0xDEADBF) + await ctx.channel.send(embed = em2) + + break + else: + self.bot.log("Monster not in database") + await ctx.send("I don't know that monster...") # Looks up a spell - def spellFunc(self, command): - self.bot.log("Looking up "+command) + async def spellFunc(self, ctx, query): + query = cap(query) + self.bot.log("Looking up "+query) # Opens "spells.json" data = json.load(open('resources/lookup/spells.json', encoding = "utf8")) - if str(command) in data: + if query in data: self.bot.log("Returning spell information") - spell_output = ("***"+str(command)+"***\n*"+str(data[str(command)]["level"])+" level "+str(data[str(command)]["school"])+"\nCasting Time: "+str(data[str(command)]["casting_time"])+"\nRange: "+str(data[str(command)]["range"])+"\nComponents: "+str(data[str(command)]["components"])+"\nDuration: "+str(data[str(command)]["duration"])+"*\n \n"+str(data[str(command)]["description"])) + sendMessage = (f"***{query}***\n*{data[query]['level']} level {data[query]['school']}\nCasting Time: {data[query]['casting_time']}\nRange:{data[query]['range']}\nComponents:{data[query]['components']}\nDuration:{data[query]['duration']}*\n \n{data[query]['description']}") else: self.bot.log("I don't know that spell (error code 501)") - spell_output = "I don't think that's a spell (error code 501)" - self.bot.log("Successfully ran /spell") - return(spell_output) + sendMessage = "I don't think that's a spell (error code 501)" + + if len(sendMessage) > 2000: + await ctx.send(sendMessage[:2000]) + await ctx.send(sendMessage[2000:]) + else: + await ctx.send(sendMessage) diff --git a/funcs/other/bedreNetflix.py b/funcs/other/bedreNetflix.py index 7c98520..3870a42 100644 --- a/funcs/other/bedreNetflix.py +++ b/funcs/other/bedreNetflix.py @@ -13,6 +13,8 @@ class BedreNetflix(): #Returns a list of no more than 5 options when user requests a movie async def requestMovie(self, ctx, movieName): + await ctx.defer() + self.bot.log("Searching for "+movieName) movieList = imdb.IMDb().search_movie(movieName) movies = [] @@ -89,6 +91,8 @@ class BedreNetflix(): #Returns a list of no more than 5 options when user requests a show async def requestShow(self, ctx, showName): + await ctx.defer() + self.bot.log("Searching for "+showName) movies = imdb.IMDb().search_movie(showName) #Replace with tvdb shows = [] @@ -301,6 +305,8 @@ class BedreNetflix(): await ctx.send("```"+messageText[:cutOffIndex]+"```") await SendLongMessage(ctx,messageText[cutOffIndex+1:]) + await ctx.defer() + # showDM, showMovies, showShows, episodes params = [False, False, False, False] showDMArgs = ["d", "dm", "downloading", "downloadmanager"] diff --git a/funcs/other/generators.py b/funcs/other/generators.py index febe219..37f66f9 100644 --- a/funcs/other/generators.py +++ b/funcs/other/generators.py @@ -15,7 +15,7 @@ class Generators(): yield (corpus[i], corpus[i+1], corpus[i+2]) # Generates a random name - def nameGen(self): + async def nameGen(self, ctx): # Makes a list of all names from "names.txt" names = open('resources/names.txt', encoding='utf8').read() corpus = list(names) @@ -74,11 +74,12 @@ class Generators(): done = True genName = "".join(chain) self.bot.log("Generated "+genName[:-1]) + # Returns the name - return(genName) + await ctx.send(genName) # Generates a random tavern name - def tavernGen(self): + async def tavernGen(self, ctx): # Lists first parts, second parts and third parts of tavern names fp = ["The Silver","The Golden","The Staggering","The Laughing","The Prancing","The Gilded","The Running","The Howling","The Slaughtered","The Leering","The Drunken","The Leaping","The Roaring","The Frowning","The Lonely","The Wandering","The Mysterious","The Barking","The Black","The Gleaming","The Tap-Dancing","The Sad","The Sexy","The Artificial","The Groovy","The Merciful","The Confused","The Pouting","The Horny","The Okay","The Friendly","The Hungry","The Handicapped","The Fire-breathing","The One-Eyed","The Psychotic","The Mad","The Evil","The Idiotic","The Trusty","The Busty"] sp = ["Eel","Dolphin","Dwarf","Pegasus","Pony","Rose","Stag","Wolf","Lamb","Demon","Goat","Spirit","Horde","Jester","Mountain","Eagle","Satyr","Dog","Spider","Star","Dad","Rat","Jeremy","Mouse","Unicorn","Pearl","Ant","Crab","Penguin","Octopus","Lawyer","Ghost","Toad","Handjob","Immigrant","SJW","Dragon","Bard","Sphinx","Soldier","Salmon","Owlbear","Kite","Frost Giant","Arsonist"] @@ -89,4 +90,4 @@ class Generators(): self.bot.log("Generated "+genTav) # Return the name - return(genTav) + await ctx.send(genTav) diff --git a/funcs/other/other.py b/funcs/other/other.py index 90bff31..c4a6290 100644 --- a/funcs/other/other.py +++ b/funcs/other/other.py @@ -10,6 +10,8 @@ from .bedreNetflix import BedreNetflix from .nerdShit import NerdShit from .generators import Generators +from utils import cap + class MyStringifier(d20.MarkdownStringifier): def _str_expression(self, node): if node.comment == None: @@ -57,29 +59,31 @@ class Other(): await ctx.send(embed = embed) # Responds with a greeting of a time-appropriate maner - def helloFunc(self, author): + async def helloFunc(self, ctx): def time_in_range(start, end, x): # Return true if x is in the range [start, end] if start <= end: return start <= x <= end else: return start <= x or x <= end + + author = ctx.author.display_name now = datetime.datetime.now() if time_in_range(now.replace(hour=5, minute=0, second=0, microsecond=0),now.replace(hour=10, minute=0, second=0, microsecond=0), now): - return("Good morning, "+str(author)) - elif time_in_range(now.replace(hour=10, minute=0, second=0, microsecond=0),now.replace(hour=13, minute=0, second=0, microsecond=0), now): - return("Good day, "+str(author)) + sendMessage = "Good morning, "+str(author) elif time_in_range(now.replace(hour=13, minute=0, second=0, microsecond=0),now.replace(hour=18, minute=0, second=0, microsecond=0), now): - return("Good afternoon, "+str(author)) + sendMessage = "Good afternoon, "+str(author) elif time_in_range(now.replace(hour=18, minute=0, second=0, microsecond=0),now.replace(hour=22, minute=0, second=0, microsecond=0), now): - return("Good evening, "+str(author)) + sendMessage = "Good evening, "+str(author) elif time_in_range(now.replace(hour=22, minute=0, second=0, microsecond=0),now.replace(hour=23, minute=59, second=59, microsecond=0), now): - return("Good night, "+str(author)) + sendMessage = "Good night, "+str(author) else: - return("Hello, "+str(author)) + sendMessage = "Hello, "+str(author) + + await ctx.send(sendMessage) # Finds a random picture online - def imageFunc(self): + async def imageFunc(self, ctx): # Picks a type of camera, which decides the naming scheme cams = ("one","two","three","four") cam = random.choice(cams) @@ -113,22 +117,31 @@ class Other(): tree = lxml.etree.HTML(read) images = tree.xpath('//a[@class = "thumb"]/@href') - # Picks an image - number = random.randint(1,len(images))-1 - image = images[number] + if len(images) == 0: + await ctx.send("Found no images") + else: + # Picks an image + number = random.randint(1,len(images))-1 + image = images[number] - self.bot.log("Picked image number "+str(number)) + self.bot.log("Picked image number "+str(number)) - # Returns the image - self.bot.log("Successfully returned an image") - return(image) + # Returns the image + self.bot.log("Successfully returned an image") + + await ctx.send(image) # Finds a page from the Senkulpa Wikia - def findWikiPage(self, search : str): + async def findWikiPage(self, ctx, search : str): + await ctx.defer() + search = cap(search) + foundPage = False + self.bot.log("Trying to find wiki page for "+search) wikia.set_lang("da") searchResults = wikia.search("senkulpa",search) if len(searchResults) > 0: + foundPage = True searchResult = searchResults[0].replace(",","%2C") self.bot.log("Found page \""+searchResult+"\"") page = wikia.page("senkulpa",searchResult) @@ -137,15 +150,39 @@ class Other(): images = page.images if len(images) > 0: image = images[len(images)-1]+"/revision/latest?path-prefix=da" - return page.title, content, image else: - return page.title, content, "" + image = "" else: - self.bot.log("Couldn't find the page (error code 1002)") - return "", "Couldn't find page (error code 1002)", "" + self.bot.log("Couldn't find the page") + await ctx.send("Couldn't find page (error code 1002)") - def rollDice(self, user, rollString): + if foundPage: + self.bot.log("Sending the embedded message",str(ctx.channel_id)) + content += f"\n[Læs mere]({page.url})" + embed = discord.Embed(title = page.title, description = content, colour=0xDEADBF) + if image != "": + embed.set_thumbnail(url=image) + + await ctx.send(embed = embed) + + async def rollDice(self, ctx, rollString): + user = ctx.author.display_name while len(rollString) > 1 and rollString[0] == " ": rollString = rollString[1:] - return user+" :game_die:\n"+str(d20.roll(rollString, allow_comments=True,stringifier=MyStringifier())) + + roll = d20.roll(rollString, allow_comments=True, stringifier=MyStringifier()) + await ctx.send(f"{user} :game_die:\n{roll}") + + async def helpFunc(self, ctx, command): + if command == "": + with open("resources/help/help.txt",encoding="utf-8") as f: + text = f.read() + em = discord.Embed(title = "Help", description = text,colour = 0x59f442) + await ctx.send(embed = em) + else: + self.bot.log(f"Looking for help-{command}.txt",str(ctx.channel_id)) + with open(f"resources/help/help-{command}.txt",encoding="utf-8") as f: + text = f.read() + em = discord.Embed(title = command.capitalize(), description = text,colour = 0x59f442) + await ctx.send(embed = em) diff --git a/funcs/starWarsFuncs/starWarsChar.py b/funcs/starWarsFuncs/starWarsChar.py index 71f56c9..30cb2f3 100644 --- a/funcs/starWarsFuncs/starWarsChar.py +++ b/funcs/starWarsFuncs/starWarsChar.py @@ -1,5 +1,6 @@ import json import string +import discord class StarWarsChar(): def __init__(self, bot): @@ -470,7 +471,10 @@ class StarWarsChar(): return cmd - def parseChar(self,user : str, cmd : str): + async def parseChar(self, ctx, parameters : str): + user = f"#{ctx.author.id}" + cmd = string.capwords(parameters.replace("+","+ ").replace("-","- ").replace(",",", ")) + returnEmbed = False cmd = self.replaceSpaces(cmd) @@ -487,8 +491,9 @@ class StarWarsChar(): if cmd == "": if userCharacter != None: - text1, text2 = self.characterSheet(userCharacter) - return text1, self.replaceWithSpaces(text2) + title, text = self.characterSheet(userCharacter) + text = self.replaceWithSpaces(text) + returnEmbed = True else: self.bot.log("Makin' a character for "+self.bot.databaseFuncs.getName(user)) with open("resources/starWars/starwarstemplates.json", "r") as f: @@ -496,14 +501,20 @@ class StarWarsChar(): newChar = templates["Character"] newChar["_id"] = user self.bot.database["starwars characters"].insert_one(newChar) - return "", "Character for " + self.bot.databaseFuncs.getName(user) + " created" + await ctx.send("Character for " + self.bot.databaseFuncs.getName(user) + " created") else: if cmd == "Purge": self.bot.log("Deleting "+self.bot.databaseFuncs.getName(user)+"'s character") self.bot.database["starwars characters"].delete_one({"_id":user}) - return "", "Character for " + self.bot.databaseFuncs.getName(user) + " deleted" + await ctx.send("Character for " + self.bot.databaseFuncs.getName(user) + " deleted") else: - return "", self.replaceWithSpaces(str(self.charData(user,cmd))) + await ctx.send(self.replaceWithSpaces(str(self.charData(user,cmd)))) + + if returnEmbed: + em = discord.Embed(title = title, description = text, colour=0xDEADBF) + await ctx.send(embed = em) + + def lightsaberChar(self,user : str): userCharacter = self.bot.database["starwars characters"].find_one({"_id":user}) diff --git a/funcs/starWarsFuncs/starWarsDestiny.py b/funcs/starWarsFuncs/starWarsDestiny.py index f12ddac..527d31a 100644 --- a/funcs/starWarsFuncs/starWarsDestiny.py +++ b/funcs/starWarsFuncs/starWarsDestiny.py @@ -4,13 +4,13 @@ class StarWarsDestiny(): def destinyNew(self, num : int): self.bot.log("Creating a new destiny pool with "+str(num)+" players") - roll, diceResults = self.bot.starwarsroll.roll(0,0,0,0,0,0,num) + roll, diceResults = self.bot.starWars.roll.roll(0,0,0,0,0,0,num) roll = "".join(sorted(roll)) with open("resources/starWars/destinyPoints.txt","wt") as f: f.write(roll) - return "Rolled for Destiny Points and got:\n"+self.bot.starwarsroll.diceResultToEmoji(diceResults)+"\n"+self.bot.starwarsroll.resultToEmoji(roll) + return "Rolled for Destiny Points and got:\n"+self.bot.starWars.roll.diceResultToEmoji(diceResults)+"\n"+self.bot.starWars.roll.resultToEmoji(roll) def destinyUse(self, user : str): with open("resources/starWars/destinyPoints.txt","rt") as f: @@ -24,7 +24,7 @@ class StarWarsDestiny(): with open("resources/starWars/destinyPoints.txt","wt") as f: f.write(points) self.bot.log("Did it") - return "Used a dark side destiny point. Destiny pool is now:\n"+self.bot.starwarsroll.resultToEmoji(points) + return "Used a dark side destiny point. Destiny pool is now:\n"+self.bot.starWars.roll.resultToEmoji(points) else: self.bot.log("There were no dark side destiny points") return "No dark side destiny points" @@ -36,31 +36,37 @@ class StarWarsDestiny(): with open("resources/starWars/destinyPoints.txt","wt") as f: f.write(points) self.bot.log("Did it") - return "Used a light side destiny point. Destiny pool is now:\n"+self.bot.starwarsroll.resultToEmoji(points) + return "Used a light side destiny point. Destiny pool is now:\n"+self.bot.starWars.roll.resultToEmoji(points) else: self.bot.log("There were no dark side destiny points") return "No light side destiny points" - def parseDestiny(self, user : str, cmd : str): + async def parseDestiny(self, ctx, cmd : str): + user = f"#{ctx.author.id}" if cmd != "": while cmd[0] == ' ': cmd = cmd[1:] if cmd == "": break - if cmd == "": self.bot.log("Retrieving destiny pool info") with open("resources/starWars/destinyPoints.txt","rt") as f: - return self.bot.starwarsroll.resultToEmoji(f.read()) + sendMessage = self.bot.starWars.roll.resultToEmoji(f.read()) else: commands = cmd.upper().split(" ") if commands[0] == "N": if len(commands) > 1: - return self.destinyNew(int(commands[1])) + sendMessage = self.destinyNew(int(commands[1])) else: - return "You need to give an amount of players (error code 921)" + sendMessage = "You need to give an amount of players (error code 921)" elif commands[0] == "U": - return self.destinyUse(user) + sendMessage = self.destinyUse(user) else: - return "I didn't quite understand that (error code 922)" + sendMessage = "I didn't quite understand that (error code 922)" + + messageList = sendMessage.split("\n") + await ctx.send(messageList[0]) + if len(messageList) > 1: + for messageItem in messageList[1:]: + await ctx.channel.send(messageItem) diff --git a/funcs/starWarsFuncs/starWarsRoll.py b/funcs/starWarsFuncs/starWarsRoll.py index 0cd045d..e7174ad 100644 --- a/funcs/starWarsFuncs/starWarsRoll.py +++ b/funcs/starWarsFuncs/starWarsRoll.py @@ -239,7 +239,7 @@ class StarWarsRoll(): return random.choice(table) # Rolls for critical injury - def critRoll(self, addington : int): + async def critRoll(self, ctx, addington : int): dd = "<:difficulty:690973992470708296>" sd = "<:setback:690972157890658415>" bd = "<:boost:690972178216386561>" @@ -253,14 +253,14 @@ class StarWarsRoll(): "**Discouraging Wound**: Flip one light side Destiny point to a dark side Destiny point (reverse if NPC), "+dd] * 5 + [ "**Stunned**: The target is staggered until the end of his next turn, "+dd] * 5 + [ "**Stinger**: Increase the difficulty of next check by one, "+dd] * 5 + [ - "**Bowled Over**: The target is knocked prone and suffers 1 sttrain, "+dd+dd] * 5 + [ + "**Bowled Over**: The target is knocked prone and suffers 1 strain, "+dd+dd] * 5 + [ "**Head Ringer**: The target increases the difficulty of all Intellect and Cunning checks by one until the end of the encounter, "+dd+dd] * 5 + [ "**Fearsome Wound**: The target increases the difficulty of all Presence and Willpower checks by one until the end of the encounter, "+dd+dd] * 5 + [ "**Agonizing Wound**: The target increases the difficulty of all Brawn and Agility checks by one until the end of the encounter, "+dd+dd] * 5 + [ "**Slightly Dazed**: The target is disoriented until the end of the encounter, "+dd+dd] * 5 + [ "**Scattered Senses**: The target removes all "+bd+" from skill checks until the end of the encounter, "+dd+dd] * 5 + [ "**Hamstrung**: The target loses his free maneuver until the end of the encounter, "+dd+dd] * 5 + [ - "**Overpowered**: The target leaves himselp open, and the attacker may immediately attempt another free attack agains him, using the exact same pool as the original, "+dd+dd] * 5 + [ + "**Overpowered**: The target leaves himself open, and the attacker may immediately attempt another free attack agains him, using the exact same pool as the original, "+dd+dd] * 5 + [ "**Winded**: Until the end of the encounter, the target cannot voluntarily suffer strain to activate any abilities or gain additional maneuvers, "+dd+dd] * 5 + [ "**Compromised**: Incerase difficulty of all skill checks by one until the end of the encounter, "+dd+dd] * 5 + [ "**At the brink**: The target suffers 1 strain each time he performs an action, "+dd+dd+dd] * 5 + [ @@ -288,56 +288,64 @@ class StarWarsRoll(): characteristic = random.choice(["brawn"] * 3 + ["agility"] * 3 + ["intellect", "cunning", "presence"]) results = "**Gruesome Injury**: The target's "+characteristic+" is permanently one lower, "+dd+dd+dd+dd - return "Roll: "+str(roll)+"\nInjury:\n"+results + sendMessage = "Roll: "+str(roll)+"\nInjury:\n"+results + + messageList = sendMessage.split("\n") + await ctx.send(messageList[0]) + if len(messageList) > 1: + for messageItem in messageList[1:]: + await ctx.channel.send(messageItem) # Parses the command into something the other functions understand - def parseRoll(self, user : str,cmd : str = ""): + async def parseRoll(self, ctx, cmd : str = ""): + user = f"#{ctx.author.id}" cmd = re.sub(' +',' ',cmd.upper()) + " " if cmd[0] == " ": cmd = cmd[1:] - cmd = self.bot.starwarschar.replaceSpaces(string.capwords(cmd)) + cmd = self.bot.starWars.character.replaceSpaces(string.capwords(cmd)) commands = cmd.split(" ") + validCommand = False + if commands[0] == "": rollParameters = [1,0,3,0,0,0,0] else: rollParameters = [0,0,0,0,0,0,0] if string.capwords(commands[0]) == "Obligations": - try: - return self.obligationRoll() - except: - self.bot.log("Obligation fucked up (error code 911)") - return "An error ocurred (error code 911)" + sendMessage = self.obligationRoll() elif string.capwords(commands[0]) in skillData: self.bot.log("Oh look! This guy has skills!") - if self.bot.starwarschar.userHasChar: + if self.bot.starWars.character.userHasChar(user): self.bot.log("They have a character. That much we know") - skillLevel = self.bot.starwarschar.charData(user,"Skills " + string.capwords(commands[0])) + skillLevel = self.bot.starWars.character.charData(user,"Skills " + string.capwords(commands[0])) if string.capwords(commands[0]) == "Lightsaber": self.bot.log("The skill is lightsaber") - charLevel = self.bot.starwarschar.charData(user,"Characteristics " + self.bot.starwarschar.lightsaberChar(user)) + charLevel = self.bot.starWars.character.charData(user,"Characteristics " + self.bot.starWars.character.lightsaberChar(user)) else: - charLevel = self.bot.starwarschar.charData(user,"Characteristics " + skillData[string.capwords(commands[0])]) + charLevel = self.bot.starWars.character.charData(user,"Characteristics " + skillData[string.capwords(commands[0])]) abilityDice = abs(charLevel-skillLevel) proficiencyDice = min(skillLevel,charLevel) commands = [str(abilityDice)] + [str(proficiencyDice)] + commands[1:] self.bot.log("Converted skill to dice") + validCommand = True else: - self.bot.log("Okay, no they don't i guess (error code 912)") - return "You don't have a user. You can make one with /starwarscharacter (error code 912)" + self.bot.log("Okay, no they don't i guess") + sendMessage = "You don't have a user. You can make one with /starwarscharacter" elif string.capwords(commands[0]) in ["Ranged","Piloting"]: self.bot.log("They fucked up writing the name of a ranged or piloting skill") if string.capwords(commands[0]) == "Ranged": - return "Did you mean \"Ranged - Heavy\" or \"Ranged - Light\" (error code 913)" + sendMessage = "Did you mean \"Ranged - Heavy\" or \"Ranged - Light\" (error code 913)" else: - return "Did you mean \"Piloting - Planetary\" or \"Piloting - Space\" (error code 913)" + sendMessage = "Did you mean \"Piloting - Planetary\" or \"Piloting - Space\" (error code 913)" + else: + validCommand = True - try: + if validCommand: self.bot.log("Converting commands to dice") for x, command in enumerate(commands): if command != "": @@ -358,21 +366,26 @@ class StarWarsRoll(): rollParameters[6] = int(command.replace("F","")) else: rollParameters[x] = int(command) - except: - self.bot.log("Someone fucked u-up! (it was them) (error code 914)") - return "Invalid input! (error code 914)" - self.bot.log("Rolling "+str(rollParameters)) - rollResults, diceResults = self.roll(rollParameters[0],rollParameters[1],rollParameters[2],rollParameters[3],rollParameters[4],rollParameters[5],rollParameters[6]) + self.bot.log("Rolling "+str(rollParameters)) + rollResults, diceResults = self.roll(rollParameters[0],rollParameters[1],rollParameters[2],rollParameters[3],rollParameters[4],rollParameters[5],rollParameters[6]) - simplified = self.simplify(rollResults) + simplified = self.simplify(rollResults) - name = self.bot.starwarschar.getCharName(user) + name = self.bot.starWars.character.getCharName(user) - self.bot.log("Returns results and simplified results") + self.bot.log("Returns results and simplified results") - if simplified == "": - return name + " rolls: " + "\n" + self.diceResultToEmoji(diceResults) + "\nEverything cancels out!" - else: - return name + " rolls: " + "\n" + self.diceResultToEmoji(diceResults) + "\n" + self.resultToEmoji(simplified) + if simplified == "": + sendMessage = name + " rolls: " + "\n" + self.diceResultToEmoji(diceResults) + "\nEverything cancels out!" + else: + sendMessage = name + " rolls: " + "\n" + self.diceResultToEmoji(diceResults) + "\n" + self.resultToEmoji(simplified) + messageList = sendMessage.split("\n") + await ctx.send(messageList[0]) + if len(messageList) > 1: + for messageItem in messageList[1:]: + if messageItem == "": + self.bot.log("Tried to send empty message") + else: + await ctx.channel.send(messageItem) diff --git a/project-guidelines.md b/project-guidelines.md index c351765..212f61c 100644 --- a/project-guidelines.md +++ b/project-guidelines.md @@ -44,6 +44,7 @@ Comments, strings, variable names, class names, docstrings, as well as all other ## Code Style All the Python code should follow the [PEP 8 guidelines](https://www.python.org/dev/peps/pep-0008/), with the following differences: + Variable and function names must be camelCase, and must fully consist of either full words or common/understandable abbreviations. ++ Use f-strings when applicable. ### Documentation + Comment lines should not exede 72 characters. @@ -84,7 +85,7 @@ All the Python code should follow the [PEP 8 guidelines](https://www.python.org/ Code called by a command should not have `try` and `except` statements. All errors should be raised to the `on_command_error()` or `Command.error()` functions, where they can be dealt with. ## Cogs -The `Command` methods in cogs should only exist to perform small tasks or call code from elsewhere in the Gwendolyn code. Therefore, a single `Command` method should not contain more than 3 lines of code and should not use any modules other than `Discord.py` and the Gwendolyn modules. +The `Command` methods in cogs should only exist to perform small tasks or call code from elsewhere in the Gwendolyn code. Therefore, a single `Command` method should not contain more than 3 lines of code and should not use any modules other than `Discord.py` and the Gwendolyn modules. If a cog method calls a function in Gwendolyn, ctx must be passed, and the function should handle sending messages. ## Codebase Management ### Folders @@ -102,11 +103,11 @@ Things you should know about the logging: + The function can take either a list of strings or a string as its first parameter. If the parameter is a string, it is converted to a list of 1 string. + The first string in the list is printed. All strings in the list are logged to the log-file. + If the list is longer than 1 string, `(details in log)` is added to the printed string. -+ The level parameter is 20 by default, which means the level is `INFO`. 40 corresponds to a level of `ERROR`, and 10 corresponds to a level of `DEBUG`. Only use these levels when logging. -+ Logs of level `DEBUG` are not printed. -+ Logs of level `ERROR` should only be created in the `on_command_error()` or `Command.error()` functions. ++ The level parameter is 20 by default, which means the level is `INFO`. 40 corresponds to a level of `ERROR`, and 25 corresponds to `print`. ++ Logs of level `INFO` are not printed. ++ Logs of level `ERROR` should only be created in the `on_command_error()`, `on_error()` or `Command.error()` functions. ### Logging rules 1. Never call the `logThis()` function from `/utils/utilFuncs/`. Always call `bot.log`. -1. The `on_slash_command()` and `on_ready()` events are the only times log should be called at level 20.`DEBUG` level logs should be used for all other logging. +1. The `on_slash_command()` and `on_ready()` events are the only times log should be called at level 25. `INFO` level logs should be used for all other logging. 1. Always provide the channel id if available. Although you shouldn't pass the channel id to a function purely to use it in logs. diff --git a/requirements.txt b/requirements.txt index ca4587f..e49b144 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,8 @@ cachetools==4.2.1 certifi==2020.12.5 chardet==3.0.4 d20==1.0.4 -discord.py==1.6.0 +discord-py-slash-command @ git+https://github.com/eunwoo1104/discord-py-slash-command.git@3c63f9fe9d186c8492e85c3153aff268f1dd5cae +discord.py==1.7.1 dnspython==2.1.0 finnhub-python==2.1.0 gitdb==4.0.7 @@ -14,7 +15,9 @@ GitPython==3.1.0 greenlet==1.0.0 idna==2.10 IMDbPY==6.8 +inflect==5.3.0 jaraco.context==4.0.0 +jaraco.itertools==6.0.1 lark-parser==0.9.0 lxml==4.5.0 more-itertools==8.7.0 @@ -24,9 +27,10 @@ numpy==1.18.2 Pillow==7.1.1 pymongo==3.11.3 requests==2.25.1 +six==1.15.0 smmap==4.0.0 soupsieve==2.2.1 -SQLAlchemy==1.4.3 +SQLAlchemy==1.4.5 typing-extensions==3.7.4.3 urllib3==1.25.8 websockets==8.1 diff --git a/resources/help/help-blackjack.txt b/resources/help/help-blackjack.txt index 49464d1..b3bc998 100644 --- a/resources/help/help-blackjack.txt +++ b/resources/help/help-blackjack.txt @@ -1 +1 @@ -Kommandoen `/blackjack` starter et spil blackjack. `/blackjack bet [beløb]` lader dig vædde en mængde af dine GwendoBucks. Du bruger `/blackjack hit`, `/blackjack stand`, `/blackjack split` og `/blackjack double` i løbet af spillet. \ No newline at end of file +Kommandoen `/blackjack start` starter et spil blackjack. `/blackjack bet [beløb]` lader dig vædde en mængde af dine GwendoBucks. Du bruger `/blackjack hit`, `/blackjack stand`, `/blackjack split` og `/blackjack double` i løbet af spillet. \ No newline at end of file diff --git a/resources/slashParameters.json b/resources/slashParameters.json index 84dab46..0b38945 100644 --- a/resources/slashParameters.json +++ b/resources/slashParameters.json @@ -40,6 +40,29 @@ } ] }, + "blackjackCards" : { + "base" : "blackjack", + "name" : "cards", + "description" : "Get a count of the cards used in blackjack games" + }, + "blackjackDouble" : { + "base" : "blackjack", + "name" : "double", + "description" : "Double your bet in blackjack", + "options" : [ + { + "name" : "hand", + "description" : "The number of the hand to double your bet on", + "type" : 4, + "required" : "false" + } + ] + }, + "blackjackHilo" : { + "base" : "blackjack", + "name" : "hilo", + "description" : "Get the current hi-lo value for the cards used in blackjack games" + }, "blackjackHit" : { "base" : "blackjack", "name" : "hit", @@ -53,6 +76,24 @@ } ] }, + "blackjackShuffle" : { + "base" : "blackjack", + "name" : "shuffle", + "description" : "Shuffle the cards used in blackjack games" + }, + "blackjackSplit" : { + "base" : "blackjack", + "name" : "split", + "description" : "Split your hand in blackjack", + "options" : [ + { + "name" : "hand", + "description" : "The number of the hand to split, in case you've already split once", + "type" : 4, + "required" : "false" + } + ] + }, "blackjackStand" : { "base" : "blackjack", "name" : "stand", @@ -99,23 +140,10 @@ } ] }, - "connectFourStop" : { + "connectFourSurrender" : { "base" : "connectFour", - "name" : "stop", - "description" : "Stop the game of connect four" - }, - "connectFourPlace" : { - "base" : "connectFour", - "name" : "place", - "description" : "Place a piece", - "options" : [ - { - "name" : "column", - "description" : "The column to place the piece", - "type" : 4, - "required" : "true" - } - ] + "name" : "surrender", + "description" : "Surrender the game of connect four" }, "downloading" : { "name" : "downloading", diff --git a/utils/__init__.py b/utils/__init__.py index 4598629..7d2a46c 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -1,6 +1,7 @@ """A collections of utilities used by Gwendolyn and her functions""" -__all__ = ["Options", "Credentials", "databaseFuncs", "getParams", "logThis", "cap", "makeFiles", "replaceMultiple", "emojiToCommand"] +__all__ = ["Options", "Credentials", "databaseFuncs", "EventHandler", "ErrorHandler", "getParams", "logThis", "cap", "makeFiles", "replaceMultiple", "emojiToCommand"] from .helperClasses import Options, Credentials, databaseFuncs +from .eventHandlers import EventHandler, ErrorHandler from .utilFunctions import getParams, logThis, cap, makeFiles, replaceMultiple, emojiToCommand \ No newline at end of file diff --git a/utils/eventHandlers.py b/utils/eventHandlers.py new file mode 100644 index 0000000..1d2de31 --- /dev/null +++ b/utils/eventHandlers.py @@ -0,0 +1,103 @@ +import discord, traceback, discord_slash, sys +from discord.ext import commands + +from .utilFunctions import emojiToCommand + +class EventHandler(): + def __init__(self, bot): + self.bot = bot + + async def on_slash_command(self, ctx): + if ctx.subcommand_name is not None: + subcommand = f" {ctx.subcommand_name} " + else: + subcommand = " " + args = " ".join([str(i) for i in ctx.args]) + fullCommand = f"/{ctx.command}{subcommand}{args}" + logMessage = f"{ctx.author.display_name} ran {fullCommand}" + self.bot.log(logMessage, str(ctx.channel_id), level = 25) + + async def on_ready(self): + await self.bot.databaseFuncs.syncCommands() + self.bot.log("Logged in as "+self.bot.user.name+", "+str(self.bot.user.id), level = 25) + game = discord.Game("Use /help for commands") + await self.bot.change_presence(activity=game, status = discord.Status.online) + + async def on_reaction_add(self, reaction, user): + if user.bot == False: + message = reaction.message + channel = message.channel + self.bot.log(f"{user.display_name} reacted to a message",str(channel.id)) + try: + connectFourTheirTurn, piece = self.bot.databaseFuncs.connectFourReactionTest(channel,message,"#"+str(user.id)) + except: + connectFourTheirTurn = False + + bedreNetflixMessage, addMovie, imdbIds = self.bot.databaseFuncs.bedreNetflixReactionTest(channel, message) + + if connectFourTheirTurn: + column = emojiToCommand(reaction.emoji) + await self.bot.games.connectFour.placePiece(message, f"#{user.id}", column-1) + elif bedreNetflixMessage and addMovie: + moviePick = emojiToCommand(reaction.emoji) + await message.delete() + if moviePick == "none": + imdbID = None + else: + imdbID = imdbIds[moviePick-1] + await self.bot.other.bedreNetflix.addMovie(channel,imdbID) + elif bedreNetflixMessage and not addMovie: + showPick = emojiToCommand(reaction.emoji) + await message.delete() + if showPick == "none": + imdbName = None + else: + imdbName = imdbIds[showPick-1] + await self.bot.other.bedreNetflix.addShow(channel,imdbName) + elif self.bot.databaseFuncs.hangmanReactionTest(channel, message, f"#{user.id}"): + self.bot.log("They reacted to the hangman message") + if ord(reaction.emoji) in range(127462,127488): + guess = chr(ord(reaction.emoji)-127397) + await self.bot.games.hangman.guess(message, f"#{user.id}", guess) + else: + self.bot.log("Bot they didn't react with a valid guess") + +class ErrorHandler(): + def __init__(self, bot): + self.bot = bot + + async def on_slash_command_error(self, ctx, error): + if isinstance(error, commands.CommandNotFound): + await ctx.send("That's not a command (error code 001)") + elif isinstance(error, discord.errors.NotFound): + self.bot.log("Deleted message before I could add all reactions") + elif isinstance(error, commands.errors.MissingRequiredArgument): + self.bot.log(f"{error}",str(ctx.channel_id)) + await ctx.send("Missing command parameters (error code 002). Try using `!help [command]` to find out how to use the command.") + else: + exception = traceback.format_exception(type(error), error, error.__traceback__) + stopAt = "\nThe above exception was the direct cause of the following exception:\n\n" + if stopAt in exception: + index = exception.index(stopAt) + exception = exception[:index] + + exceptionString = "".join(exception) + self.bot.log([f"exception in /{ctx.name}", f"{exceptionString}"],str(ctx.channel_id), 40) + if isinstance(error, discord.errors.NotFound): + self.bot.log("Context is non-existant", level = 40) + else: + await ctx.send("Something went wrong (error code 000)") + + async def on_error(self, method): + errorType = sys.exc_info()[0] + if errorType == discord.errors.NotFound: + self.bot.log("Deleted message before I could add all reactions") + else: + exception = traceback.format_exc() + stopAt = "\nThe above exception was the direct cause of the following exception:\n\n" + if stopAt in exception: + index = exception.index(stopAt) + exception = exception[:index] + + exceptionString = "".join(exception) + self.bot.log([f"exception in {method}", f"{exceptionString}"], level = 40) \ No newline at end of file diff --git a/utils/helperClasses.py b/utils/helperClasses.py index 07f2f99..53894f3 100644 --- a/utils/helperClasses.py +++ b/utils/helperClasses.py @@ -53,12 +53,12 @@ class databaseFuncs(): def getName(self, userID): user = self.bot.database["users"].find_one({"_id":userID}) - if user != None: + if userID == f"#{self.bot.user.id}": + return "Gwendolyn" + elif user != None: return user["user name"] - elif userID == "Gwendolyn": - return userID else: - self.bot.log("Couldn't find user "+userID) + self.bot.log(f"Couldn't find user {userID}") return userID def getID(self,userName): @@ -70,7 +70,7 @@ class databaseFuncs(): self.bot.log("Couldn't find user "+userName) return None - def deleteGame(self, gameType,channel): + def deleteGame(self, gameType, channel): self.bot.database[gameType].delete_one({"_id":channel}) def stopServer(self): @@ -78,6 +78,7 @@ class databaseFuncs(): self.bot.database["blackjack games"].delete_many({}) self.bot.database["connect 4 games"].delete_many({}) self.bot.database["hangman games"].delete_many({}) + self.bot.database["hex games"].delete_many({}) if not self.bot.options.testing: g = git.cmd.Git("") @@ -100,7 +101,7 @@ class databaseFuncs(): else: return False, 0 - def hangmanReactionTest(self, channel,message): + def hangmanReactionTest(self, channel, message, user): try: with open("resources/games/oldImages/hangman"+str(channel.id), "r") as f: oldMessages = f.read().splitlines() @@ -111,8 +112,11 @@ class databaseFuncs(): for oldMessage in oldMessages: oldMessageID = int(oldMessage) if message.id == oldMessageID: - self.bot.log("They reacted to the hangman game") - gameMessage = True + game = self.bot.database["hangman games"].find_one({"_id":str(channel.id)}) + if user == game["player"]: + gameMessage = True + + break return gameMessage diff --git a/utils/utilFunctions.py b/utils/utilFunctions.py index aaee5bf..b821576 100644 --- a/utils/utilFunctions.py +++ b/utils/utilFunctions.py @@ -1,11 +1,21 @@ import json -import time import logging import os +import sys from .helperClasses import Options +FORMAT = " %(asctime)s | %(name)-16s | %(levelname)-8s | %(message)s" +PRINTFORMAT = "%(asctime)s - %(message)s" +DATEFORMAT = "%Y-%m-%d %H:%M:%S" + logging.addLevelName(25, "PRINT") -logging.basicConfig(filename="gwendolyn.log", level=logging.INFO) +logging.basicConfig(format=FORMAT, datefmt=DATEFORMAT, level=logging.INFO, filename="gwendolyn.log") +logger = logging.getLogger("Gwendolyn") +printer = logging.getLogger("printer") +handler = logging.StreamHandler(sys.stdout) +handler.setFormatter(logging.Formatter(fmt = PRINTFORMAT, datefmt=DATEFORMAT)) +printer.addHandler(handler) +printer.propagate = False def getParams(): with open("resources/slashParameters.json", "r") as f: @@ -20,25 +30,24 @@ def getParams(): return params def logThis(messages, channel : str = "", level : int = 20): - localtime = time.asctime(time.localtime(time.time())) channel = channel.replace("Direct Message with ","") if type(messages) is str: messages = [messages] + printMessage = messages[0] + for x, msg in enumerate(messages): - if channel == "": - messages[x] = localtime+" - "+msg - else: - messages[x] = localtime+" ("+channel+") - "+msg + if channel != "": + messages[x] = f"{msg} - ({channel})" if len(messages) > 1: - messages[0] += " (details in log)" + printMessage += " (details in log)" if level >= 25: - print(messages[0]) + printer.log(level, printMessage) for logMessage in messages: - logging.log(level, logMessage) + logger.log(level, logMessage) # Capitalizes all words except some of them def cap(s):