From 43f26ec38376ac566a8602e690536000f1f357b5 Mon Sep 17 00:00:00 2001 From: NikolajDanger Date: Thu, 15 Apr 2021 15:16:56 +0200 Subject: [PATCH 01/23] :pencil: Cogs and utils Improved code style and added comments and docstrings to cogs and utils. --- Gwendolyn.py | 137 +++++++++++++++------ cogs/EventCog.py | 20 ++-- cogs/GameCogs.py | 112 ++++++++++------- cogs/LookupCog.py | 18 ++- cogs/MiscCog.py | 53 +++++---- cogs/StarWarsCog.py | 31 ++--- resources/longStrings.json | 4 + utils/__init__.py | 9 +- utils/eventHandlers.py | 184 ++++++++++++++++++---------- utils/helperClasses.py | 238 ++++++++++++++++++++++++++++++++----- utils/utilFunctions.py | 174 ++++++++++++++++++++++----- 11 files changed, 725 insertions(+), 255 deletions(-) create mode 100644 resources/longStrings.json diff --git a/Gwendolyn.py b/Gwendolyn.py index 5a48420..20342c5 100644 --- a/Gwendolyn.py +++ b/Gwendolyn.py @@ -1,63 +1,138 @@ -import os, finnhub, platform, asyncio, discord +""" +Contains the Gwendolyn class, and runs it when run as script. -from discord.ext import commands -from discord_slash import SlashCommand -from pymongo import MongoClient +*Classes* +--------- + Gwendolyn(discord.ext.commands.Bot) +""" +import os # Used for loading cogs in Gwendolyn.addCogs +import finnhub # Used to add a finhub client to the bot +import platform # Used to test if the bot is running on windows, in +# order to fix a bug with asyncio +import asyncio # used to set change the loop policy if the bot is +# running on windows +import discord # Used for discord.Intents and discord.Status +import discord_slash # Used to initialized SlashCommands object + +from discord.ext import commands # Used to inherit from commands.bot +from pymongo import MongoClient # Used for database management from funcs import Money, StarWars, Games, Other, LookupFuncs -from utils import Options, Credentials, logThis, makeFiles, databaseFuncs, EventHandler, ErrorHandler +from utils import (Options, Credentials, logThis, makeFiles, databaseFuncs, + EventHandler, ErrorHandler, longStrings) + class Gwendolyn(commands.Bot): + """ + A multifunctional Discord bot. + + *Methods* + --------- + log(messages: Union[str, list], channel: str = "", + level: int = 20) + stop(ctx: discord_slash.context.SlashContext) + defer(ctx: discord_slash.context.SlashContext) + """ + def __init__(self): + """Initialize the bot.""" + intents = discord.Intents.default() + intents.members = True + initParams = { + "command_prefix": " ", + "case_insensitive": True, + "intents": intents, + "status": discord.Status.dnd + } + super().__init__(**initParams) + + self._addClientsAndOptions() + self._addUtilClasses() + self._addFunctionContainers() + self._addCogs() + + def _addClientsAndOptions(self): + """Add all the client, option and credentials objects.""" + self.longStrings = longStrings() self.options = Options() self.credentials = Credentials() - self.finnhubClient = finnhub.Client(api_key = self.credentials.finnhubKey) - self.MongoClient = MongoClient(f"mongodb+srv://{self.credentials.mongoDBUser}:{self.credentials.mongoDBPassword}@gwendolyn.qkwfy.mongodb.net/Gwendolyn?retryWrites=true&w=majority") + finnhubKey = self.credentials.finnhubKey + self.finnhubClient = finnhub.Client(api_key=finnhubKey) + mongoUser = self.credentials.mongoDBUser + mongoPassword = self.credentials.mongoDBPassword + mongoLink = f"mongodb+srv://{mongoUser}:{mongoPassword}@gwendolyn" + mongoLink += ".qkwfy.mongodb.net/Gwendolyn?retryWrites=true&w=majority" + dataBaseClient = MongoClient(mongoLink) if self.options.testing: self.log("Testing mode") - self.database = self.MongoClient["Gwendolyn-Test"] + self.database = dataBaseClient["Gwendolyn-Test"] else: - self.database = self.MongoClient["Gwendolyn"] + self.database = dataBaseClient["Gwendolyn"] + def _addUtilClasses(self): + """Add all the classes used as utility.""" + self.databaseFuncs = databaseFuncs(self) + self.eventHandler = EventHandler(self) + self.errorHandler = ErrorHandler(self) + slashParams = { + "sync_commands": True, + "sync_on_cog_reload": True, + "override_type": True + } + self.slash = discord_slash.SlashCommand(self, **slashParams) + + def _addFunctionContainers(self): + """Add all the function containers used for commands.""" self.starWars = StarWars(self) self.other = Other(self) self.lookupFuncs = LookupFuncs(self) 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 + def _addCogs(self): + """Load cogs.""" + for filename in os.listdir("./cogs"): + if filename.endswith(".py"): + self.load_extension(f"cogs.{filename[:-3]}") - super().__init__(command_prefix=" ", case_insensitive=True, intents = intents, status = discord.Status.dnd) - - def log(self, messages, channel : str = "", level : int = 20): + def log(self, messages, channel: str = "", level: int = 20): + """Log a message. Described in utils/utilFunctions.py.""" logThis(messages, channel, level) - async def stop(self, ctx): + async def stop(self, ctx: discord_slash.context.SlashContext): + """ + Stop the bot, and stop running games. + + Only stops the bot if the user in ctx.author is one of the + admins given in options.txt. + + *parameters* + ------------ + ctx: discord_slash.context.SlashContext + The context of the "/stop" slash command. + """ 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) + await self.change_presence(status=discord.Status.offline) - self.databaseFuncs.stopServer() + self.databaseFuncs.wipeGames() - self.log("Logging out", level = 25) + 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)") + logMessage = f"{ctx.author.display_name} tried to stop me!" + self.log(logMessage, str(ctx.channel_id)) + await ctx.send(f"I don't think I will, {ctx.author.display_name}") - async def defer(self, ctx): + async def defer(self, ctx: discord_slash.context.SlashContext): + """Send a "Gwendolyn is thinking" message to the user.""" try: await ctx.defer() - except: + except discord_slash.error.AlreadyResponded: self.log("defer failed") - if __name__ == "__main__": if platform.system() == "Windows": asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) @@ -67,15 +142,9 @@ if __name__ == "__main__": # Creates the Bot bot = Gwendolyn() - bot.slash = SlashCommand(bot, sync_commands=True, sync_on_cog_reload=True, override_type=True) - - #Loads cogs - for filename in os.listdir("./cogs"): - if filename.endswith(".py"): - bot.load_extension(f"cogs.{filename[:-3]}") try: # Runs the whole shabang bot.run(bot.credentials.token) - except: - bot.log("Could not log in. Remember to write your bot token in the credentials.txt file") \ No newline at end of file + except Exception: + bot.log(bot.longStrings["Can't log in"]) diff --git a/cogs/EventCog.py b/cogs/EventCog.py index 7b72f6b..50fc3b9 100644 --- a/cogs/EventCog.py +++ b/cogs/EventCog.py @@ -1,34 +1,40 @@ -from discord.ext import commands +"""Contains the EventCog, which runs code for specific bot events.""" +from discord.ext import commands # Has the cog class + class EventCog(commands.Cog): + """Handles bot events.""" + def __init__(self, bot): + """Initialize the 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): + """Log and set bot status when bot logs in.""" await self.bot.eventHandler.on_ready() - # Logs when user sends a command @commands.Cog.listener() async def on_slash_command(self, ctx): + """Log when a slash command is run.""" 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): + """Log when a slash error occurs.""" await self.bot.errorHandler.on_slash_command_error(ctx, error) - # Logs if on error occurs async def on_error(self, method, *args, **kwargs): + """Log when an error occurs.""" await self.bot.errorHandler.on_error(method) - # 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_reaction_add(self, reaction, user): + """Handle when someone reacts to a message.""" await self.bot.eventHandler.on_reaction_add(reaction, user) + def setup(bot): + """Add the eventcog to the bot.""" bot.add_cog(EventCog(bot)) diff --git a/cogs/GameCogs.py b/cogs/GameCogs.py index 4714437..c813e24 100644 --- a/cogs/GameCogs.py +++ b/cogs/GameCogs.py @@ -1,155 +1,177 @@ -from discord.ext import commands -from discord_slash import cog_ext +"""Contains all the cogs that deal with game commands.""" +from discord.ext import commands # Has the cog class +from discord_slash import cog_ext # Used for slash commands -from utils import getParams +from utils import getParams # pylint: disable=import-error params = getParams() + class GamesCog(commands.Cog): - def __init__(self,bot): - """Runs game stuff.""" + """Contains miscellaneous game commands.""" + + def __init__(self, bot): + """Initialize the cog.""" self.bot = bot - # Checks user balance @cog_ext.cog_slash(**params["balance"]) async def balance(self, ctx): + """Check user balance.""" 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): + """Give another user an amount of GwendoBucks.""" 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"): + async def invest(self, ctx, parameters="check"): + """Invest GwendoBucks in the stock market.""" 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 = ""): + async def trivia(self, ctx, answer=""): + """Run a game of trivia.""" await self.bot.games.trivia.triviaParse(ctx, answer) class BlackjackCog(commands.Cog): - def __init__(self,bot): - """Runs game stuff.""" + """Contains the blackjack commands.""" + + def __init__(self, bot): + """Initialize the cog.""" self.bot = bot - # Starts a game of blackjack @cog_ext.cog_subcommand(**params["blackjackStart"]) async def blackjackStart(self, ctx): + """Start a game of blackjack.""" await self.bot.games.blackjack.start(ctx) @cog_ext.cog_subcommand(**params["blackjackBet"]) async def blackjackBet(self, ctx, bet): + """Enter the game of blackjack with a bet.""" await self.bot.games.blackjack.playerDrawHand(ctx, bet) @cog_ext.cog_subcommand(**params["blackjackStand"]) - async def blackjackStand(self, ctx, hand = ""): + async def blackjackStand(self, ctx, hand=""): + """Stand on your hand in blackjack.""" await self.bot.games.blackjack.stand(ctx, hand) @cog_ext.cog_subcommand(**params["blackjackHit"]) - async def blackjackHit(self, ctx, hand = 0): + async def blackjackHit(self, ctx, hand=0): + """Hit on your hand in blackjack.""" await self.bot.games.blackjack.hit(ctx, hand) @cog_ext.cog_subcommand(**params["blackjackDouble"]) - async def blackjackDouble(self, ctx, hand = 0): + async def blackjackDouble(self, ctx, hand=0): + """Double in blackjack.""" await self.bot.games.blackjack.double(ctx, hand) @cog_ext.cog_subcommand(**params["blackjackSplit"]) - async def blackjackSplit(self, ctx, hand = 0): + async def blackjackSplit(self, ctx, hand=0): + """Split your hand in blackjack.""" await self.bot.games.blackjack.split(ctx, hand) @cog_ext.cog_subcommand(**params["blackjackHilo"]) async def blackjackHilo(self, ctx): + """Get the hilo value for the deck in blackjack.""" await self.bot.games.blackjack.hilo(ctx) @cog_ext.cog_subcommand(**params["blackjackShuffle"]) async def blackjackShuffle(self, ctx): + """Shuffle the blackjack game.""" await self.bot.games.blackjack.shuffle(ctx) @cog_ext.cog_subcommand(**params["blackjackCards"]) async def blackjackCards(self, ctx): + """Get the amount of cards left in the blackjack deck.""" await self.bot.games.blackjack.cards(ctx) class ConnectFourCog(commands.Cog): - def __init__(self,bot): - """Runs game stuff.""" + """Contains all the connect four commands.""" + + def __init__(self, bot): + """Initialize the cog.""" self.bot = bot - # Start a game of connect four against a user @cog_ext.cog_subcommand(**params["connectFourStartUser"]) async def connectFourStartUser(self, ctx, user): + """Start a game of connect four against another user.""" 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): + async def connectFourStartGwendolyn(self, ctx, difficulty=3): + """Start a game of connect four against Gwendolyn.""" await self.bot.games.connectFour.start(ctx, difficulty) - # Stop the current game of connect four @cog_ext.cog_subcommand(**params["connectFourSurrender"]) async def connectFourSurrender(self, ctx): + """Surrender the game of connect four.""" await self.bot.games.connectFour.surrender(ctx) class HangmanCog(commands.Cog): - def __init__(self,bot): - """Runs game stuff.""" + """Contains all the hangman commands.""" + + def __init__(self, bot): + """Initialize the cog.""" self.bot = bot - # Starts a game of Hangman @cog_ext.cog_subcommand(**params["hangmanStart"]) async def hangmanStart(self, ctx): + """Start a game of hangman.""" await self.bot.games.hangman.start(ctx) - # Stops a game of Hangman @cog_ext.cog_subcommand(**params["hangmanStop"]) async def hangmanStop(self, ctx): + """Stop the current game of hangman.""" await self.bot.games.hangman.stop(ctx) class HexCog(commands.Cog): - def __init__(self,bot): - """Runs game stuff.""" - self.bot = bot + """Contains all the hex commands.""" + + def __init__(self, bot): + """Initialize the cog.""" + self.bot = bot + self.hex = self.bot.games.hex - # Start a game of Hex against another user @cog_ext.cog_subcommand(**params["hexStartUser"]) async def hexStartUser(self, ctx, user): - await self.bot.games.hex.start(ctx, user) + """Start a game of hex against another player.""" + await self.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 self.bot.games.hex.start(ctx, difficulty) + async def hexStartGwendolyn(self, ctx, difficulty=2): + """Start a game of hex against Gwendolyn.""" + await self.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.hex.placeHex(ctx, coordinates, f"#{ctx.author.id}") + """Place a piece in the hex game.""" + await self.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) + """Undo your last hex move.""" + await self.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) + """Perform a hex swap.""" + await self.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) + """Surrender the hex game.""" + await self.hex.surrender(ctx) def setup(bot): + """Add all the cogs to the bot.""" bot.add_cog(GamesCog(bot)) bot.add_cog(BlackjackCog(bot)) bot.add_cog(ConnectFourCog(bot)) bot.add_cog(HangmanCog(bot)) - bot.add_cog(HexCog(bot)) \ No newline at end of file + bot.add_cog(HexCog(bot)) diff --git a/cogs/LookupCog.py b/cogs/LookupCog.py index 1b23cd1..f41d38b 100644 --- a/cogs/LookupCog.py +++ b/cogs/LookupCog.py @@ -1,24 +1,32 @@ -from discord.ext import commands -from discord_slash import cog_ext +"""Contains the LookupCog, which deals with the lookup commands.""" +from discord.ext import commands # Has the cog class +from discord_slash import cog_ext # Used for slash commands -from utils import getParams +from utils import getParams # pylint: disable=import-error params = getParams() + class LookupCog(commands.Cog): + """Contains the lookup commands.""" + def __init__(self, bot): - """Runs lookup commands.""" + """Initialize the cog.""" self.bot = bot # Looks up a spell @cog_ext.cog_slash(**params["spell"]) async def spell(self, ctx, query): + """Look up a spell.""" await self.bot.lookupFuncs.spellFunc(ctx, query) # Looks up a monster @cog_ext.cog_slash(**params["monster"]) async def monster(self, ctx, query): + """Look up a monster.""" await self.bot.lookupFuncs.monsterFunc(ctx, query) + def setup(bot): - bot.add_cog(LookupCog(bot)) \ No newline at end of file + """Add the cog to the bot.""" + bot.add_cog(LookupCog(bot)) diff --git a/cogs/MiscCog.py b/cogs/MiscCog.py index 136c0fb..ab6e146 100644 --- a/cogs/MiscCog.py +++ b/cogs/MiscCog.py @@ -1,94 +1,99 @@ -import discord, codecs, string, json -from discord.ext import commands -from discord_slash import cog_ext +"""Contains the MiscCog, which deals with miscellaneous commands.""" +from discord.ext import commands # Has the cog class +from discord_slash import cog_ext # Used for slash commands -from utils import getParams # pylint: disable=import-error +from utils import getParams # pylint: disable=import-error params = getParams() + class MiscCog(commands.Cog): + """Contains the miscellaneous commands.""" + def __init__(self, bot): - """Runs misc commands.""" + """Initialize the cog.""" self.bot = bot self.bot.remove_command("help") self.generators = bot.other.generators self.bedreNetflix = bot.other.bedreNetflix self.nerdShit = bot.other.nerdShit - # Sends the bot's latency @cog_ext.cog_slash(**params["ping"]) async def ping(self, ctx): + """Send the bot's latency.""" await ctx.send(f"Pong!\nLatency is {round(self.bot.latency * 1000)}ms") - # Restarts the bot @cog_ext.cog_slash(**params["stop"]) async def stop(self, ctx): + """Stop the bot.""" await self.bot.stop(ctx) - # Gets help for specific command @cog_ext.cog_slash(**params["help"]) - async def helpCommand(self, ctx, command = ""): + async def helpCommand(self, ctx, command=""): + """Get help for commands.""" await self.bot.other.helpFunc(ctx, command) - # Lets you thank the bot @cog_ext.cog_slash(**params["thank"]) async def thank(self, ctx): + """Thank the bot.""" await ctx.send("You're welcome :blush:") - # Sends a friendly message @cog_ext.cog_slash(**params["hello"]) async def hello(self, ctx): + """Greet the bot.""" await self.bot.other.helloFunc(ctx) - # Rolls dice @cog_ext.cog_slash(**params["roll"]) - async def roll(self, ctx, dice = "1d20"): + async def roll(self, ctx, dice="1d20"): + """Roll dice.""" await self.bot.other.rollDice(ctx, dice) - # Sends a random image @cog_ext.cog_slash(**params["image"]) async def image(self, ctx): + """Get a random image from Bing.""" await self.bot.other.imageFunc(ctx) - # Finds a random movie @cog_ext.cog_slash(**params["movie"]) async def movie(self, ctx): + """Get a random movie from the Plex server.""" await self.bot.other.movieFunc(ctx) - # Generates a random name @cog_ext.cog_slash(**params["name"]) async def name(self, ctx): + """Generate a random name.""" await self.generators.nameGen(ctx) - # Generates a random tavern name @cog_ext.cog_slash(**params["tavern"]) async def tavern(self, ctx): + """Generate a random tavern name.""" await self.generators.tavernGen(ctx) - # Finds a page on the Senkulpa wiki @cog_ext.cog_slash(**params["wiki"]) - async def wiki(self, ctx, wikiPage = ""): + async def wiki(self, ctx, wikiPage=""): + """Get a page on a fandom wiki.""" 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): + """Search for a movie and add it to the Plex server.""" 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): + """Search for a show and add it to the Plex server.""" await self.bedreNetflix.requestShow(ctx, show) - #Returns currently downloading torrents @cog_ext.cog_slash(**params["downloading"]) - async def downloading(self, ctx, parameters = "-d"): + async def downloading(self, ctx, parameters="-d"): + """Get the current downloading torrents.""" await self.bedreNetflix.downloading(ctx, parameters) - #Looks up on Wolfram Alpha @cog_ext.cog_slash(**params["wolf"]) async def wolf(self, ctx, query): + """Perform a search on Wolfram Alpha.""" await self.nerdShit.wolfSearch(ctx, query) + def setup(bot): + """Add the cog to the bot.""" bot.add_cog(MiscCog(bot)) diff --git a/cogs/StarWarsCog.py b/cogs/StarWarsCog.py index 57e39f0..72516d4 100644 --- a/cogs/StarWarsCog.py +++ b/cogs/StarWarsCog.py @@ -1,37 +1,40 @@ -import discord, string, json +"""Contains the StarWarsCog, which deals with Star Wars commands.""" from discord.ext import commands from discord_slash import cog_ext -from utils import getParams +from utils import getParams # pylint: disable=import-error params = getParams() -class starWarsCog(commands.Cog): + +class StarWarsCog(commands.Cog): + """Contains the Star Wars commands.""" def __init__(self, bot): - """Runs star wars commands.""" + """Initialize the cog.""" self.bot = bot - # Rolls star wars dice @cog_ext.cog_slash(**params["starWarsRoll"]) - async def starWarsRoll(self, ctx, dice = ""): + async def starWarsRoll(self, ctx, dice=""): + """Roll Star Wars dice.""" await self.bot.starWars.roll.parseRoll(ctx, dice) - # Controls destiny points @cog_ext.cog_slash(**params["starWarsDestiny"]) - async def starWarsDestiny(self, ctx, parameters = ""): + async def starWarsDestiny(self, ctx, parameters=""): + """Control Star Wars destiny points.""" 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): + async def starWarsCrit(self, ctx, severity: int = 0): + """Roll for critical injuries.""" 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 = ""): + async def starWarsCharacter(self, ctx, parameters=""): + """Access and change Star Wars character sheet data.""" await self.bot.starWars.character.parseChar(ctx, parameters) + def setup(bot): - bot.add_cog(starWarsCog(bot)) \ No newline at end of file + """Add the cog to the bot.""" + bot.add_cog(StarWarsCog(bot)) diff --git a/resources/longStrings.json b/resources/longStrings.json new file mode 100644 index 0000000..a7b418f --- /dev/null +++ b/resources/longStrings.json @@ -0,0 +1,4 @@ +{ + "missing parameters" : "Missing command parameters. Try using `!help [command]` to find out how to use the command.", + "Can't log in": "Could not log in. Remember to write your bot token in the credentials.txt file" +} \ No newline at end of file diff --git a/utils/__init__.py b/utils/__init__.py index 7d2a46c..43fa506 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -1,7 +1,10 @@ -"""A collections of utilities used by Gwendolyn and her functions""" +"""A collections of utilities used by Gwendolyn and her functions.""" -__all__ = ["Options", "Credentials", "databaseFuncs", "EventHandler", "ErrorHandler", "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 +from .utilFunctions import (getParams, logThis, cap, makeFiles, + replaceMultiple, emojiToCommand, longStrings) diff --git a/utils/eventHandlers.py b/utils/eventHandlers.py index 491846b..1ec9810 100644 --- a/utils/eventHandlers.py +++ b/utils/eventHandlers.py @@ -1,13 +1,52 @@ -import discord, traceback, discord_slash, sys -from discord.ext import commands +""" +Classes used to handle bot events and errors. + +*Classes* +--------- + EventHandler + ErrorHandler +""" +import discord # Used to init discord.Game and discord.Status, as well +# as compare errors to discord errors and as typehints +import traceback # Used to get the traceback of errors +import sys # Used to get traceback when the specific error is not +# available +from discord.ext import commands # Used to compare errors with command +# errors + +from discord_slash.context import SlashContext +from utils.utilFunctions import emojiToCommand -from .utilFunctions import emojiToCommand class EventHandler(): + """ + Handles bot events. + + *Methods* + --------- + on_ready() + on_slash_command(ctx: discord_slash.context.SlashContext) + on_reaction_add(ctx: discord_slash.context.SlashContext) + """ + def __init__(self, bot): + """Initialize the handler.""" self.bot = bot - async def on_slash_command(self, ctx): + async def on_ready(self): + """Log and sets status when it logs in.""" + await self.bot.databaseFuncs.syncCommands() + name = self.bot.user.name + userid = str(self.bot.user.id) + loggedInMessage = f"Logged in as {name}, {userid}" + self.bot.log(loggedInMessage, level=25) + game = discord.Game("Use /help for commands") + + onlineStatus = discord.Status.online + await self.bot.change_presence(activity=game, status=onlineStatus) + + async def on_slash_command(self, ctx: SlashContext): + """Log when a slash command is given.""" if ctx.subcommand_name is not None: subcommand = f" {ctx.subcommand_name} " else: @@ -21,100 +60,117 @@ class EventHandler(): args = " ".join([str(i) for i in ctx.args]) fullCommand = f"/{ctx.command}{subcommand}{subcommandGroup}{args}" logMessage = f"{ctx.author.display_name} ran {fullCommand}" - self.bot.log(logMessage, str(ctx.channel_id), level = 25) + 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 : discord.Reaction, user): - if user.bot == False: + async def on_reaction_add(self, reaction: discord.Reaction, + user: discord.User): + """Take action if the reaction is on a command message.""" + if not user.bot: + tests = self.bot.databaseFuncs 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 + reactedMessage = f"{user.display_name} reacted to a message" + self.bot.log(reactedMessage, str(channel.id)) + plexData = tests.bedreNetflixReactionTest(message) + # plexData is a list containing 3 elements: whether it was + # the addshow/addmovie command message the reaction was to + # (bool), whether it's a movie (bool) (if false, it's a + # show), and the imdb ids/names for the for the movies or + # shows listed in the message (list). - bedreNetflixMessage, addMovie, imdbIds = self.bot.databaseFuncs.bedreNetflixReactionTest(channel, message) + reactionTestParams = [message, f"#{str(user.id)}"] - if connectFourTheirTurn: + if tests.connectFourReactionTest(*reactionTestParams): column = emojiToCommand(reaction.emoji) - await self.bot.games.connectFour.placePiece(message, f"#{user.id}", column-1) - elif bedreNetflixMessage and addMovie: - moviePick = emojiToCommand(reaction.emoji) - if moviePick == "none": - imdbID = None - else: - imdbID = imdbIds[moviePick-1] + params = [message, f"#{user.id}", column-1] + await self.bot.games.connectFour.placePiece(*params) - if isinstance(channel, discord.DMChannel): - await message.delete() - await self.bot.other.bedreNetflix.addMovie(message, imdbID, False) - else: - await message.clear_reactions() - await self.bot.other.bedreNetflix.addMovie(message, imdbID) - elif bedreNetflixMessage and not addMovie: - showPick = emojiToCommand(reaction.emoji) - if showPick == "none": - imdbName = None - else: - imdbName = imdbIds[showPick-1] + if plexData[0]: + plexFuncs = self.bot.other.bedreNetflix + if plexData[1]: + moviePick = emojiToCommand(reaction.emoji) + if moviePick == "none": + imdbID = None + else: + imdbID = plexData[2][moviePick-1] - if isinstance(channel, discord.DMChannel): - await message.delete() - await self.bot.other.bedreNetflix.addShow(message, imdbName, False) + if isinstance(channel, discord.DMChannel): + await message.delete() + await plexFuncs.addMovie(message, imdbID, False) + else: + await message.clear_reactions() + await plexFuncs.addMovie(message, imdbID) else: - await message.clear_reactions() - await self.bot.other.bedreNetflix.addShow(message, imdbName) + showPick = emojiToCommand(reaction.emoji) + if showPick == "none": + imdbName = None + else: + imdbName = plexData[2][showPick-1] - elif self.bot.databaseFuncs.hangmanReactionTest(channel, message, f"#{user.id}"): + if isinstance(channel, discord.DMChannel): + await message.delete() + await plexFuncs.addShow(message, imdbName, False) + else: + await message.clear_reactions() + await plexFuncs.addShow(message, imdbName) + + elif tests.hangmanReactionTest(*reactionTestParams): self.bot.log("They reacted to the hangman message") - if ord(reaction.emoji) in range(127462,127488): + if ord(reaction.emoji) in range(127462, 127488): + # The range is letter-emojis guess = chr(ord(reaction.emoji)-127397) - await self.bot.games.hangman.guess(message, f"#{user.id}", guess) + # Converts emoji to letter + params = [message, f"#{user.id}", guess] + await self.bot.games.hangman.guess(*params) else: self.bot.log("Bot they didn't react with a valid guess") + class ErrorHandler(): + """ + Handles errors. + + *Methods* + --------- + on_slash_command_error(ctx: discord_slash.context.SlashContext, + error: Exception) + on_error(method: str) + """ + def __init__(self, bot): + """Initialize the handler.""" self.bot = bot - async def on_slash_command_error(self, ctx, error): + async def on_slash_command_error(self, ctx: SlashContext, + error: Exception): + """Log when there's a slash command.""" if isinstance(error, commands.CommandNotFound): - await ctx.send("That's not a command (error code 001)") + await ctx.send("That's not a command") 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.") + self.bot.log(f"{error}", str(ctx.channel_id)) + await ctx.send(self.bot.longStrings["missing parameters"]) 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] + params = [type(error), error, error.__traceback__] + exception = traceback.format_exception(*params) exceptionString = "".join(exception) - self.bot.log([f"exception in /{ctx.name}", f"{exceptionString}"],str(ctx.channel_id), 40) + logMessages = [f"exception in /{ctx.name}", f"{exceptionString}"] + self.bot.log(logMessages, str(ctx.channel_id), 40) if isinstance(error, discord.errors.NotFound): - self.bot.log("Context is non-existant", level = 40) + 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): + async def on_error(self, method: str): + """Log when there's an error.""" 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 + logMessages = [f"exception in {method}", f"{exceptionString}"] + self.bot.log(logMessages, level=40) diff --git a/utils/helperClasses.py b/utils/helperClasses.py index 53894f3..84b1019 100644 --- a/utils/helperClasses.py +++ b/utils/helperClasses.py @@ -1,6 +1,44 @@ -import re, git, os, json, time +""" +Contains classes used for utilities. -def sanitize(data : str, options : bool = False): +*Functions* +----------- + Sanitize(data: str, lowerCaseValue: bool = false) -> dict + +*Classes* +--------- + Options() + Credentials() + DatabaseFuncs() +""" +import re # Used in getID +import git # Used to pull when stopping +import os # Used to test if files exist +import json # Used to read the data about addmovie/addshow +import time # Used to test how long it's been since commands were synced +import discord # Used for type hints + + +def sanitize(data: str, lowerCaseValue: bool = False): + """ + Sanitize and create a dictionary from a string. + + Each element is created from a line with a : in it. The key is left + of the :, the value is right of it. + + *Parameters* + ------------ + data: str + The string to create a dict from. + lowerCaseValue: bool = False + Whether the value of each element should be lowercase. + + *Returns* + --------- + dct: dict + The sanitized dictionary of elements. + + """ data = data.splitlines() dct = {} for line in data: @@ -8,7 +46,7 @@ def sanitize(data : str, options : bool = False): lineValues = line.split(":") lineValues[0] = lineValues[0].lower() lineValues[1] = lineValues[1].replace(" ", "") - if options: + if lowerCaseValue: lineValues[1] = lineValues[1].lower() if lineValues[0] in ["testing guild ids", "admins"]: @@ -23,18 +61,26 @@ def sanitize(data : str, options : bool = False): return dct + class Options(): + """Contains the options for the bot.""" + def __init__(self): - with open("options.txt","r") as f: + """Initialize the options.""" + with open("options.txt", "r") as f: data = sanitize(f.read(), True) self.testing = data["testing"] self.guildIds = data["testing guild ids"] self.admins = data["admins"] + class Credentials(): + """Contains the credentials for the bot and apis.""" + def __init__(self): - with open("credentials.txt","r") as f: + """Initialize the credentials.""" + with open("credentials.txt", "r") as f: data = sanitize(f.read()) self.token = data["bot token"] @@ -46,34 +92,99 @@ class Credentials(): self.radarrKey = data["radarr api key"] self.sonarrKey = data["sonarr api key"] + class databaseFuncs(): + """ + Manages database functions. + + *Methods* + --------- + getName(userID: str) -> str + getID(userName: str) -> str + deleteGame(gameType: str, channel: str) + wipeGames() + connectFourReactionTest(message: discord.Message, + user: discord.User) -> bool + hangmanReactionTest(message: discord.Message, + user: discord.User) -> bool + BedreNetflixReactionTest(message: discord.Message, + user: discord.User) -> bool, bool, + list + syncCommands() + """ + def __init__(self, bot): + """Initialize the class.""" self.bot = bot - def getName(self, userID): - user = self.bot.database["users"].find_one({"_id":userID}) + def getName(self, userID: str): + """ + Get the name of a user you have the # id of. + + *Parameters: + ------------ + userID: str + The id of the user you want the name of. The format is + "#" + str(discord.User.id) + + *Returns* + --------- + userName: str + The name of the user. If the user couldn't be found, + returns the userID. + """ + user = self.bot.database["users"].find_one({"_id": userID}) if userID == f"#{self.bot.user.id}": return "Gwendolyn" - elif user != None: + elif user is not None: return user["user name"] else: self.bot.log(f"Couldn't find user {userID}") return userID - def getID(self,userName): - user = self.bot.database["users"].find_one({"user name":re.compile(userName, re.IGNORECASE)}) + def getID(self, userName: str): + """ + Get the id of a user you have the username of. - if user != None: + *Parameters: + ------------ + userName: str + The name of the user you want the id of. + + *Returns* + --------- + userID: str + The id of the user in the format "#" + + str(discord.User.id). If the user couldn't be found, + returns the userName. + """ + userSearch = {"user name": re.compile(userName, re.IGNORECASE)} + user = self.bot.database["users"].find_one(userSearch) + + if user is not None: return user["_id"] else: self.bot.log("Couldn't find user "+userName) return None - def deleteGame(self, gameType, channel): - self.bot.database[gameType].delete_one({"_id":channel}) + def deleteGame(self, gameType: str, channel: str): + """ + Remove a game from the database. - def stopServer(self): + *Parameters* + ------------ + gameType: str + The name of the collection the game is in, like + "hangman games", "blackjack games" etc. + channel: str + The channel id of the channel the game is on as a + string. + """ + self.bot.database[gameType].delete_one({"_id": channel}) + + def wipeGames(self): + """Delete all running games and pull from git.""" self.bot.database["trivia questions"].delete_many({}) self.bot.database["blackjack games"].delete_many({}) self.bot.database["connect 4 games"].delete_many({}) @@ -84,35 +195,80 @@ class databaseFuncs(): g = git.cmd.Git("") g.pull() - def connectFourReactionTest(self,channel,message,user): - game = self.bot.database["connect 4 games"].find_one({"_id":str(channel.id)}) + def connectFourReactionTest(self, message: discord.Message, + user: discord.User): + """ + Test if the given message is the current connect four game. - with open("resources/games/oldImages/connectFour"+str(channel.id), "r") as f: + Also tests if the given user is the one who's turn it is. + + *Parameters* + ------------ + message: discord.Message + The message to test. + user: discord.User + The user to test. + *Returns* + --------- + : bool + Whether the given message is the current connect four + game and if the user who reacted is the user who's turn + it is. + """ + channel = message.channel + channelSearch = {"_id": str(channel.id)} + game = self.bot.database["connect 4 games"].find_one(channelSearch) + + filePath = f"resources/games/oldImages/connectFour{channel.id}" + with open(filePath, "r") as f: oldImage = int(f.read()) if message.id == oldImage: self.bot.log("They reacted to the connectFour game") turn = game["turn"] if user == game["players"][turn]: - return True, turn+1 + return True else: self.bot.log("It wasn't their turn") - return False, 0 + return False else: - return False, 0 + return False - def hangmanReactionTest(self, channel, message, user): - try: - with open("resources/games/oldImages/hangman"+str(channel.id), "r") as f: + def hangmanReactionTest(self, message: discord.Message, + user: discord.User): + """ + Test if the given message is the current hangman game. + + Also tests if the given user is the one who's playing hangman. + + *Parameters* + ------------ + message: discord.Message + The message to test. + user: discord.User + The user to test. + *Returns* + --------- + : bool + Whether the given message is the current hangman game + and if the user who reacted is the user who's playing + hangman. + """ + channel = message.channel + filePath = f"resources/games/oldImages/hangman{channel.id}" + if os.path.isfile(filePath): + with open(filePath, "r") as f: oldMessages = f.read().splitlines() - except: + else: return False gameMessage = False for oldMessage in oldMessages: oldMessageID = int(oldMessage) if message.id == oldMessageID: - game = self.bot.database["hangman games"].find_one({"_id":str(channel.id)}) + database = self.bot.database["hangman games"] + channelSearch = {"_id": str(channel.id)} + game = database.find_one(channelSearch) if user == game["player"]: gameMessage = True @@ -120,9 +276,30 @@ class databaseFuncs(): return gameMessage - def bedreNetflixReactionTest(self, channel, message): - if os.path.isfile(f"resources/bedreNetflix/oldMessage{str(channel.id)}"): - with open("resources/bedreNetflix/oldMessage"+str(channel.id),"r") as f: + def bedreNetflixReactionTest(self, message: discord.Message): + """ + Test if the given message is the response to a plex request. + + *Parameters* + ------------ + message: discord.Message + The message to test. + + *Returns* + --------- + : bool + Whether the message is the response to a plex request. + : bool + Whether it was a movie request (false for a show + request) + : list + A list of ids or names of the shows or movies that + Gwendolyn presented after the request. + """ + channel = message.channel + filePath = f"resources/bedreNetflix/oldMessage{str(channel.id)}" + if os.path.isfile(filePath): + with open(filePath, "r") as f: data = json.load(f) else: return False, None, None @@ -136,6 +313,7 @@ class databaseFuncs(): return False, None, None async def syncCommands(self): + """Sync the slash commands with the discord API.""" collection = self.bot.database["last synced"] lastSynced = collection.find_one() now = time.time() @@ -144,6 +322,6 @@ class databaseFuncs(): self.bot.log(f"Updating commands: {slashCommandList}") await self.bot.slash.sync_all_commands() idNumber = lastSynced["_id"] - queryFilter = {"_id" : idNumber} - update = {"$set" : {"last synced" : now}} + queryFilter = {"_id": idNumber} + update = {"$set": {"last synced": now}} collection.update_one(queryFilter, update) diff --git a/utils/utilFunctions.py b/utils/utilFunctions.py index 92c655a..6fea627 100644 --- a/utils/utilFunctions.py +++ b/utils/utilFunctions.py @@ -1,3 +1,18 @@ +""" +Contains utility functions used by parts of the bot. + +*Functions* +----------- + longstrings() -> dict + getParams() -> dict + logThis(messages: Union[str, list], channel: str = "", + level: int = 20) + cap(s: str) -> str + makeFiles() + replaceMultiple(mainString: str, toBeReplaced: list, + newString: str) -> str + emojiToCommand(emoji: str) -> str +""" import json import logging import os @@ -5,22 +20,55 @@ import sys import imdb from .helperClasses import Options + +# All of this is logging configuration 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(format=FORMAT, datefmt=DATEFORMAT, level=logging.INFO, filename="gwendolyn.log") +loggingConfigParams = { + "format": FORMAT, + "datefmt": DATEFORMAT, + "level": logging.INFO, + "filename": "gwendolyn.log" +} +logging.basicConfig(**loggingConfigParams) logger = logging.getLogger("Gwendolyn") printer = logging.getLogger("printer") handler = logging.StreamHandler(sys.stdout) -handler.setFormatter(logging.Formatter(fmt = PRINTFORMAT, datefmt=DATEFORMAT)) +handler.setFormatter(logging.Formatter(fmt=PRINTFORMAT, datefmt=DATEFORMAT)) printer.addHandler(handler) printer.propagate = False -imdb._logging.setLevel("CRITICAL") +imdb._logging.setLevel("CRITICAL") # Basically disables imdbpy +# logging, since it's printed to the terminal. + + +def longStrings(): + """ + Get the data from resources/longStrings.json. + + *Returns* + --------- + data: dict + The long strings and their keys. + """ + with open("resources/longStrings.json", "r") as f: + data = json.load(f) + + return data + def getParams(): + """ + Get the slash command parameters. + + *Returns* + --------- + params: dict + The parameters for every slash command. + """ with open("resources/slashParameters.json", "r") as f: params = json.load(f) @@ -32,8 +80,25 @@ def getParams(): return params -def logThis(messages, channel : str = "", level : int = 20): - channel = channel.replace("Direct Message with ","") + +def logThis(messages, channel: str = "", level: int = 20): + """ + Log something in Gwendolyn's logs. + + *Parameters* + ------------ + messages: Union[str, list] + A string or list of strings to be logged. If there are + multiple strings and the level is PRINT (25) or higher, + only the first string will be printed. + channel: str = "" + The channel the event to be logged occurred in. Will be + logged along with the message(s). + level: int = 20 + The level to log the message(s) at. If PRINT (25) or + higher, the first message will be printed to the console. + """ + channel = channel.replace("Direct Message with ", "") if type(messages) is str: messages = [messages] @@ -41,9 +106,11 @@ def logThis(messages, channel : str = "", level : int = 20): for x, msg in enumerate(messages): if channel != "": - messages[x] = f"{msg} - ({channel})" + messages[x] = f"{msg} - ({channel})" # Adds channel to log + # messages - if len(messages) > 1: + if len(messages) > 1: # Tells user to check the log if there are + # more messages there printMessage += " (details in log)" if level >= 25: @@ -52,10 +119,24 @@ def logThis(messages, channel : str = "", level : int = 20): for logMessage in messages: logger.log(level, logMessage) -# Capitalizes all words except some of them -def cap(s): - no_caps_list = ["of","the"] - # Capitalizes a strink like a movie title + +def cap(s: str): + """ + Capitalize a string like a movie title. + + That means "of" and "the" are not capitalized. + + *Parameters* + ------------ + s: str + The string to capitalized. + + *Returns* + --------- + res: str + The capitalized string. + """ + no_caps_list = ["of", "the"] word_number = 0 lst = s.split() res = '' @@ -67,22 +148,25 @@ def cap(s): res = res[:-1] return res -def makeFiles(): - def makeJsonFile(path,content): - # Creates json file if it doesn't exist - if not os.path.isfile(path): - logThis(path.split("/")[-1]+" didn't exist. Making it now.") - with open(path,"w") as f: - json.dump(content,f,indent = 4) - def makeTxtFile(path,content): - # Creates txt file if it doesn't exist +def makeFiles(): + """Create all the files and directories needed by Gwendolyn.""" + def makeJsonFile(path, content): + """Create json file if it doesn't exist.""" if not os.path.isfile(path): logThis(path.split("/")[-1]+" didn't exist. Making it now.") - with open(path,"w") as f: + with open(path, "w") as f: + json.dump(content, f, indent=4) + + def makeTxtFile(path, content): + """Create txt file if it doesn't exist.""" + if not os.path.isfile(path): + logThis(path.split("/")[-1]+" didn't exist. Making it now.") + with open(path, "w") as f: f.write(content) def directory(path): + """Create directory if it doesn't exist.""" if not os.path.isdir(path): os.makedirs(path) logThis("The "+path.split("/")[-1]+" directory didn't exist") @@ -91,26 +175,57 @@ def makeFiles(): data = json.load(f) for path, content in data["json"].items(): - makeJsonFile(path,content) + makeJsonFile(path, content) for path, content in data["txt"].items(): - makeTxtFile(path,content) + makeTxtFile(path, content) for path in data["folder"]: directory(path) -# Replaces multiple things with the same thing -def replaceMultiple(mainString, toBeReplaces, newString): + +def replaceMultiple(mainString: str, toBeReplaced: list, newString: str): + """ + Replace multiple substrings in a string with the same substring. + + *Parameters* + ------------ + mainString: str + The string to replace substrings in. + toBeReplaced: list + The substrings to replace. + newString: str + The string to replace the substrings with. + + *Returns* + --------- + mainString: str + The string with the substrings replaced. + """ # Iterate over the strings to be replaced - for elem in toBeReplaces : + for elem in toBeReplaced: # Check if string is in the main string - if elem in mainString : + if elem in mainString: # Replace the string mainString = mainString.replace(elem, newString) return mainString -def emojiToCommand(emoji): + +def emojiToCommand(emoji: str): + """ + Convert emoji to text. + + *Parameters* + ------------ + emoji: str + The emoji to decipher. + + *Returns* + --------- + : str + The deciphered string. + """ if emoji == "1️⃣": return 1 elif emoji == "2️⃣": @@ -131,4 +246,5 @@ def emojiToCommand(emoji): return "none" elif emoji == "✔️": return 1 - else: return "" + else: + return "" From de127a179b5cf0f92197462d0b932183c1748eb5 Mon Sep 17 00:00:00 2001 From: NikolajDanger Date: Sat, 17 Apr 2021 20:59:47 +0200 Subject: [PATCH 02/23] :black_joker: Blackjack --- cogs/GameCogs.py | 2 +- funcs/__init__.py | 2 +- funcs/games/blackjack.py | 1489 ++++++++++++++++++++++++------------ requirements.txt | 6 +- resources/longStrings.json | 10 +- utils/utilFunctions.py | 12 +- 6 files changed, 1016 insertions(+), 505 deletions(-) diff --git a/cogs/GameCogs.py b/cogs/GameCogs.py index c813e24..ebd1f54 100644 --- a/cogs/GameCogs.py +++ b/cogs/GameCogs.py @@ -50,7 +50,7 @@ class BlackjackCog(commands.Cog): @cog_ext.cog_subcommand(**params["blackjackBet"]) async def blackjackBet(self, ctx, bet): """Enter the game of blackjack with a bet.""" - await self.bot.games.blackjack.playerDrawHand(ctx, bet) + await self.bot.games.blackjack.enterGame(ctx, bet) @cog_ext.cog_subcommand(**params["blackjackStand"]) async def blackjackStand(self, ctx, hand=""): diff --git a/funcs/__init__.py b/funcs/__init__.py index 729f445..597efd8 100644 --- a/funcs/__init__.py +++ b/funcs/__init__.py @@ -1,6 +1,6 @@ """A collection of all Gwendolyn functions.""" -__all__ = ["Games" , "Money", "LookupFuncs", "StarWars"] +__all__ = ["Games", "Money", "LookupFuncs", "StarWars"] from .games import Money, Games diff --git a/funcs/games/blackjack.py b/funcs/games/blackjack.py index 1d68b0d..075e33d 100644 --- a/funcs/games/blackjack.py +++ b/funcs/games/blackjack.py @@ -1,47 +1,109 @@ -import random -import math -import datetime -import asyncio -import discord +""" +Runs commands, game logic and imaging for blackjack games. + +*Classes* +--------- + Blackjack + Contains the blackjack game logic. + DrawBlackjack + Draws images of the blackjack table. +""" +import random # Used to shuffle the blackjack cards +import math # Used for flooring decimal numbers +import datetime # Used to generate the game id +import asyncio # Used for sleeping +import discord # Used for discord.file +import discord_slash # Used for typehints from PIL import Image, ImageDraw, ImageFont from shutil import copyfile from utils import replaceMultiple + class Blackjack(): - def __init__(self,bot): + """ + Deals with blackjack commands and gameplay logic. + + *Methods* + --------- + hit(ctx: discord_slash.context.SlashContext, + handNumber: int = 0) + double(ctx: discord_slash.context.SlashContext, + handNumber: int = 0) + stand(ctx: discord_slash.context.SlashContext, + handNumber: int = 0) + split(ctx: discord_slash.context.SlashContext, + handNumber: int = 0) + enterGame(ctx: discord_slash.context.SlashContext, bet: int) + start(ctx: discord_slash.context.SlashContext) + hilo(ctx: discord_slash.context.SlashContext) + shuffle(ctx: discord_slash.context.SlashContext) + cards(ctx: discord_slash.context.SlashContext) + """ + + def __init__(self, bot): + """Initialize the class.""" self.bot = bot self.draw = DrawBlackjack(bot) + self.decks = 4 - # Shuffles the blackjack cards - def blackjackShuffle(self, decks, channel): + def _blackjackShuffle(self, channel: str): + """ + Shuffle an amount of decks equal to self.decks. + + The shuffled cards are placed in the database as the cards that + are used by other blackjack functions. + + *Parameters* + ------------ + channel: str + The id of the channel where the cards will be used. + """ 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 + allDecks = deck.split("\n") * self.decks random.shuffle(allDecks) - self.bot.database["blackjack cards"].update_one({"_id":channel},{"$set":{"_id":channel,"cards":allDecks}},upsert=True) + blackjackCards = self.bot.database["blackjack cards"] + cards = {"_id": channel} + cardUpdater = {"$set": {"_id": channel, "cards": allDecks}} + blackjackCards.update_one(cards, cardUpdater, upsert=True) # Creates hilo file - self.bot.log("creating hilo doc for "+channel) + self.bot.log(f"creating hilo doc for {channel}") data = 0 - self.bot.database["hilo"].update_one({"_id":channel},{"$set":{"_id":channel,"hilo":data}},upsert=True) + blackjackHilo = self.bot.database["hilo"] + hiloUpdater = {"$set": {"_id": channel, "hilo": data}} + blackjackHilo.update_one({"_id": channel}, hiloUpdater, upsert=True) - return + def _calcHandValue(self, hand: list): + """ + Calculate the value of a blackjack hand. - # Calculates the value of a blackjack hand - def calcHandValue(self, hand : list): - self.bot.log("Calculating hand value") + *Parameters* + ------------ + hand: list + The hand to calculate the value of. Each element in the + list is a card represented as a string in the format of + "xy" where x is the number of the card (0, k, q, and j + for 10s, kings, queens and jacks) and y is the suit (d, + c, h or s). + + *Returns* + --------- + handValue: int + The blackjack value of the hand. + """ values = [] values.append(0) for card in hand: cardValue = card[0] - cardValue = replaceMultiple(cardValue,["0","k","q","j"],"10") + cardValue = replaceMultiple(cardValue, ["0", "k", "q", "j"], "10") if cardValue == "a": length = len(values) for x in range(length): @@ -58,102 +120,180 @@ class Blackjack(): if value <= 21: handValue = value - self.bot.log("Calculated "+str(hand)+" to be "+str(handValue)) + self.bot.log(f"Calculated the value of {hand} to be {handValue}") return handValue - # Draws a card from the deck - def drawCard(self, channel): + def _drawCard(self, channel: str): + """ + Draw a card from the stack. + + *Parameters* + ------------ + channel: str + The id of the channel the card is drawn in. + """ self.bot.log("drawing a card") - drawnCard = self.bot.database["blackjack cards"].find_one({"_id":channel})["cards"][0] - self.bot.database["blackjack cards"].update_one({"_id":channel},{"$pop":{"cards":-1}}) - value = self.calcHandValue([drawnCard]) + blackjackCards = self.bot.database["blackjack cards"] + drawnCard = blackjackCards.find_one({"_id": channel})["cards"][0] + blackjackCards.update_one({"_id": channel}, {"$pop": {"cards": -1}}) + value = self._calcHandValue([drawnCard]) + + blackjackHilo = self.bot.database["hilo"] if value <= 6: - self.bot.database["hilo"].update_one({"_id":channel},{"$inc":{"hilo":1}}) + blackjackHilo.update_one({"_id": channel}, {"$inc": {"hilo": 1}}) elif value >= 10: - self.bot.database["hilo"].update_one({"_id":channel},{"$inc":{"hilo":-1}}) + blackjackHilo.update_one({"_id": channel}, {"$inc": {"hilo": -1}}) return drawnCard - # Dealer draws a card and checks if they should draw another one - def dealerDraw(self,channel): - game = self.bot.database["blackjack games"].find_one({"_id":channel}) + def _dealerDraw(self, channel: str): + """ + Draw a card for the dealer. + + *Parameters* + ------------ + channel: str + The id of the channel to draw a card for the dealer in. + + *Returns* + --------- + done: bool + Whether the dealer is done drawing cards. + """ + game = self.bot.database["blackjack games"].find_one({"_id": channel}) done = False dealerHand = game["dealer hand"] - if self.calcHandValue(dealerHand) < 17: - dealerHand.append(self.drawCard(channel)) - self.bot.database["blackjack games"].update_one({"_id":channel},{"$set":{"dealer hand":dealerHand}}) + blackjackGames = self.bot.database["blackjack games"] + + if self._calcHandValue(dealerHand) < 17: + dealerHand.append(self._drawCard(channel)) + dealerUpdater = {"$set": {"dealer hand": dealerHand}} + blackjackGames.update_one({"_id": channel}, dealerUpdater) else: done = True - if self.calcHandValue(dealerHand) > 21: - self.bot.database["blackjack games"].update_one({"_id":channel},{"$set":{"dealer busted":True}}) + if self._calcHandValue(dealerHand) > 21: + dealerUpdater = {"$set": {"dealer busted": True}} + blackjackGames.update_one({"_id": channel}, dealerUpdater) return done - # Goes to the next round and calculates some stuff - def blackjackContinue(self, channel): + def _blackjackContinue(self, channel: str): + """ + Continues the blackjack game to the next round. + + *Parameters* + ------------ + channel: str + The id of the channel the blackjack game is in + + *Returns* + --------- + sendMessage: str + The message to send to the channel. + allStanding: bool + If all players are standing. + gameDone: bool + If the game has finished. + """ self.bot.log("Continuing blackjack game") - game = self.bot.database["blackjack games"].find_one({"_id":channel}) + game = self.bot.database["blackjack games"].find_one({"_id": channel}) done = False - self.bot.database["blackjack games"].update_one({"_id":channel},{"$inc":{"round":1}}) + blackjackGames = self.bot.database["blackjack games"] + blackjackGames.update_one({"_id": channel}, {"$inc": {"round": 1}}) allStanding = True preAllStanding = True - message = "All players are standing. The dealer now shows his cards and draws." + message = self.bot.longStrings["Blackjack all players standing"] if game["all standing"]: self.bot.log("All are standing") - done = self.dealerDraw(channel) + done = self._dealerDraw(channel) message = "The dealer draws a card." - game = self.bot.database["blackjack games"].find_one({"_id":channel}) + blackjackGames.find_one({"_id": channel}) self.bot.log("Testing if all are standing") for user in game["user hands"]: - try: - newUser, allStanding, preAllStanding = self.testIfStanding(game["user hands"][user],allStanding,preAllStanding,True) - self.bot.database["blackjack games"].update_one({"_id":channel},{"$set":{"user hands."+user:newUser}}) - except: - self.bot.log("Error in testing if all are standing (error code 1331)") + userHand = game["user hands"][user] + testParams = [userHand, allStanding, preAllStanding, True] + standingTest = (self._testIfStanding(*testParams)) + newUser, allStanding, preAllStanding = standingTest + handUpdater = {"$set": {"user hands."+user: newUser}} + blackjackGames.update_one({"_id": channel}, handUpdater) if allStanding: - self.bot.database["blackjack games"].update_one({"_id":channel},{"$set":{"all standing":True}}) + gameUpdater = {"$set": {"all standing": True}} + blackjackGames.update_one({"_id": channel}, gameUpdater) - try: - self.draw.drawImage(channel) - except: - self.bot.log("Error drawing blackjack table (error code 1340)") + self.draw.drawImage(channel) if allStanding: - if done == False: + if not done: return message, True, done else: return "The dealer is done drawing cards", True, done elif preAllStanding: return "", True, done else: - if game["round"] == 1: - firstRoundMessage = ". You can also double down with \"/blackjack double\" or split with \"/blackjack split\"" + if game["round"] == 0: + firstRoundMsg = self.bot.longStrings["Blackjack first round"] else: - firstRoundMessage = "" - return "You have 2 minutes to either hit or stand with \"/blackjack hit\" or \"/blackjack stand\""+firstRoundMessage+". It's assumed you're standing if you don't make a choice.", False, done + firstRoundMsg = "" - def testIfStanding(self, hand,allStanding,preAllStanding,topLevel): - if hand["hit"] == False: + sendMessage = self.bot.longStrings["Blackjack commands"] + print(firstRoundMsg) + sendMessage = sendMessage.format(firstRoundMsg) + return sendMessage, False, done + + def _testIfStanding(self, hand: dict, allStanding: bool, + preAllStanding: bool, topLevel: bool): + """ + Test if a player is standing on all their hands. + + Also resets the hand if it's not standing + + *Parameters* + ------------ + hand: dict + The hand to test and reset. + allStanding: bool + Is set to True at the top level. If it's false, the + player is not standing on one of the previously tested + hands. + preAllStanding: bool + Is set to True at the top level. + topLevel: bool + If the input hand is _all_ if the player's hands. If + False, it's one of the hands resulting from a split. + + *Returns* + --------- + hand: dict + The reset hand. + allStanding: bool + If the player is standing on all their hands. + preAllStanding: bool + Is true if allStanding is True, or if a player has done + something equivalent to standing but still needs to see + the newly drawn card. + """ + # If the user has not hit, they are by definition standing. + if not hand["hit"]: hand["standing"] = True - if hand["standing"] == False: + if not hand["standing"]: allStanding = False - if self.calcHandValue(hand["hand"]) >= 21 or hand["doubled"]: + if self._calcHandValue(hand["hand"]) >= 21 or hand["doubled"]: hand["standing"] = True else: preAllStanding = False @@ -162,29 +302,344 @@ class Blackjack(): if topLevel: if hand["split"] >= 1: - hand["other hand"], allStanding, preAllStanding = self.testIfStanding(hand["other hand"],allStanding,preAllStanding,False) + testHand = hand["other hand"] + testParams = [testHand, allStanding, preAllStanding, False] + standingTest = (self._testIfStanding(*testParams)) + hand["other hand"], allStanding, preAllStanding = standingTest if hand["split"] >= 2: - hand["third hand"], allStanding, preAllStanding = self.testIfStanding(hand["third hand"],allStanding,preAllStanding,False) + testHand = hand["third hand"] + testParams = [testHand, allStanding, preAllStanding, False] + standingTest = (self._testIfStanding(*testParams)) + hand["third hand"], allStanding, preAllStanding = standingTest if hand["split"] >= 3: - hand["fourth hand"], allStanding, preAllStanding = self.testIfStanding(hand["fourth hand"],allStanding,preAllStanding,False) + testHand = hand["fourth hand"] + testParams = [testHand, allStanding, preAllStanding, False] + standingTest = (self._testIfStanding(*testParams)) + hand["fourth hand"], allStanding, preAllStanding = standingTest return hand, allStanding, preAllStanding - # When players try to hit - async def hit(self, ctx, handNumber = 0): + def _blackjackFinish(self, channel: str): + """ + Generate the winnings message after the blackjack game ends. + + *Parameters* + ------------ + channel: str + The id of the channel the game is in. + + *Returns* + --------- + finalWinnings: str + The winnings message. + """ + finalWinnings = "*Final Winnings:*\n" + + game = self.bot.database["blackjack games"].find_one({"_id": channel}) + + dealerValue = self._calcHandValue(game["dealer hand"]) + dealerBlackjack = game["dealer blackjack"] + dealerBusted = game["dealer busted"] + + for user in game["user hands"]: + _calcWinningsParams = [ + game["user hands"][user], + dealerValue, + True, + dealerBlackjack, + dealerBusted + ] + winningCalc = (self._calcWinnings(*_calcWinningsParams)) + winnings, netWinnings, reason = winningCalc + + userName = self.bot.databaseFuncs.getName(user) + + if winnings < 0: + if winnings == -1: + finalWinnings += f"{userName} lost 1 GwendoBuck {reason}\n" + else: + moneyLost = -1 * winnings + winningText = f"{userName} lost {moneyLost} GwendoBucks" + winningText += f" {reason}\n" + finalWinnings += winningText + else: + if winnings == 1: + finalWinnings += f"{userName} won 1 GwendoBuck {reason}\n" + else: + winningText = f"{userName} won {winnings} GwendoBucks" + winningText += f" {reason}\n" + finalWinnings += winningText + + self.bot.money.addMoney(user, netWinnings) + + self.bot.database["blackjack games"].delete_one({"_id": channel}) + + return finalWinnings + + def _calcWinnings(self, hand: dict, dealerValue: int, topLevel: bool, + dealerBlackjack: bool, dealerBusted: bool): + """ + Calculate how much a user has won/lost in the blackjack game. + + *Parameters* + ------------ + hand: dict + The hand to calculate the winnings of. + dealerValue: int + The dealer's hand value. + topLevel: bool + If the input hand is _all_ if the player's hands. If + False, it's one of the hands resulting from a split. + dealerBlackjack: bool + If the dealer has a blackjack. + dealerBusted: bool + If the dealer busted. + + *Returns* + --------- + winnings: int + How much the player has won/lost. + netWinnings: int + winnings minus the original bet. This is added to the + user's account, since the bet was removed from their + account when they placed the bet. + reason: str + The reason for why they won/lost. + """ + self.bot.log("Calculating winnings") + reason = "" + bet = hand["bet"] + winnings = -1 * bet + netWinnings = 0 + handValue = self._calcHandValue(hand["hand"]) + + if hand["blackjack"] and not dealerBlackjack: + reason += "(blackjack)" + winnings += math.floor(2.5 * bet) + netWinnings += math.floor(2.5 * bet) + elif dealerBlackjack: + reason += "(dealer blackjack)" + elif hand["busted"]: + reason += "(busted)" + else: + if dealerBusted: + reason = "(dealer busted)" + winnings += 2 * bet + netWinnings += 2 * bet + elif handValue > dealerValue: + winnings += 2 * bet + netWinnings += 2 * bet + reason = "(highest value)" + elif handValue == dealerValue: + reason = "(pushed)" + winnings += bet + netWinnings += bet + else: + reason = "(highest value)" + + if topLevel: + if hand["split"] >= 1: + _calcWinningsParams = [ + hand["other hand"], + dealerValue, + False, + dealerBlackjack, + dealerBusted + ] + winningsCalc = self._calcWinnings(*_calcWinningsParams) + winningsTemp, netWinningsTemp, reasonTemp = winningsCalc + winnings += winningsTemp + netWinnings += netWinningsTemp + reason += reasonTemp + if hand["split"] >= 2: + _calcWinningsParams = [ + hand["third hand"], + dealerValue, + False, + dealerBlackjack, + dealerBusted + ] + winningsCalc = self._calcWinnings(*_calcWinningsParams) + winningsTemp, netWinningsTemp, reasonTemp = winningsCalc + winnings += winningsTemp + netWinnings += netWinningsTemp + reason += reasonTemp + if hand["split"] >= 3: + _calcWinningsParams = [ + hand["fourth hand"], + dealerValue, + False, + dealerBlackjack, + dealerBusted + ] + winningsCalc = self._calcWinnings(*_calcWinningsParams) + winningsTemp, netWinningsTemp, reasonTemp = winningsCalc + winnings += winningsTemp + netWinnings += netWinningsTemp + reason += reasonTemp + + return winnings, netWinnings, reason + + def _getHandNumber(self, user: dict, handNumber: int): + """ + Get the hand with the given number. + + *Parameters* + ------------ + user: dict + The full hand dict of the user. + handNumber: int + The number of the hand to get. + + *Returns* + --------- + hand: dict + The hand. + handNumber: int + The same as handNumber, except if the user hasn't + split. If the user hasn't split, returns 0. + """ + hand = None + + if user["split"] == 0: + hand = user + handNumber = 0 + else: + if handNumber != 0: + if handNumber == 1: + hand = user + elif handNumber == 2: + hand = user["other hand"] + elif handNumber == 3: + hand = user["third hand"] + elif handNumber == 4: + hand = user["fourth hand"] + + return hand, handNumber + + def _isRoundDone(self, game: dict): + """ + Find out if the round is done. + + *Parameters* + ------------ + game: dict + The game to check. + + *Returns* + --------- + roundDone: bool + Whether the round is done. + """ + roundDone = True + + for person in game["user hands"].values(): + if (not person["hit"]) and (not person["standing"]): + roundDone = False + + if person["split"] > 0: + if not person["other hand"]["hit"]: + if not person["other hand"]["standing"]: + roundDone = False + + if person["split"] > 1: + if not person["third hand"]["hit"]: + if not person["third hand"]["standing"]: + roundDone = False + + if person["split"] > 2: + if not person["fourth hand"]["hit"]: + if not person["fourth hand"]["standing"]: + roundDone = False + + return roundDone + + async def _blackjackLoop(self, channel, gameRound: int, gameID: str): + """ + Run blackjack logic and continue if enough time passes. + + *Parameters* + ------------ + channel: guildChannel or DMChannel + The channel the game is happening in. + gameRound: int + The round to start. + gameID: str + The ID of the game. + """ + self.bot.log("Loop "+str(gameRound), str(channel.id)) + + oldImagePath = f"resources/games/oldImages/blackjack{channel.id}" + with open(oldImagePath, "r") as f: + oldImage = await channel.fetch_message(int(f.read())) + + continueData = (self._blackjackContinue(str(channel.id))) + new_message, allStanding, gamedone = continueData + if new_message != "": + self.bot.log(new_message, str(channel.id)) + await channel.send(new_message) + + if not gamedone: + await oldImage.delete() + tablesPath = "resources/games/blackjackTables/" + filePath = f"{tablesPath}blackjackTable{channel.id}.png" + oldImage = await channel.send(file=discord.File(filePath)) + with open(oldImagePath, "w") as f: + f.write(str(oldImage.id)) + + if allStanding: + await asyncio.sleep(5) + else: + await asyncio.sleep(120) + + blackjackGames = self.bot.database["blackjack games"] + game = blackjackGames.find_one({"_id": str(channel.id)}) + + if game is None: + rightRound = False + else: + realRound = game["round"] or -1 + realID = game["gameID"] or -1 + rightRound = gameRound == realRound and gameID == realID + + if rightRound: + if not gamedone: + logMessage = f"Loop {gameRound} calling self._blackjackLoop()" + self.bot.log(logMessage, str(channel.id)) + await self._blackjackLoop(channel, gameRound+1, gameID) + else: + new_message = self._blackjackFinish(str(channel.id)) + await channel.send(new_message) + else: + logMessage = f"Ending loop on round {gameRound}" + self.bot.log(logMessage, str(channel.id)) + + async def hit(self, ctx: discord_slash.context.SlashContext, + handNumber: int = 0): + """ + Hit on a hand. + + *Parameters* + ------------ + ctx: discord_slash.context.SlashContext + The context of the command. + handNumber: int = 0 + The number of the hand to hit. + """ await self.bot.defer(ctx) channel = str(ctx.channel_id) user = f"#{ctx.author.id}" roundDone = False - game = self.bot.database["blackjack games"].find_one({"_id":channel}) + blackjackGames = self.bot.database["blackjack games"] + game = blackjackGames.find_one({"_id": channel}) if user in game["user hands"]: userHands = game["user hands"][user] - hand, handNumber = self.getHandNumber(userHands, handNumber) + hand, handNumber = self._getHandNumber(userHands, handNumber) - if hand == None: + if hand is None: logMessage = "They didn't specify a hand" sendMessage = "You need to specify a hand" elif game["round"] <= 0: @@ -197,28 +652,27 @@ class Blackjack(): logMessage = "They're already standing" sendMessage = "You can't hit when you're standing" else: - hand["hand"].append(self.drawCard(channel)) + hand["hand"].append(self._drawCard(channel)) hand["hit"] = True - handValue = self.calcHandValue(hand["hand"]) + 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}}) + handPath = f"user hands.{user}.other hand" elif handNumber == 3: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".third hand":hand}}) + handPath = f"user hands.{user}.third hand" elif handNumber == 4: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".fourth hand":hand}}) + handPath = f"user hands.{user}.fourth hand" else: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user:hand}}) + handPath = f"user hands.{user}" - roundDone = self.isRoundDone(self.bot.database["blackjack games"].find_one({"_id":channel})) + gameUpdater = {"$set": {handPath: hand}} + blackjackGames.update_one({"_id": channel}, gameUpdater) + game = blackjackGames.find_one({"_id": channel}) + roundDone = self._isRoundDone(game) sendMessage = f"{ctx.author.display_name} hit" logMessage = "They succeeded" @@ -231,22 +685,34 @@ class Blackjack(): if roundDone: gameID = game["gameID"] - self.bot.log("Hit calling self.blackjackLoop()", channel) - await self.blackjackLoop(ctx.channel, game["round"]+1, gameID) + self.bot.log("Hit calling self._blackjackLoop()", channel) + await self._blackjackLoop(ctx.channel, game["round"]+1, gameID) - # When players try to double down - async def double(self, ctx, handNumber = 0): + async def double(self, ctx: discord_slash.context.SlashContext, + handNumber: int = 0): + """ + Double a hand. + + *Parameters* + ------------ + ctx: discord_slash.context.SlashContext + The context of the command. + handNumber: int = 0 + The number of the hand to double. + """ await self.bot.defer(ctx) channel = str(ctx.channel_id) user = f"#{ctx.author.id}" roundDone = False + blackjackGames = self.bot.database["blackjack games"] - game = self.bot.database["blackjack games"].find_one({"_id":channel}) + game = blackjackGames.find_one({"_id": channel}) if user in game["user hands"]: - hand, handNumber = self.getHandNumber(game["user hands"][user],handNumber) + handParams = [game["user hands"][user], handNumber] + hand, handNumber = self._getHandNumber(*handParams) - if hand == None: + if hand is None: logMessage = "They didn't specify a hand" sendMessage = "You need to specify a hand" elif game["round"] <= 0: @@ -261,42 +727,42 @@ class Blackjack(): elif len(hand["hand"]) != 2: logMessage = "They tried to double after round 1" sendMessage = "You can only double on the first round" + elif self.bot.money.checkBalance(user) < hand["bet"]: + logMessage = "They tried to double without being in the game" + sendMessage = "You can't double when you're not in the game" else: 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" + 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: + handPath = f"user hands.{user}.other hand" + elif handNumber == 3: + handPath = f"user hands.{user}.third hand" + elif handNumber == 4: + handPath = f"user hands.{user}.fourth hand" else: - self.bot.money.addMoney(user,-1 * bet) + handPath = f"user hands.{user}" - hand["hand"].append(self.drawCard(channel)) - hand["hit"] = True - hand["doubled"] = True - hand["bet"] += bet + gameUpdater = {"$set": {handPath: hand}} + blackjackGames.update_one({"_id": channel}, gameUpdater) - handValue = self.calcHandValue(hand["hand"]) + game = blackjackGames.find_one({"_id": channel}) + roundDone = self._isRoundDone(game) - - 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" + sendMessage = self.bot.longStrings["Blackjack double"] + userName = self.bot.databaseFuncs.getName(user) + sendMessage = sendMessage.format(bet, userName) + logMessage = "They succeeded" else: logMessage = "They tried to double without being in the game" sendMessage = "You can't double when you're not in the game" @@ -306,23 +772,34 @@ class Blackjack(): if roundDone: gameID = game["gameID"] - self.bot.log("Double calling self.blackjackLoop()", channel) - await self.blackjackLoop(ctx.channel, game["round"]+1, gameID) + self.bot.log("Double calling self._blackjackLoop()", channel) + await self._blackjackLoop(ctx.channel, game["round"]+1, gameID) - # When players try to stand - async def stand(self, ctx, handNumber = 0): + async def stand(self, ctx: discord_slash.context.SlashContext, + handNumber: int = 0): + """ + Stand on a hand. + + *Parameters* + ------------ + ctx: discord_slash.context.SlashContext + The context of the command. + handNumber: int = 0 + The number of the hand to stand on. + """ await self.bot.defer(ctx) channel = str(ctx.channel_id) user = f"#{ctx.author.id}" roundDone = False - game = self.bot.database["blackjack games"].find_one({"_id":channel}) + blackjackGames = self.bot.database["blackjack games"] + game = blackjackGames.find_one({"_id": channel}) - if user in game["user hands"]: + if game is not None and user in game["user hands"]: + handParams = [game["user hands"][user], handNumber] + hand, handNumber = self._getHandNumber(*handParams) - hand, handNumber = self.getHandNumber(game["user hands"][user],handNumber) - - if hand == None: + if hand is None: sendMessage = "You need to specify which hand" logMessage = "They didn't specify a hand" elif game["round"] <= 0: @@ -338,19 +815,18 @@ class Blackjack(): hand["standing"] = True if handNumber == 2: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".other hand":hand}}) + handPath = f"user hands.{user}.other hand" elif handNumber == 3: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".third hand":hand}}) + handPath = f"user hands.{user}.third hand" elif handNumber == 4: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".fourth hand":hand}}) + handPath = f"user hands.{user}.fourth hand" else: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user:hand}}) + handPath = f"user hands.{user}" - roundDone = self.isRoundDone(self.bot.database["blackjack games"].find_one({"_id":channel})) + gameUpdater = {"$set": {handPath: hand}} + blackjackGames.update_one({"_id": channel}, gameUpdater) + game = blackjackGames.find_one({"_id": channel}) + roundDone = self._isRoundDone(game) sendMessage = f"{ctx.author.display_name} is standing" logMessage = "They succeeded" @@ -364,18 +840,29 @@ class Blackjack(): if roundDone: gameID = game["gameID"] - self.bot.log("Stand calling self.blackjackLoop()", channel) - await self.blackjackLoop(ctx.channel, game["round"]+1, gameID) + self.bot.log("Stand calling self._blackjackLoop()", channel) + await self._blackjackLoop(ctx.channel, game["round"]+1, gameID) - # When players try to split - async def split(self, ctx, handNumber = 0): + async def split(self, ctx: discord_slash.context.SlashContext, + handNumber: int = 0): + """ + Split a hand. + + *Parameters* + ------------ + ctx: discord_slash.context.SlashContext + The context of the command. + handNumber: int = 0 + The number of the hand to split. + """ await self.bot.defer(ctx) channel = str(ctx.channel_id) user = f"#{ctx.author.id}" roundDone = False handNumberError = False - game = self.bot.database["blackjack games"].find_one({"_id":channel}) + blackjackGames = self.bot.database["blackjack games"] + game = blackjackGames.find_one({"_id": channel}) if game["user hands"][user]["split"] == 0: hand = game["user hands"][user] @@ -410,7 +897,7 @@ class Blackjack(): sendMessage = "You can only split 3 times" elif hand["hit"]: logMessage = "They've already hit" - sendMessage = "You've already hit" + sendMessage = "You've already hit or split this hand." elif hand["standing"]: logMessage = "They're already standing" sendMessage = "You're already standing" @@ -418,34 +905,37 @@ class Blackjack(): logMessage = "They tried to split after the first round" sendMessage = "You can only split on the first round" else: - firstCard = self.calcHandValue([hand["hand"][0]]) - secondCard = self.calcHandValue([hand["hand"][1]]) + 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" + sendMessage = self.bot.longStrings["Blackjack different cards"] 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) + 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} + "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)) + newHand["hand"].append(self._drawCard(channel)) + hand["hand"].append(self._drawCard(channel)) + hand["hit"] = True - handValue = self.calcHandValue(hand["hand"]) - otherHandValue = self.calcHandValue(newHand["hand"]) + handValue = self._calcHandValue(hand["hand"]) + otherHandValue = self._calcHandValue(newHand["hand"]) if handValue > 21: hand["busted"] = True elif handValue == 21: @@ -457,31 +947,34 @@ class Blackjack(): newHand["blackjack"] = True if handNumber == 2: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".other hand":hand}}) + handPath = f"user hands.{user}.other hand" elif handNumber == 3: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".third hand":hand}}) + handPath = f"user hands.{user}.third hand" else: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user:hand}}) + handPath = f"user hands.{user}" + + gameUpdater = {"$set": {handPath: hand}} + blackjackGames.update_one({"_id": channel}, gameUpdater) if otherHand == 3: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".third hand":newHand}}) + otherHandPath = f"user hands.{user}.third hand" elif otherHand == 4: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".fourth hand":newHand}}) + otherHandPath = f"user hands.{user}.fourth hand" else: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".other hand":newHand}}) + otherHandPath = f"user hands.{user}.other hand" - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$inc":{"user hands."+user+".split":1}}) + gameUpdater = {"$set": {otherHandPath: newHand}} + blackjackGames.update_one({"_id": channel}, gameUpdater) - roundDone = self.isRoundDone(self.bot.database["blackjack games"].find_one({"_id":channel})) + splitUpdater = {"$inc": {"user hands."+user+".split": 1}} + blackjackGames.update_one({"_id": channel}, splitUpdater) - 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." + game = blackjackGames.find_one({"_id": channel}) + roundDone = self._isRoundDone(game) + + sendMessage = self.bot.longStrings["Blackjack split"] + userName = self.bot.databaseFuncs.getName(user) + sendMessage = sendMessage.format(userName) logMessage = "They succeeded" await ctx.send(sendMessage) @@ -489,21 +982,31 @@ class Blackjack(): if roundDone: gameID = game["gameID"] - self.bot.log("Stand calling self.blackjackLoop()", channel) - await self.blackjackLoop(ctx.channel, game["round"]+1, 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 - async def playerDrawHand(self, ctx, bet : int): + async def enterGame(self, ctx: discord_slash.context.SlashContext, + bet: int): + """ + Enter the blackjack game. + + *Parameters* + ------------ + ctx: discord_slash.context.SlashContext + The context of the command. + bet: int + The bet to enter with. + """ await self.bot.defer(ctx) channel = str(ctx.channel_id) user = f"#{ctx.author.id}" collection = self.bot.database["blackjack games"] - game = collection.find_one({"_id":channel}) + game = collection.find_one({"_id": channel}) userName = self.bot.databaseFuncs.getName(user) self.bot.log(f"{userName} is trying to join the Blackjack game") - if game == None: + if game is None: sendMessage = "There is no game going on in this channel" logMessage = sendMessage elif user in game["user hands"]: @@ -522,78 +1025,97 @@ class Blackjack(): sendMessage = "You don't have enough GwendoBucks" logMessage = "They didn't have enough GwendoBucks" else: - self.bot.money.addMoney(user,-1 * bet) - playerHand = [self.drawCard(channel) for _ in range(2)] + self.bot.money.addMoney(user, -1 * bet) + playerHand = [self._drawCard(channel) for _ in range(2)] - handValue = self.calcHandValue(playerHand) + 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":{}} + 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) + 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) + self.bot.log(logMessage) + await ctx.send(sendMessage) - # Starts a game of blackjack - async def start(self, ctx): + async def start(self, ctx: discord_slash.context.SlashContext): + """ + Start a blackjack game. + + *Parameters* + ------------ + ctx: discord_slash.context.SlashContext + The context of the command. + """ await self.bot.defer(ctx) channel = str(ctx.channel_id) blackjackMinCards = 50 - blackjackDecks = 4 + self.bot.log("Starting blackjack game") await ctx.send("Starting a new game of blackjack") cardsLeft = 0 - cards = self.bot.database["blackjack cards"].find_one({"_id":channel}) - if cards != None: + cards = self.bot.database["blackjack cards"].find_one({"_id": channel}) + if cards is not None: cardsLeft = len(cards["cards"]) # Shuffles if not enough cards if cardsLeft < blackjackMinCards: - self.blackjackShuffle(blackjackDecks, channel) + self._blackjackShuffle(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}) + 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: + if game is None: - dealerHand = [self.drawCard(channel),self.drawCard(channel)] + dealerHand = [self._drawCard(channel), self._drawCard(channel)] gameID = datetime.datetime.now().strftime('%Y%m%d%H%M%S') - newGame = {"_id":channel,"dealer hand": dealerHand,"dealer busted":False,"dealer blackjack":False,"user hands": {},"all standing":False,"round":0,"gameID":gameID} + newGame = { + "_id": channel, "dealer hand": dealerHand, + "dealer busted": False, "dealer blackjack": False, + "user hands": {}, "all standing": False, "round": 0, + "gameID": gameID + } - if self.calcHandValue(dealerHand) == 21: + if self._calcHandValue(dealerHand) == 21: newGame["dealer blackjack"] = True self.bot.database["blackjack games"].insert_one(newGame) - copyfile("resources/games/blackjackTable.png","resources/games/blackjackTables/blackjackTable"+channel+".png") + tableImagesPath = "resources/games/blackjackTables/" + emptyTableImagePath = f"resources/games/blackjackTable.png" + newTableImagePath = f"{tableImagesPath}blackjackTable{channel}.png" + copyfile(emptyTableImagePath, newTableImagePath) gameStarted = True if gameStarted: - sendMessage = "Blackjack game started. Use \"/blackjack bet [amount]\" to enter the game within the next 30 seconds." + sendMessage = self.bot.longStrings["Blackjack started"] await ctx.channel.send(sendMessage) - filePath = f"resources/games/blackjackTables/blackjackTable{channel}.png" - oldImage = await ctx.channel.send(file = discord.File(filePath)) + tableImagesPath = "resources/games/blackjackTables/" + filePath = f"{tableImagesPath}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)) @@ -602,365 +1124,346 @@ class Blackjack(): gamedone = False - game = self.bot.database["blackjack games"].find_one({"_id":str(channel)}) + blackjackGames = self.bot.database["blackjack games"] + game = blackjackGames.find_one({"_id": channel}) if len(game["user hands"]) == 0: gamedone = True - await ctx.channel.send("No one entered the game. Ending the game.") + sendMessage = "No one entered the game. Ending the game." + await ctx.channel.send(sendMessage) + gameID = game["gameID"] # Loop of game rounds - if gamedone == False: - self.bot.log("start() calling blackjackLoop()", channel) - await self.blackjackLoop(ctx.channel,1,gameID) + if not gamedone: + self.bot.log("start() calling _blackjackLoop()", channel) + await self._blackjackLoop(ctx.channel, 1, gameID) else: - new_message = self.blackjackFinish(channel) + new_message = self._blackjackFinish(channel) await ctx.channel.send(new_message) else: - await ctx.channel.send("There's already a blackjack game going on. Try again in a few minutes.") + sendMessage = self.bot.longStrings["Blackjack going on"] + await ctx.channel.send(sendMessage) self.bot.log("There was already a game going on") - # Ends the game and calculates winnings - def blackjackFinish(self,channel): - finalWinnings = "*Final Winnings:*\n" + async def hilo(self, ctx: discord_slash.context.SlashContext): + """ + Get the hilo of the blackjack game. - game = self.bot.database["blackjack games"].find_one({"_id":channel}) - - dealerValue = self.calcHandValue(game["dealer hand"]) - dealerBlackjack = game["dealer blackjack"] - dealerBusted = game["dealer busted"] - - try: - for user in game["user hands"]: - - winnings, netWinnings, reason = self.calcWinnings(game["user hands"][user],dealerValue,True,dealerBlackjack,dealerBusted) - - if winnings < 0: - if winnings == -1: - finalWinnings += self.bot.databaseFuncs.getName(user)+" lost "+str(-1 * winnings)+" GwendoBuck "+reason+"\n" - else: - finalWinnings += self.bot.databaseFuncs.getName(user)+" lost "+str(-1 * winnings)+" GwendoBucks "+reason+"\n" - else: - if winnings == 1: - finalWinnings += self.bot.databaseFuncs.getName(user)+" won "+str(winnings)+" GwendoBuck "+reason+"\n" - else: - finalWinnings += self.bot.databaseFuncs.getName(user)+" won "+str(winnings)+" GwendoBucks "+reason+"\n" - - self.bot.money.addMoney(user,netWinnings) - - except: - self.bot.log("Error calculating winnings (error code 1311)") - - self.bot.database["blackjack games"].delete_one({"_id":channel}) - - return finalWinnings - - def calcWinnings(self,hand, dealerValue, topLevel, dealerBlackjack, dealerBusted): - self.bot.log("Calculating winnings") - reason = "" - bet = hand["bet"] - winnings = -1 * bet - netWinnings = 0 - handValue = self.calcHandValue(hand["hand"]) - - if hand["blackjack"] and dealerBlackjack == False: - reason += "(blackjack)" - winnings += math.floor(2.5 * bet) - netWinnings += math.floor(2.5 * bet) - elif dealerBlackjack: - reason += "(dealer blackjack)" - elif hand["busted"]: - reason += "(busted)" - else: - if dealerBusted: - reason = "(dealer busted)" - winnings += 2 * bet - netWinnings += 2 * bet - elif handValue > dealerValue: - winnings += 2 * bet - netWinnings += 2 * bet - reason = "(highest value)" - elif handValue == dealerValue: - reason = "(pushed)" - winnings += bet - netWinnings += bet - else: - reason = "(highest value)" - - if topLevel: - if hand["split"] >= 1: - winningsTemp, netWinningsTemp, reasonTemp = self.calcWinnings(hand["other hand"],dealerValue,False,dealerBlackjack,dealerBusted) - winnings += winningsTemp - netWinnings += netWinningsTemp - reason += reasonTemp - if hand["split"] >= 2: - winningsTemp, netWinningsTemp, reasonTemp = self.calcWinnings(hand["third hand"],dealerValue,False,dealerBlackjack,dealerBusted) - winnings += winningsTemp - netWinnings += netWinningsTemp - reason += reasonTemp - if hand["split"] >= 3: - winningsTemp, netWinningsTemp, reasonTemp = self.calcWinnings(hand["fourth hand"],dealerValue,False,dealerBlackjack,dealerBusted) - winnings += winningsTemp - netWinnings += netWinningsTemp - reason += reasonTemp - - return winnings, netWinnings, reason - - def getHandNumber(self, user,handNumber): - try: - hand = None - - if user["split"] == 0: - hand = user - handNumber = 0 - else: - if handNumber != 0: - if handNumber == 1: - hand = user - elif handNumber == 2: - hand = user["other hand"] - elif handNumber == 3: - hand = user["third hand"] - elif handNumber == 4: - hand = user["fourth hand"] - - return hand, handNumber - except: - self.bot.log("Problem with getHandNumber() (error code 1322)") - - def isRoundDone(self,game): - roundDone = True - - for person in game["user hands"].values(): - if person["hit"] == False and person["standing"] == False: - roundDone = False - - if person["split"] > 0: - if person["other hand"]["hit"] == False and person["other hand"]["standing"] == False: - roundDone = False - - if person["split"] > 1: - if person["third hand"]["hit"] == False and person["third hand"]["standing"] == False: - roundDone = False - - if person["split"] > 2: - if person["fourth hand"]["hit"] == False and person["fourth hand"]["standing"] == False: - roundDone = False - - return roundDone - - # Loop of blackjack game rounds - async def blackjackLoop(self,channel,gameRound,gameID): - self.bot.log("Loop "+str(gameRound),str(channel.id)) - - with open("resources/games/oldImages/blackjack"+str(channel.id), "r") as f: - oldImage = await channel.fetch_message(int(f.read())) - - new_message, allStanding, gamedone = self.blackjackContinue(str(channel.id)) - if new_message != "": - self.bot.log(new_message,str(channel.id)) - await channel.send(new_message) - if gamedone == False: - await oldImage.delete() - oldImage = await channel.send(file = discord.File("resources/games/blackjackTables/blackjackTable"+str(channel.id)+".png")) - with open("resources/games/oldImages/blackjack"+str(channel.id), "w") as f: - f.write(str(oldImage.id)) - - try: - if allStanding: - await asyncio.sleep(5) - else: - await asyncio.sleep(120) - except: - self.bot.log("Loop "+str(gameRound)+" interrupted (error code 1321)") - - game = self.bot.database["blackjack games"].find_one({"_id":str(channel.id)}) - - if game != None: - realRound = game["round"] - realGameID = game["gameID"] - - if gameRound == realRound and realGameID == gameID: - if gamedone == False: - self.bot.log("Loop "+str(gameRound)+" calling self.blackjackLoop()",str(channel.id)) - await self.blackjackLoop(channel,gameRound+1,gameID) - else: - try: - new_message = self.blackjackFinish(str(channel.id)) - except: - self.bot.log("Something fucked up (error code 1310)") - await channel.send(new_message) - else: - self.bot.log("Ending loop on round "+str(gameRound),str(channel.id)) - else: - self.bot.log("Ending loop on round "+str(gameRound),str(channel.id)) - - # Returning current hi-lo value - async def hilo(self, ctx): + *Parameters* + ------------ + ctx: discord_slash.context.SlashContext + The context of the command. + """ channel = ctx.channel_id - data = self.bot.database["hilo"].find_one({"_id":str(channel)}) - if data != None: + data = self.bot.database["hilo"].find_one({"_id": str(channel)}) + if data is not None: hilo = str(data["hilo"]) else: hilo = "0" await ctx.send(f"Hi-lo value: {hilo}", hidden=True) - # Shuffles the blackjack deck - async def shuffle(self, ctx): - blackjackDecks = 4 + async def shuffle(self, ctx: discord_slash.context.SlashContext): + """ + Shuffle the cards used for blackjack. + + *Parameters* + ------------ + ctx: discord_slash.context.SlashContext + The context of the command. + """ channel = ctx.channel_id - self.blackjackShuffle(blackjackDecks,str(channel)) - self.bot.log("Shuffling the blackjack deck...",str(channel)) + self._blackjackShuffle(str(channel)) + self.bot.log("Shuffling the blackjack deck...", str(channel)) await ctx.send("Shuffling the deck...") + async def cards(self, ctx: discord_slash.context.SlashContext): + """ + Get how many cards are left for blackjack. - # Tells you the amount of cards left - async def cards(self, ctx): + *Parameters* + ------------ + ctx: discord_slash.context.SlashContext + The context of the command. + """ channel = ctx.channel_id cardsLeft = 0 - cards = self.bot.database["blackjack cards"].find_one({"_id":str(channel)}) - if cards != None: + blackjackGames = self.bot.database["blackjack cards"] + cards = blackjackGames.find_one({"_id": str(channel)}) + if cards is not None: cardsLeft = len(cards["cards"]) - decksLeft = round(cardsLeft/52,1) - await ctx.send(f"Cards left:\n{cardsLeft} cards, {decksLeft} decks", hidden=True) + decksLeft = round(cardsLeft/52, 1) + sendMessage = f"Cards left:\n{cardsLeft} cards, {decksLeft} decks" + await ctx.send(sendMessage, hidden=True) + class DrawBlackjack(): - def __init__(self,bot): + """ + Draws the blackjack image. + + *Methods* + --------- + drawImage(channel: str) + + *Attributes* + ------------ + bot: Gwendolyn + The instance of the bot. + BORDER: int + The size of the border in pixels. + PLACEMENT: list + The order to place the user hands in. + """ + + def __init__(self, bot): + """Initialize the class.""" self.bot = bot self.BORDER = 100 - self.PLACEMENT = [0,0] - self.ROTATION = 0 + self.PLACEMENT = [2, 1, 3, 0, 4] - def drawImage(self,channel): - self.bot.log("Drawing blackjack table",channel) - game = self.bot.database["blackjack games"].find_one({"_id":channel}) + def drawImage(self, channel: str): + """ + Draw the table image. - fnt = ImageFont.truetype('resources/fonts/futura-bold.ttf', 50) - fntSmol = ImageFont.truetype('resources/fonts/futura-bold.ttf', 40) - self.BORDERSmol = int(self.BORDER/3.5) + *Parameters* + ------------ + channel: str + The id of the channel the game is in. + """ + self.bot.log("Drawing blackjack table", channel) + game = self.bot.database["blackjack games"].find_one({"_id": channel}) + + font = ImageFont.truetype('resources/fonts/futura-bold.ttf', 50) + smallFont = ImageFont.truetype('resources/fonts/futura-bold.ttf', 40) + self.SMALLBORDER = int(self.BORDER/3.5) table = Image.open("resources/games/blackjackTable.png") - self.PLACEMENT = [2,1,3,0,4] textImage = ImageDraw.Draw(table) hands = game["user hands"] dealerBusted = game["dealer busted"] dealerBlackjack = game["dealer blackjack"] - try: - if game["all standing"] == False: - dealerHand = self.drawHand(game["dealer hand"],True,False,False) - else: - dealerHand = self.drawHand(game["dealer hand"],False,dealerBusted,dealerBlackjack) - except: - self.bot.log("Error drawing dealer hand (error code 1341a)") + if not game["all standing"]: + handParams = [ + game["dealer hand"], + True, + False, + False + ] + else: + handParams = [ + game["dealer hand"], + False, + dealerBusted, + dealerBlackjack + ] - table.paste(dealerHand,(800-self.BORDERSmol,20-self.BORDERSmol),dealerHand) + dealerHand = self._drawHand(*handParams) + + pastePosition = (800-self.SMALLBORDER, 20-self.SMALLBORDER) + table.paste(dealerHand, pastePosition, dealerHand) for x in range(len(hands)): key, value = list(hands.items())[x] key = self.bot.databaseFuncs.getName(key) - #self.bot.log("Drawing "+key+"'s hand") - userHand = self.drawHand(value["hand"],False,value["busted"],value["blackjack"]) - try: - if value["split"] == 3: - table.paste(userHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),280-self.BORDERSmol),userHand) - userOtherHand = self.drawHand(value["other hand"]["hand"],False,value["other hand"]["busted"],value["other hand"]["blackjack"]) - table.paste(userOtherHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),420-self.BORDERSmol),userOtherHand) - userThirdHand = self.drawHand(value["third hand"]["hand"],False,value["third hand"]["busted"],value["third hand"]["blackjack"]) - table.paste(userThirdHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),560-self.BORDERSmol),userThirdHand) - userFourthHand = self.drawHand(value["fourth hand"]["hand"],False,value["fourth hand"]["busted"],value["fourth hand"]["blackjack"]) - table.paste(userFourthHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),700-self.BORDERSmol),userFourthHand) - elif value["split"] == 2: - table.paste(userHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),420-self.BORDERSmol),userHand) - userOtherHand = self.drawHand(value["other hand"]["hand"],False,value["other hand"]["busted"],value["other hand"]["blackjack"]) - table.paste(userOtherHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),560-self.BORDERSmol),userOtherHand) - userThirdHand = self.drawHand(value["third hand"]["hand"],False,value["third hand"]["busted"],value["third hand"]["blackjack"]) - table.paste(userThirdHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),700-self.BORDERSmol),userThirdHand) - elif value["split"] == 1: - table.paste(userHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),560-self.BORDERSmol),userHand) - userOtherHand = self.drawHand(value["other hand"]["hand"],False,value["other hand"]["busted"],value["other hand"]["blackjack"]) - table.paste(userOtherHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),700-self.BORDERSmol),userOtherHand) - else: - table.paste(userHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),680-self.BORDERSmol),userHand) - except: - self.bot.log("Error drawing player hands (error code 1341b)") + handParams = [ + value["hand"], + False, + value["busted"], + value["blackjack"] + ] + userHand = self._drawHand(*handParams) + positionX = 32-self.SMALLBORDER+(384*self.PLACEMENT[x]) - textWidth = fnt.getsize(key)[0] - if textWidth < 360: - textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2)-3,1010-3),key,fill=(0,0,0), font=fnt) - textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2)+3,1010-3),key,fill=(0,0,0), font=fnt) - textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2)-3,1010+3),key,fill=(0,0,0), font=fnt) - textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2)+3,1010+3),key,fill=(0,0,0), font=fnt) - textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2),1005),key,fill=(255,255,255), font=fnt) + if value["split"] >= 1: + handParamsTwo = [ + value["other hand"]["hand"], + False, + value["other hand"]["busted"], + value["other hand"]["blackjack"] + ] + userOtherHand = self._drawHand(*handParamsTwo) + + if value["split"] >= 2: + handParamsThree = [ + value["third hand"]["hand"], + False, + value["third hand"]["busted"], + value["third hand"]["blackjack"] + ] + userThirdHand = self._drawHand(*handParamsThree) + + if value["split"] >= 3: + handParamsFour = [ + value["fourth hand"]["hand"], + False, + value["fourth hand"]["busted"], + value["fourth hand"]["blackjack"] + ] + userFourthHand = self._drawHand(*handParamsFour) + + if value["split"] == 3: + positionOne = (positionX, 280-self.SMALLBORDER) + positionTwo = (positionX, 420-self.SMALLBORDER) + positionThree = (positionX, 560-self.SMALLBORDER) + positionFour = (positionX, 700-self.SMALLBORDER) + + table.paste(userHand, positionOne, userHand) + table.paste(userOtherHand, positionTwo, userOtherHand) + table.paste(userThirdHand, positionThree, userThirdHand) + table.paste(userFourthHand, positionFour, userFourthHand) + elif value["split"] == 2: + positionOne = (positionX, 420-self.SMALLBORDER) + positionTwo = (positionX, 560-self.SMALLBORDER) + positionThree = (positionX, 700-self.SMALLBORDER) + + table.paste(userHand, positionOne, userHand) + table.paste(userOtherHand, positionTwo, userOtherHand) + table.paste(userThirdHand, positionThree, userThirdHand) + elif value["split"] == 1: + positionOne = (positionX, 560-self.SMALLBORDER) + positionTwo = (positionX, 700-self.SMALLBORDER) + + table.paste(userHand, positionOne, userHand) + table.paste(userOtherHand, positionTwo, userOtherHand) else: - textWidth = fntSmol.getsize(key)[0] - textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2)-2,1020-2),key,fill=(0,0,0), font=fntSmol) - textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2)+2,1020-2),key,fill=(0,0,0), font=fntSmol) - textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2)-2,1020+2),key,fill=(0,0,0), font=fntSmol) - textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2)+2,1020+2),key,fill=(0,0,0), font=fntSmol) - textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2),1015),key,fill=(255,255,255), font=fntSmol) + positionOne = (positionX, 680-self.SMALLBORDER) + table.paste(userHand, positionOne, userHand) + + textWidth = font.getsize(key)[0] + textX = 32+(384*self.PLACEMENT[x])+117-int(textWidth/2) + black = (0, 0, 0) + white = (255, 255, 255) + + if textWidth < 360: + # Black shadow behind and slightly below white text + textImage.text((textX-3, 1010-3), key, fill=black, font=font) + textImage.text((textX+3, 1010-3), key, fill=black, font=font) + textImage.text((textX-3, 1010+3), key, fill=black, font=font) + textImage.text((textX+3, 1010+3), key, fill=black, font=font) + textImage.text((textX, 1005), key, fill=white, font=font) + else: + textWidth = smallFont.getsize(key)[0] + shadows = [ + (textX-2, 1020-2), + (textX+2, 1020-2), + (textX-2, 1020+2), + (textX+2, 1020+2) + ] + textImage.text(shadows[0], key, fill=black, font=smallFont) + textImage.text(shadows[1], key, fill=black, font=smallFont) + textImage.text(shadows[2], key, fill=black, font=smallFont) + textImage.text(shadows[3], key, fill=black, font=smallFont) + textImage.text((textX, 1015), key, fill=white, font=smallFont) self.bot.log("Saving table image") - table.save("resources/games/blackjackTables/blackjackTable"+channel+".png") + tableImagesPath = "resources/games/blackjackTables/" + tableImagePath = f"{tableImagesPath}blackjackTable{channel}.png" + table.save(tableImagePath) return - def drawHand(self, hand, dealer, busted, blackjack): - self.bot.log("Drawing hand "+str(hand)+", "+str(busted)+", "+str(blackjack)) - fnt = ImageFont.truetype('resources/fonts/futura-bold.ttf', 200) - fnt2 = ImageFont.truetype('resources/fonts/futura-bold.ttf', 120) + def _drawHand(self, hand: dict, dealer: bool, busted: bool, + blackjack: bool): + """ + Draw a hand. + + *Parameters* + ------------ + hand: dict + The hand to draw. + dealer: bool + If the hand belongs to the dealer who hasn't shown + their second card. + busted: bool + If the hand is busted. + blackjack: bool + If the hand has a blackjack. + + *Returns* + --------- + background: Image + The image of the hand. + """ + self.bot.log("Drawing hand {hand}, {busted}, {blackjack}") + font = ImageFont.truetype('resources/fonts/futura-bold.ttf', 200) + font2 = ImageFont.truetype('resources/fonts/futura-bold.ttf', 120) length = len(hand) - background = Image.new("RGBA", ((self.BORDER*2)+691+(125*(length-1)),(self.BORDER*2)+1065),(0,0,0,0)) + backgroundWidth = (self.BORDER*2)+691+(125*(length-1)) + backgroundSize = (backgroundWidth, (self.BORDER*2)+1065) + background = Image.new("RGBA", backgroundSize, (0, 0, 0, 0)) textImage = ImageDraw.Draw(background) + cardY = self.BORDER+self.PLACEMENT[1] if dealer: img = Image.open("resources/games/cards/"+hand[0].upper()+".png") - #self.ROTATION = (random.randint(-20,20)/10.0) - img = img.rotate(self.ROTATION,expand = 1) - #self.PLACEMENT = [random.randint(-20,20),random.randint(-20,20)] - background.paste(img,(self.BORDER+self.PLACEMENT[0],self.BORDER+self.PLACEMENT[1]),img) + cardPosition = (self.BORDER+self.PLACEMENT[0], cardY) + background.paste(img, cardPosition, img) img = Image.open("resources/games/cards/red_back.png") - #self.ROTATION = (random.randint(-20,20)/10.0) - img = img.rotate(self.ROTATION,expand = 1) - #self.PLACEMENT = [random.randint(-20,20),random.randint(-20,20)] - background.paste(img,(125+self.BORDER+self.PLACEMENT[0],self.BORDER+self.PLACEMENT[1]),img) + cardPosition = (125+self.BORDER+self.PLACEMENT[0], cardY) + background.paste(img, cardPosition, img) else: for x in range(length): - img = Image.open("resources/games/cards/"+hand[x].upper()+".png") - #self.ROTATION = (random.randint(-20,20)/10.0) - img = img.rotate(self.ROTATION,expand = 1) - #self.PLACEMENT = [random.randint(-20,20),random.randint(-20,20)] - background.paste(img,(self.BORDER+(x*125)+self.PLACEMENT[0],self.BORDER+self.PLACEMENT[1]),img) + cardPath = f"resources/games/cards/{hand[x].upper()}.png" + img = Image.open(cardPath) + cardPosition = (self.BORDER+(x*125)+self.PLACEMENT[0], cardY) + background.paste(img, cardPosition, img) w, h = background.size textHeight = 290+self.BORDER + white = (255, 255, 255) + black = (0, 0, 0) + red = (255, 50, 50) + gold = (155, 123, 0) - #self.bot.log("Drawing busted/blackjack") if busted: - textWidth = fnt.getsize("BUSTED")[0] - textImage.text((int(w/2)-int(textWidth/2)-10,textHeight+20-10),"BUSTED",fill=(0,0,0), font=fnt) - textImage.text((int(w/2)-int(textWidth/2)+10,textHeight+20-10),"BUSTED",fill=(0,0,0), font=fnt) - textImage.text((int(w/2)-int(textWidth/2)-10,textHeight+20+10),"BUSTED",fill=(0,0,0), font=fnt) - textImage.text((int(w/2)-int(textWidth/2)+10,textHeight+20+10),"BUSTED",fill=(0,0,0), font=fnt) - textImage.text((int(w/2)-int(textWidth/2)-5,textHeight-5),"BUSTED",fill=(255,255,255), font=fnt) - textImage.text((int(w/2)-int(textWidth/2)+5,textHeight-5),"BUSTED",fill=(255,255,255), font=fnt) - textImage.text((int(w/2)-int(textWidth/2)-5,textHeight+5),"BUSTED",fill=(255,255,225), font=fnt) - textImage.text((int(w/2)-int(textWidth/2)+5,textHeight+5),"BUSTED",fill=(255,255,255), font=fnt) - textImage.text((int(w/2)-int(textWidth/2),textHeight),"BUSTED",fill=(255,50,50), font=fnt) + textWidth = font.getsize("BUSTED")[0] + textX = int(w/2)-int(textWidth/2) + positions = [ + (textX-10, textHeight+20-10), + (textX+10, textHeight+20-10), + (textX-10, textHeight+20+10), + (textX+10, textHeight+20+10), + (textX-5, textHeight-5), + (textX+5, textHeight-5), + (textX-5, textHeight+5), + (textX+5, textHeight+5), + (textX, textHeight) + ] + textImage.text(positions[0], "BUSTED", fill=black, font=font) + textImage.text(positions[1], "BUSTED", fill=black, font=font) + textImage.text(positions[2], "BUSTED", fill=black, font=font) + textImage.text(positions[3], "BUSTED", fill=black, font=font) + textImage.text(positions[4], "BUSTED", fill=white, font=font) + textImage.text(positions[5], "BUSTED", fill=white, font=font) + textImage.text(positions[6], "BUSTED", fill=white, font=font) + textImage.text(positions[7], "BUSTED", fill=white, font=font) + textImage.text(positions[8], "BUSTED", fill=red, font=font) elif blackjack: - textWidth = fnt2.getsize("BLACKJACK")[0] - textImage.text((int(w/2)-int(textWidth/2)-6,textHeight+20-6),"BLACKJACK",fill=(0,0,0), font=fnt2) - textImage.text((int(w/2)-int(textWidth/2)+6,textHeight+20-6),"BLACKJACK",fill=(0,0,0), font=fnt2) - textImage.text((int(w/2)-int(textWidth/2)-6,textHeight+20+6),"BLACKJACK",fill=(0,0,0), font=fnt2) - textImage.text((int(w/2)-int(textWidth/2)+6,textHeight+20+6),"BLACKJACK",fill=(0,0,0), font=fnt2) - textImage.text((int(w/2)-int(textWidth/2)-3,textHeight-3),"BLACKJACK",fill=(255,255,255), font=fnt2) - textImage.text((int(w/2)-int(textWidth/2)+3,textHeight-3),"BLACKJACK",fill=(255,255,255), font=fnt2) - textImage.text((int(w/2)-int(textWidth/2)-3,textHeight+3),"BLACKJACK",fill=(255,255,255), font=fnt2) - textImage.text((int(w/2)-int(textWidth/2)+3,textHeight+3),"BLACKJACK",fill=(255,255,255), font=fnt2) - textImage.text((int(w/2)-int(textWidth/2),textHeight),"BLACKJACK",fill=(155,123,0), font=fnt2) - - #self.bot.log("Returning resized image") - return background.resize((int(w/3.5),int(h/3.5)),resample=Image.BILINEAR) + textWidth = font2.getsize("BLACKJACK")[0] + textX = int(w/2)-int(textWidth/2) + positions = [ + (textX-6, textHeight+20-6), + (textX+6, textHeight+20-6), + (textX-6, textHeight+20+6), + (textX+6, textHeight+20+6), + (textX-3, textHeight-3), + (textX+3, textHeight-3), + (textX-3, textHeight+3), + (textX+3, textHeight+3), + (textX, textHeight) + ] + textImage.text(positions[0], "BLACKJACK", fill=black, font=font2) + textImage.text(positions[1], "BLACKJACK", fill=black, font=font2) + textImage.text(positions[2], "BLACKJACK", fill=black, font=font2) + textImage.text(positions[3], "BLACKJACK", fill=black, font=font2) + textImage.text(positions[4], "BLACKJACK", fill=white, font=font2) + textImage.text(positions[5], "BLACKJACK", fill=white, font=font2) + textImage.text(positions[6], "BLACKJACK", fill=white, font=font2) + textImage.text(positions[7], "BLACKJACK", fill=white, font=font2) + textImage.text(positions[8], "BLACKJACK", fill=gold, font=font2) + resizedSize = (int(w/3.5), int(h/3.5)) + return background.resize(resizedSize, resample=Image.BILINEAR) diff --git a/requirements.txt b/requirements.txt index e66d93c..ce8b57f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,10 +8,10 @@ certifi==2020.12.5 chardet==4.0.0 colorama==0.4.4 d20==1.1.0 -discord-py-slash-command==1.1.1 +discord-py-slash-command==1.1.2 discord.py==1.7.1 dnspython==2.1.0 -docutils==0.16 +docutils==0.17 fandom-py==0.2.1 finnhub-python==2.4.0 gitdb==4.0.7 @@ -21,7 +21,7 @@ idna==2.10 IMDbPY==2020.9.25 isort==5.8.0 jaraco.context==4.0.0 -lark-parser==0.9.0 +lark-parser==0.11.2 lazy-object-proxy==1.6.0 lxml==4.6.3 mccabe==0.6.1 diff --git a/resources/longStrings.json b/resources/longStrings.json index a7b418f..1458e3d 100644 --- a/resources/longStrings.json +++ b/resources/longStrings.json @@ -1,4 +1,12 @@ { "missing parameters" : "Missing command parameters. Try using `!help [command]` to find out how to use the command.", - "Can't log in": "Could not log in. Remember to write your bot token in the credentials.txt file" + "Can't log in": "Could not log in. Remember to write your bot token in the credentials.txt file", + "Blackjack all players standing": "All players are standing. The dealer now shows his cards and draws.", + "Blackjack first round": ". You can also double down with \"/blackjack double\" or split with \"/blackjack split\"", + "Blackjack commands": "You have 2 minutes to either hit or stand with \"/blackjack hit\" or \"/blackjack stand\"{}. It's assumed you're standing if you don't make a choice.", + "Blackjack double": "Adding another {} GwendoBucks to {}'s bet and drawing another card.", + "Blackjack different cards": "You can only split if your cards have the same value", + "Blackjack split": "Splitting {}'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.", + "Blackjack started": "Blackjack game started. Use \"/blackjack bet [amount]\" to enter the game within the next 30 seconds.", + "Blackjack going on": "There's already a blackjack game going on. Try again in a few minutes." } \ No newline at end of file diff --git a/utils/utilFunctions.py b/utils/utilFunctions.py index 6fea627..a472a21 100644 --- a/utils/utilFunctions.py +++ b/utils/utilFunctions.py @@ -13,12 +13,12 @@ Contains utility functions used by parts of the bot. newString: str) -> str emojiToCommand(emoji: str) -> str """ -import json -import logging -import os -import sys -import imdb -from .helperClasses import Options +import json # Used by longString(), getParams() and makeFiles() +import logging # Used for logging +import os # Used by makeFiles() to check if files exist +import sys # Used to specify printing for logging +import imdb # Used to disable logging for the module +from .helperClasses import Options # Used by getParams() # All of this is logging configuration From 872ad1aa6d6b6f730ebd7ff19d0bd58408603ac3 Mon Sep 17 00:00:00 2001 From: NikolajDanger Date: Sat, 17 Apr 2021 21:30:46 +0200 Subject: [PATCH 03/23] :dollar: Money --- funcs/games/money.py | 151 +++++++++++++++++++++++++++++++++---------- 1 file changed, 116 insertions(+), 35 deletions(-) diff --git a/funcs/games/money.py b/funcs/games/money.py index dea2812..c626a91 100644 --- a/funcs/games/money.py +++ b/funcs/games/money.py @@ -1,70 +1,151 @@ +""" +Contains the code that deals with money. + +*Classes* +--------- + Money + Deals with money. +""" +import discord_slash # Used for typehints +import discord # Used for typehints + + class Money(): + """ + Deals with money. + + *Methods* + --------- + checkBalance(user: str) + sendBalance(ctx: discord_slash.context.SlashContext) + addMoney(user: str, amount: int) + giveMoney(ctx: discord_slash.context.SlashContext, user: discord.User, + amount: int) + + *Attributes* + ------------ + bot: Gwendolyn + The instance of Gwendolyn + database: pymongo.Client + The mongo database + """ def __init__(self, bot): + """Initialize the class.""" self.bot = bot self.database = bot.database - # Returns the account balance for a user - def checkBalance(self, user): + def checkBalance(self, user: str): + """ + Get the account balance of a user. + + *Parameters* + ------------ + user: str + The user to get the balance of. + + *Returns* + --------- + balance: int + The balance of the user's account. + """ self.bot.log("checking "+user+"'s account balance") - userData = self.database["users"].find_one({"_id":user}) + userData = self.database["users"].find_one({"_id": user}) - if userData != None: + if userData is not None: return userData["money"] - else: return 0 + else: + return 0 - async def sendBalance(self, ctx): + async def sendBalance(self, ctx: discord_slash.context.SlashContext): + """ + Get your own account balance. + + *Parameters* + ------------ + ctx: discord_slash.context.SlashContext + The context of the command. + """ await self.bot.defer(ctx) response = self.checkBalance("#"+str(ctx.author.id)) + userName = ctx.author.display_name if response == 1: - new_message = ctx.author.display_name + " has " + str(response) + " GwendoBuck" + new_message = f"{userName} has {response} GwendoBuck" else: - new_message = ctx.author.display_name + " has " + str(response) + " GwendoBucks" + new_message = f"{userName} has {response} GwendoBucks" await ctx.send(new_message) # Adds money to the account of a user - def addMoney(self,user,amount): + def addMoney(self, user: str, amount: int): + """ + Add money to a user account. + + *Parameters* + ------------ + user: str + The id of the user to give money. + amount: int + The amount to add to the user's account. + """ self.bot.log("adding "+str(amount)+" to "+user+"'s account") - userData = self.database["users"].find_one({"_id":user}) + userData = self.database["users"].find_one({"_id": user}) - if userData != None: - self.database["users"].update_one({"_id":user},{"$inc":{"money":amount}}) + if userData is not None: + updater = {"$inc": {"money": amount}} + self.database["users"].update_one({"_id": user}, updater) else: - self.database["users"].insert_one({"_id":user,"user name":self.bot.databaseFuncs.getName(user),"money":amount}) + newUser = { + "_id": user, + "user name": self.bot.databaseFuncs.getName(user), + "money": amount + } + self.database["users"].insert_one(newUser) # Transfers money from one user to another - async def giveMoney(self, ctx, user, amount): + async def giveMoney(self, ctx: discord_slash.context.SlashContext, + user: discord.User, amount: int): + """ + Give someone else money from your account. + + *Parameters* + ------------ + ctx: discord_slash.context.SlashContext + The context of the command. + user: discord.User + The user to give money. + amount: int + The amount to transfer. + """ await self.bot.defer(ctx) username = user.display_name - if self.bot.databaseFuncs.getID(username) == None: + if self.bot.databaseFuncs.getID(username) is 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} + userID = f"#{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}"}) + userid = f"#{ctx.author.id}" + userData = self.database["users"].find_one({"_id": userid}) targetUser = self.bot.databaseFuncs.getID(username) - if amount > 0: - if targetUser != None: - if userData != None: - if userData["money"] >= amount: - self.addMoney(f"#{ctx.author.id}",-1 * amount) - self.addMoney(targetUser,amount) - await ctx.send(f"Transferred {amount} GwendoBucks to {username}") - else: - 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") - await ctx.send("You don't have that many GwendoBuck") - else: - self.bot.log("They weren't in the system") - await ctx.send("The target doesn't exist") - else: + if amount <= 0: self.bot.log("They tried to steal") await ctx.send("Yeah, no. You can't do that") + elif targetUser is None: + self.bot.log("They weren't in the system") + await ctx.send("The target doesn't exist") + elif userData is None or userData["money"] < amount: + self.bot.log("They didn't have enough GwendoBucks") + await ctx.send("You don't have that many GwendoBuck") + else: + self.addMoney(f"#{ctx.author.id}", -1 * amount) + self.addMoney(targetUser, amount) + await ctx.send(f"Transferred {amount} GwendoBucks to {username}") From 0e714b3f5cda3ccd0f451ad4aadc3cb6ae26ce0c Mon Sep 17 00:00:00 2001 From: NikolajDanger Date: Sat, 17 Apr 2021 21:44:00 +0200 Subject: [PATCH 04/23] :dice: Games container --- funcs/games/gamesContainer.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/funcs/games/gamesContainer.py b/funcs/games/gamesContainer.py index 203209a..0169cc8 100644 --- a/funcs/games/gamesContainer.py +++ b/funcs/games/gamesContainer.py @@ -1,3 +1,13 @@ +""" +Has a container for game functions. + +*Classes* +--------- + Games + Container for game functions. +""" + + from .invest import Invest from .trivia import Trivia from .blackjack import Blackjack @@ -5,8 +15,29 @@ from .connectFour import ConnectFour from .hangman import Hangman from .hex import HexGame + class Games(): + """ + Contains game classes. + + *Attributes* + ------------ + bot: Gwendolyn + The instance of Gwendolyn. + invest + Contains investment functions. + blackjack + Contains blackjack functions. + connectFour + Contains connect four functions. + hangman + Contains hangman functions. + hex + Contains hex functions + """ + def __init__(self, bot): + """Initialize the container.""" self.bot = bot self.invest = Invest(bot) From 4c6c8c2ea36ce99e2249294a249004832ac348bf Mon Sep 17 00:00:00 2001 From: NikolajDanger Date: Sun, 18 Apr 2021 14:06:39 +0200 Subject: [PATCH 05/23] :moneybag: Invest --- funcs/games/invest.py | 297 +++++++++++++++++++++++++++---------- resources/longStrings.json | 4 +- 2 files changed, 220 insertions(+), 81 deletions(-) diff --git a/funcs/games/invest.py b/funcs/games/invest.py index 75bb33a..2501353 100644 --- a/funcs/games/invest.py +++ b/funcs/games/invest.py @@ -1,111 +1,243 @@ -import discord +""" +Contains functions relating to invest commands. + +*Classes* +--------- + Invest + Contains all the code for the invest commands. +""" +import discord # Used for embeds +from discord_slash.context import SlashContext # Used for type hints + class Invest(): + """ + Contains all the invest functions. + + *Methods* + --------- + getPrice(symbol: str) -> int + getPortfolio(user: str) -> str + buyStock(user: str, stock: str: buyAmount: int) -> str + sellStock(user: str, stock: str, buyAmount: int) -> str + parseInvest(ctx: discord_slash.context.SlashContext, + parameters: str) + """ + def __init__(self, bot): + """Initialize the class.""" self.bot = bot - def getPrice(self, symbol : str): + def getPrice(self, symbol: str): + """ + Get the price of a stock. + + *Parameters* + ------------ + symbol: str + The symbol of the stock to get the price of. + + *Returns* + --------- + price: int + The price of the stock. + """ res = self.bot.finnhubClient.quote(symbol.upper()) if res == {}: return 0 else: return int(res["c"] * 100) - def getPortfolio(self, user : str): - userInvestments = self.bot.database["investments"].find_one({"_id":user}) + def getPortfolio(self, user: str): + """ + Get the stock portfolio of a user. - if userInvestments in [None,{}]: - return f"{self.bot.databaseFuncs.getName(user)} does not have a stock portfolio." + *Parameters* + ------------ + user: str + The id of the user to get the portfolio of. + + *Returns* + --------- + portfolio: str + The portfolio. + """ + investmentsDatabase = self.bot.database["investments"] + userInvestments = investmentsDatabase.find_one({"_id": user}) + + userName = self.bot.databaseFuncs.getName(user) + + if userInvestments in [None, {}]: + return f"{userName} does not have a stock portfolio." else: - portfolio = f"**Stock portfolio for {self.bot.databaseFuncs.getName(user)}**" + portfolio = f"**Stock portfolio for {userName}**" for key, value in list(userInvestments["investments"].items()): purchaseValue = value["purchased for"] - currentValue = int((self.getPrice(key) / value["value at purchase"]) * value["purchased"]) - if purchaseValue == "?": - portfolio += f"\n**{key}**: ___{str(currentValue)} GwendoBucks___" - else: - portfolio += f"\n**{key}**: ___{str(currentValue)} GwendoBucks___ (purchased for {str(purchaseValue)})" + stockPrice = self.getPrice(key) + valueAtPurchase = value["value at purchase"] + purchasedStock = value["purchased"] + valueChange = (stockPrice / valueAtPurchase) + currentValue = int(valueChange * purchasedStock) + portfolio += f"\n**{key}**: ___{currentValue} GwendoBucks___" + + if purchaseValue != "?": + portfolio += f" (purchased for {purchaseValue})" return portfolio - def buyStock(self, user : str, stock : str, buyAmount : int): - if buyAmount >= 100: - if self.bot.money.checkBalance(user) >= buyAmount: - stockPrice = self.getPrice(stock) - if stockPrice > 0: - userInvestments = self.bot.database["investments"].find_one({"_id":user}) + def buyStock(self, user: str, stock: str, buyAmount: int): + """ + Buy an amount of a specific stock. - self.bot.money.addMoney(user,-1*buyAmount) - stock = stock.upper() + *Paramaters* + ------------ + user: str + The id of the user buying. + stock: str + The symbol of the stock to buy. + buyAmount: int + The amount of GwendoBucks to use to buy the stock. - if userInvestments != None: - userInvestments = userInvestments["investments"] - if stock in userInvestments: - value = userInvestments[stock] - newAmount = int((stockPrice / value["value at purchase"]) * value["purchased"]) + buyAmount - - self.bot.database["investments"].update_one({"_id":user}, - {"$set":{"investments."+stock+".value at purchase" : stockPrice}}) - - self.bot.database["investments"].update_one({"_id":user}, - {"$set":{"investments."+stock+".purchased" : newAmount}}) - - if value["purchased for"] != "?": - self.bot.database["investments"].update_one({"_id":user}, - {"$set":{"investments."+stock+".purchased for" : buyAmount}}) - else: - self.bot.database["investments"].update_one({"_id":user}, - {"$set":{"investments."+stock : {"purchased" : buyAmount, "value at purchase" : stockPrice, - "purchased for" : buyAmount}}}) - else: - newUser = {"_id":user,"investments":{stock : {"purchased" : buyAmount, "value at purchase" : stockPrice, "purchased for" : buyAmount}}} - self.bot.database["investments"].insert_one(newUser) - - return f"{self.bot.databaseFuncs.getName(user)} bought {buyAmount} GwendoBucks worth of {stock} stock" - else: - return f"{stock} is not traded on the american market." - else: - return "You don't have enough money for that" - else: + *Returns* + --------- + sendMessage: str + The message to return to the user. + """ + if buyAmount < 100: return "You cannot buy stocks for less than 100 GwendoBucks" + elif self.bot.money.checkBalance(user) < buyAmount: + return "You don't have enough money for that" + elif self.getPrice(stock) <= 0: + return f"{stock} is not traded on the american market." + else: + investmentsDatabase = self.bot.database["investments"] + stockPrice = self.getPrice(stock) + userInvestments = investmentsDatabase.find_one({"_id": user}) - def sellStock(self, user : str, stock : str, sellAmount : int): - if sellAmount > 0: + self.bot.money.addMoney(user, -1*buyAmount) + stock = stock.upper() - userInvestments = self.bot.database["investments"].find_one({"_id":user})["investments"] + if userInvestments is not None: + userInvestments = userInvestments["investments"] + if stock in userInvestments: + value = userInvestments[stock] + valueChange = (stockPrice / value["value at purchase"]) + currentValue = int(valueChange * value["purchased"]) + newAmount = currentValue + buyAmount + + valuePath = f"investments.{stock}.value at purchase" + updater = {"$set": {valuePath: stockPrice}} + investmentsDatabase.update_one({"_id": user}, updater) + + purchasedPath = f"investments.{stock}.purchased" + updater = {"$set": {purchasedPath: newAmount}} + investmentsDatabase.update_one({"_id": user}, updater) + + if value["purchased for"] != "?": + purchasedForPath = f"investments.{stock}.purchased for" + updater = {"$set": {purchasedForPath: buyAmount}} + investmentsDatabase.update_one({"_id": user}, updater) + else: + updater = { + "$set": { + "investments.{stock}": { + "purchased": buyAmount, + "value at purchase": stockPrice, + "purchased for": buyAmount + } + } + } + investmentsDatabase.update_one({"_id": user}, updater) + else: + newUser = { + "_id": user, + "investments": { + stock: { + "purchased": buyAmount, + "value at purchase": stockPrice, + "purchased for": buyAmount + } + } + } + investmentsDatabase.insert_one(newUser) + + userName = self.bot.databaseFuncs.getName(user) + sendMessage = "{} bought {} GwendoBucks worth of {} stock" + sendMessage = sendMessage.format(userName, buyAmount, stock) + return sendMessage + + def sellStock(self, user: str, stock: str, sellAmount: int): + """ + Sell an amount of a specific stock. + + *Paramaters* + ------------ + user: str + The id of the user selling. + stock: str + The symbol of the stock to sell. + buyAmount: int + The amount of GwendoBucks to sell for. + + *Returns* + --------- + sendMessage: str + The message to return to the user. + """ + if sellAmount <= 0: + return "no" + else: + investmentsDatabase = self.bot.database["investments"] + userData = investmentsDatabase.find_one({"_id": user}) + userInvestments = userData["investments"] stock = stock.upper() - if userInvestments != None and stock in userInvestments: + if userInvestments is not None and stock in userInvestments: value = userInvestments[stock] stockPrice = self.getPrice(stock) - self.bot.database["investments"].update_one({"_id":user}, - {"$set":{"investments."+stock+".purchased" : - int((stockPrice / value["value at purchase"]) * value["purchased"])}}) - self.bot.database["investments"].update_one({"_id":user}, - {"$set":{"investments."+stock+".value at purchase" : stockPrice}}) + priceChange = (stockPrice / value["value at purchase"]) + purchasedAmount = int(priceChange * value["purchased"]) + purchasedPath = f"investments.{stock}.purchased" + updater = {"$set": {purchasedPath: purchasedAmount}} + investmentsDatabase.update_one({"_id": user}, updater) + valueAtPurchasePath = f"investments.{stock}.value at purchase" + updater = {"$set": {valueAtPurchasePath: stockPrice}} + investmentsDatabase.update_one({"_id": user}, updater) if value["purchased"] >= sellAmount: - self.bot.money.addMoney(user,sellAmount) + self.bot.money.addMoney(user, sellAmount) if sellAmount < value["purchased"]: - self.bot.database["investments"].update_one({"_id":user}, - {"$inc":{"investments."+stock+".purchased" : -sellAmount}}) + purchasedPath = f"investments.{stock}.purchased" + updater = {"$inc": {purchasedPath: -sellAmount}} + investmentsDatabase.update_one({"_id": user}, updater) - self.bot.database["investments"].update_one({"_id":user}, - {"$set":{"investments."+stock+".purchased for" : "?"}}) + purchasedForPath = f"investments.{stock}.purchased for" + updater = {"$set": {purchasedForPath: "?"}} + investmentsDatabase.update_one({"_id": user}, updater) else: - self.bot.database["investments"].update_one({"_id":user}, - {"$unset":{"investments."+stock:""}}) + updater = {"$unset": {f"investments.{stock}": ""}} + investmentsDatabase.update_one({"_id": user}, updater) - return f"{self.bot.databaseFuncs.getName(user)} sold {sellAmount} GwendoBucks worth of {stock} stock" + userName = self.bot.databaseFuncs.getName(user) + sendMessage = "{} sold {} GwendoBucks worth of {} stock" + return sendMessage.format(userName, sellAmount, stock) else: return f"You don't have enough {stock} stocks to do that" else: return f"You don't have any {stock} stock" - else: - return "no" - async def parseInvest(self, ctx, parameters): + async def parseInvest(self, ctx: SlashContext, parameters: str): + """ + Parse an invest command. TO BE DELETED. + + *Parameters* + ------------ + ctx: discord_slash.context.SlashContext + The context of the slash command. + parameters: str + The parameters of the command. + """ await self.bot.defer(ctx) user = f"#{ctx.author.id}" @@ -116,34 +248,39 @@ class Invest(): else: price = self.getPrice(commands[1]) if price == 0: - response = f"{commands[1].upper()} is not traded on the american market." + response = "{} is not traded on the american market." + response = response.format(commands[0].upper()) else: - price = f"{price:,}".replace(",",".") - response = f"The current {commands[1].upper()} stock is valued at **{price}** GwendoBucks" + price = f"{price:,}".replace(",", ".") + response = self.bot.longStrings["Stock value"] + response = response.format(commands[1].upper(), price) elif parameters.startswith("buy"): commands = parameters.split(" ") if len(commands) == 3: - response = self.buyStock(user,commands[1],int(commands[2])) + response = self.buyStock(user, commands[1], int(commands[2])) else: - response = "You must give both a stock name and an amount of gwendobucks you wish to spend." + response = self.bot.longStrings["Stock parameters"] elif parameters.startswith("sell"): commands = parameters.split(" ") if len(commands) == 3: - try: - response = self.sellStock(user,commands[1],int(commands[2])) - except: - response = "The command must be given as \"/invest sell [stock] [amount of GwendoBucks to sell stocks for]\"" + response = self.sellStock(user, commands[1], int(commands[2])) else: - response = "You must give both a stock name and an amount of GwendoBucks you wish to sell stocks for." + response = self.bot.longStrings["Stock parameters"] else: response = "Incorrect parameters" if response.startswith("**"): responses = response.split("\n") - em = discord.Embed(title=responses[0],description="\n".join(responses[1:]),colour=0x00FF00) + text = "\n".join(responses[1:]) + embedParams = { + "title": responses[0], + "description": text, + "colour": 0x00FF00 + } + em = discord.Embed(*embedParams) await ctx.send(embed=em) else: await ctx.send(response) diff --git a/resources/longStrings.json b/resources/longStrings.json index 1458e3d..d1e1a0e 100644 --- a/resources/longStrings.json +++ b/resources/longStrings.json @@ -8,5 +8,7 @@ "Blackjack different cards": "You can only split if your cards have the same value", "Blackjack split": "Splitting {}'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.", "Blackjack started": "Blackjack game started. Use \"/blackjack bet [amount]\" to enter the game within the next 30 seconds.", - "Blackjack going on": "There's already a blackjack game going on. Try again in a few minutes." + "Blackjack going on": "There's already a blackjack game going on. Try again in a few minutes.", + "Stock value": "The current {} stock is valued at **{}** GwendoBucks", + "Stock parameters": "You must give both a stock name and an amount of GwendoBucks you wish to spend." } \ No newline at end of file From 886b73391485c84701539080246bfcaaa467491f Mon Sep 17 00:00:00 2001 From: NikolajDanger Date: Sun, 18 Apr 2021 14:42:21 +0200 Subject: [PATCH 06/23] :sparkles: Trivia --- funcs/games/trivia.py | 207 ++++++++++++++++++++++++++----------- resources/longStrings.json | 4 +- 2 files changed, 150 insertions(+), 61 deletions(-) diff --git a/funcs/games/trivia.py b/funcs/games/trivia.py index eeb4003..e01fc1d 100644 --- a/funcs/games/trivia.py +++ b/funcs/games/trivia.py @@ -1,94 +1,175 @@ -import json -import urllib -import random -import asyncio +""" +Contains code for trivia games. + +*Classes* +--------- + Trivia + Contains all the code for the trivia commands. +""" +import urllib # Used to get data from api +import json # Used to read data from api +import random # Used to shuffle answers +import asyncio # Used to sleep + +from discord_slash.context import SlashContext # Used for type hints + class Trivia(): + """ + Contains the code for trivia games. + + *Methods* + --------- + triviaStart(channel: str) -> str, str, str + triviaAnswer(user: str, channel: str, command: str) -> str + triviaCountPoints(channel: str) + triviaParse(ctx: SlashContext, answer: str) + """ + def __init__(self, bot): + """Initialize the class.""" self.bot = bot - # Starts a game of trivia. Downloads a question with answers, shuffles the wrong answers with the - # correct answer and returns the questions and answers. Also saves the question in the games.json file. - def triviaStart(self, channel : str): - question = self.bot.database["trivia questions"].find_one({"_id":channel}) + def triviaStart(self, channel: str): + """ + Start a game of trivia. - self.bot.log("Trying to find a trivia question for "+channel) + Downloads a question with answers, shuffles the wrong answers + with the correct answer and returns the questions and answers. + Also saves the question in the database. - if question == None: - with urllib.request.urlopen("https://opentdb.com/api.php?amount=10&type=multiple") as response: + *Parameters* + ------------ + channel: str + The id of the channel to start the game in + + *Returns* + --------- + sendMessage: str + The message to return to the user. + """ + triviaQuestions = self.bot.database["trivia questions"] + question = triviaQuestions.find_one({"_id": channel}) + + self.bot.log(f"Trying to find a trivia question for {channel}") + + if question is None: + apiUrl = "https://opentdb.com/api.php?amount=10&type=multiple" + with urllib.request.urlopen(apiUrl) as response: data = json.loads(response.read()) - self.bot.log("Found the question \""+data["results"][0]["question"]+"\"") + question = data["results"][0]["question"] + self.bot.log(f"Found the question \"{question}\"") answers = data["results"][0]["incorrect_answers"] answers.append(data["results"][0]["correct_answer"]) random.shuffle(answers) - correctAnswer = answers.index(data["results"][0]["correct_answer"]) + 97 + correctAnswer = data["results"][0]["correct_answer"] + correctAnswer = answers.index(correctAnswer) + 97 - self.bot.database["trivia questions"].insert_one({"_id":channel,"answer" : str(chr(correctAnswer)),"players" : {}}) + newQuestion = { + "_id": channel, + "answer": str(chr(correctAnswer)), + "players": {} + } + triviaQuestions.insert_one(newQuestion) - replacements = {"'": "\'", - """: "\"", - "“": "\"", - "”": "\"", - "é": "é"} + replacements = { + "'": "\'", + """: "\"", + "“": "\"", + "”": "\"", + "é": "é" + } question = data["results"][0]["question"] for key, value in replacements.items(): - question = question.replace(key,value) + question = question.replace(key, value) for answer in answers: for key, value in replacements.items(): - answer = answer.replace(key,value) + answer = answer.replace(key, value) return question, answers, correctAnswer else: - self.bot.log("There was already a trivia question for that channel (error code 1106)") - return "There's already a trivia question going on. Try again in like, a minute (error code 1106)", "", "" + logMessage = "There was already a trivia question for that channel" + self.bot.log(logMessage) + return self.bot.longStrings["Trivia going on"], "", "" - # Lets players answer a trivia question - def triviaAnswer(self, user : str, channel : str, command : str): - question = self.bot.database["trivia questions"].find_one({"_id":channel}) + def triviaAnswer(self, user: str, channel: str, command: str): + """ + Answer the current trivia question. - if command in ["a","b","c","d"]: - if question != None: - if user not in question["players"]: - self.bot.log(user+" answered the question in "+channel) + *Parameters* + ------------ + user: str + The id of the user who answered. + channel: str + The id of the channel the game is in. + command: str + The user's answer. - self.bot.database["trivia questions"].update_one({"_id":channel},{"$set":{"players."+user : command}}) + *Returns* + --------- + sendMessage: str + The message to send if the function failed. + """ + triviaQuestions = self.bot.database["trivia questions"] + question = triviaQuestions.find_one({"_id": channel}) - return "Locked in "+user+"'s answer" - else: - self.bot.log(user+" has already answered this question (error code 1105)") - return user+" has already answered this question (error code 1105)" - else: - self.bot.log("There's no question right now (error code 1104)") - return "There's no question right now (error code 1104)" + if command not in ["a", "b", "c", "d"]: + self.bot.log("I didn't quite understand that") + return "I didn't quite understand that" + elif question is None: + self.bot.log("There's no question right now") + return "There's no question right now" + elif user in question["players"]: + self.bot.log(f"{user} has already answered this question") + return f"{user} has already answered this question" else: - self.bot.log("I didn't quite understand that (error code 1103)") - return "I didn't quite understand that (error code 1103)" + self.bot.log(f"{user} answered the question in {channel}") + updater = {"$set": {f"players.{user}": command}} + triviaQuestions.update_one({"_id": channel}, updater) + return None - # Adds 1 GwendoBuck to each player that got the question right and deletes question from games.json. - def triviaCountPoints(self, channel : str): - question = self.bot.database["trivia questions"].find_one({"_id":channel}) + def triviaCountPoints(self, channel: str): + """ + Add money to every winner's account. + + *Parameters* + ------------ + channel: str + The id of the channel the game is in. + """ + triviaQuestions = self.bot.database["trivia questions"] + question = triviaQuestions.find_one({"_id": channel}) self.bot.log("Counting points for question in "+channel) - if question != None: + if question is not None: for player, answer in question["players"].items(): if answer == question["answer"]: - self.bot.money.addMoney(player,1) - - + self.bot.money.addMoney(player, 1) else: - self.bot.log("Couldn't find the question (error code 1102)") + self.bot.log("Couldn't find the questio") return None - async def triviaParse(self, ctx, answer): + async def triviaParse(self, ctx: SlashContext, answer: str): + """ + Parse a trivia command. + + *Parameters* + ------------ + ctx: SlashContext + The context of the command. + answer: str + The answer, if any. + """ await self.bot.defer(ctx) + channelId = str(ctx.channel_id) if answer == "": - question, options, correctAnswer = self.triviaStart(str(ctx.channel_id)) + question, options, correctAnswer = self.triviaStart(channelId) if options != "": results = "**"+question+"**\n" for x, option in enumerate(options): @@ -98,21 +179,27 @@ class Trivia(): await asyncio.sleep(60) - self.triviaCountPoints(str(ctx.channel_id)) + self.triviaCountPoints(channelId) - self.bot.databaseFuncs.deleteGame("trivia questions",str(ctx.channel_id)) + deleteGameParams = ["trivia questions", channelId] + self.bot.databaseFuncs.deleteGame(*deleteGameParams) - 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") + self.bot.log("Time's up for the trivia question", channelId) + sendMessage = self.bot.longStrings["Trivia time up"] + formatParams = [chr(correctAnswer), options[correctAnswer-97]] + sendMessage = sendMessage.format(*formatParams) + await ctx.send(sendMessage) 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}**") + elif answer in ["a", "b", "c", "d"]: + userId = f"#{ctx.author.id}" + response = self.triviaAnswer(userId, channelId, answer) + if response is None: + userName = ctx.author.display_name + await ctx.send(f"{userName} 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)") + self.bot.log("I didn't understand that", channelId) + await ctx.send("I didn't understand that") diff --git a/resources/longStrings.json b/resources/longStrings.json index d1e1a0e..188fc7f 100644 --- a/resources/longStrings.json +++ b/resources/longStrings.json @@ -10,5 +10,7 @@ "Blackjack started": "Blackjack game started. Use \"/blackjack bet [amount]\" to enter the game within the next 30 seconds.", "Blackjack going on": "There's already a blackjack game going on. Try again in a few minutes.", "Stock value": "The current {} stock is valued at **{}** GwendoBucks", - "Stock parameters": "You must give both a stock name and an amount of GwendoBucks you wish to spend." + "Stock parameters": "You must give both a stock name and an amount of GwendoBucks you wish to spend.", + "Trivia going on": "There's already a trivia question going on. Try again in like, a minute", + "Trivia time up": "Time's up! The answer was \"*{}) {}*\". Anyone who answered that has gotten 1 GwendoBuck" } \ No newline at end of file From 22925833114d42888d53513f56a2c5f59194d636 Mon Sep 17 00:00:00 2001 From: NikolajDanger Date: Sun, 18 Apr 2021 20:09:13 +0200 Subject: [PATCH 07/23] :red_circle: Connect four --- funcs/games/connectFour.py | 1111 ++++++++++++++++++++++++++---------- resources/longStrings.json | 4 +- 2 files changed, 801 insertions(+), 314 deletions(-) diff --git a/funcs/games/connectFour.py b/funcs/games/connectFour.py index 88a8024..c0d511d 100644 --- a/funcs/games/connectFour.py +++ b/funcs/games/connectFour.py @@ -1,14 +1,42 @@ -import random -import copy -import math -import discord +""" +Contains the classes that deal with playing and drawing connect four. + +*Classes* +--------- + ConnectFour + DrawConnectFour +""" +import random # Used to shuffle players and by the ai to pick from +# similar options +import copy # Used to deepcopy boards +import math # Used for math.inf +import discord # Used for typehints, discord.file and to check whether +# the opponent in ConnectFour.start is a discord.User + +from PIL import Image, ImageDraw, ImageFont # Used by DrawConnectFour() +from discord_slash.context import SlashContext # Used for typehints +from typing import Union # Used for typehints + +ROWCOUNT = 6 +COLUMNCOUNT = 7 -from PIL import Image, ImageDraw, ImageFont class ConnectFour(): - def __init__(self,bot): + """ + Deals with connect four commands and logic. + + *Methods* + --------- + start(ctx: SlashContext, opponent: Union[int, Discord.User]) + placePiece(ctx: Union[SlashContext, discord.Message], + user: int, column: int) + surrender(ctx: SlashContext) + """ + + def __init__(self, bot): + """Initialize the class.""" self.bot = bot - self.draw = drawConnectFour(bot) + self.draw = DrawConnectFour(bot) self.AISCORES = { "middle": 3, "two in a row": 10, @@ -19,67 +47,84 @@ class ConnectFour(): "win": 10000, "avoid losing": 100 } + self.REACTIONS = ["1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣"] - self.ROWCOUNT = 6 - self.COLUMNCOUNT = 7 + async def start(self, ctx: SlashContext, + opponent: Union[int, discord.User]): + """ + Start a game of connect four. - # Starts the game - async def start(self, ctx, opponent): + *Parameters* + ------------ + ctx: SlashContext + The context of the slash command. + opponent: Union[int, discord.User] + The requested opponent. If it's an int, the opponent is + Gwendolyn with a difficulty equal to the value of the + int. The difficulty is equal to how many levels the AI + searches when minimaxing. + """ await self.bot.defer(ctx) user = f"#{ctx.author.id}" channel = str(ctx.channel_id) - game = self.bot.database["connect 4 games"].find_one({"_id":channel}) + game = self.bot.database["connect 4 games"].find_one({"_id": channel}) startedGame = False canStart = True - if game != None: - sendMessage = "There's already a connect 4 game going on in this channel" + if game is not None: + sendMessage = self.bot.longStrings["Connect 4 going on"] 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) + elif 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 challenged a difficulty that doesn't exist" + canStart = False + elif type(opponent) == discord.User: + 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 = "Difficulty doesn't exist" - logMessage = "They tried to play against a difficulty that doesn't exist" + 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: + opponent = f"#{opponent.id}" + difficulty = 5 + diffText = "" + else: + sendMessage = "You can't play against yourself" + logMessage = "They tried to play against themself" 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 - if ctx.author != opponent: - opponent = f"#{opponent.id}" - difficulty = 5 - diffText = "" - else: - sendMessage = "You can't play against yourself" - logMessage = "They tried to play against themself" - canStart = False - if canStart: - board = [[0 for _ in range(self.COLUMNCOUNT)] for _ in range(self.ROWCOUNT)] + x, y = COLUMNCOUNT, ROWCOUNT + board = [[0 for _ in range(x)] for _ in range(y)] 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) @@ -101,23 +146,42 @@ class ConnectFour(): # 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)) + boardsPath = "resources/games/connect4Boards/" + filePath = f"{boardsPath}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: + oldImagesPath = "resources/games/oldImages/" + oldImagePath = f"{oldImagesPath}connectFour{ctx.channel_id}" + with open(oldImagePath, "w") as f: f.write(str(oldImage.id)) if gwendoTurn: - await self.connectFourAI(ctx) + await self._connectFourAI(ctx) else: - reactions = ["1️⃣","2️⃣","3️⃣","4️⃣","5️⃣","6️⃣","7️⃣"] - for reaction in reactions: + for reaction in self.REACTIONS: await oldImage.add_reaction(reaction) - # Places a piece at the lowest available point in a specific column - async def placePiece(self, ctx, user, column): + async def placePiece(self, ctx: Union[SlashContext, discord.Message], + user: int, column: int): + """ + Place a piece on the board. + + *Parameters* + ------------ + ctx: Union[SlashContext, discord.Message] + The context of the command/reaction. ctx can only be + SlashContext if the piece is placed by the bot + immediately after the player starts the game with a + command. Otherwise, ctx will be the message the player + reacted to when placing a piece. + user: int + The player-number of the player placing the piece. + column: int + The column the player is placing the piece in. + """ channel = str(ctx.channel.id) - game = self.bot.database["connect 4 games"].find_one({"_id":channel}) + connect4Games = self.bot.database["connect 4 games"] + game = connect4Games.find_one({"_id": channel}) playerNumber = game["players"].index(user)+1 userName = self.bot.databaseFuncs.getName(user) placedPiece = False @@ -127,39 +191,48 @@ class ConnectFour(): logMessage = "There was no game in the channel" else: board = game["board"] - board = self.placeOnBoard(board, playerNumber, column) + board = self._placeOnBoard(board, playerNumber, column) 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}}) + updater = {"$set": {"board": board}} + connect4Games.update_one({"_id": channel}, updater) + turn = (game["turn"]+1) % 2 + updater = {"$set": {"turn": turn}} + connect4Games.update_one({"_id": channel}, updater) self.bot.log("Checking for win") - won, winDirection, winCoordinates = self.isWon(board) + won, winDirection, winCoordinates = self._isWon(board) if won != 0: gameWon = True - self.bot.database["connect 4 games"].update_one({"_id":channel},{"$set":{"winner":won}}) - self.bot.database["connect 4 games"].update_one({"_id":channel},{"$set":{"win direction":winDirection}}) - self.bot.database["connect 4 games"].update_one({"_id":channel}, - {"$set":{"win coordinates":winCoordinates}}) + updater = {"$set": {"winner": won}} + connect4Games.update_one({"_id": channel}, updater) + updater = {"$set": {"win direction": winDirection}} + connect4Games.update_one({"_id": channel}, updater) + updater = {"$set": {"win coordinates": winCoordinates}} + connect4Games.update_one({"_id": channel}, updater) - sendMessage = f"{userName} placed a piece in column {column+1} and won." + sendMessage = "{} placed a piece in column {} and won. " + sendMessage = sendMessage.format(userName, column+1) logMessage = f"{userName} won" winAmount = int(game["difficulty"])**2+5 if game["players"][won-1] != f"#{self.bot.user.id}": - sendMessage += " Adding "+str(winAmount)+" GwendoBucks to their account" + sendMessage += "Adding {} GwendoBucks to their account" + sendMessage = sendMessage.format(winAmount) elif 0 not in board[0]: gameWon = True sendMessage = "It's a draw!" logMessage = "The game ended in a draw" else: gameWon = False - otherUserName = self.bot.databaseFuncs.getName(game["players"][turn]) - sendMessage = f"{userName} placed a piece in column {column+1}. It's now {otherUserName}'s turn" + otherUserId = game["players"][turn] + otherUserName = self.bot.databaseFuncs.getName(otherUserId) + sendMessage = self.bot.longStrings["Connect 4 placed"] + formatParams = [userName, column+1, otherUserName] + sendMessage = sendMessage.format(*formatParams) logMessage = "They placed the piece" gwendoTurn = (game["players"][turn] == f"#{self.bot.user.id}") @@ -172,7 +245,8 @@ class ConnectFour(): if placedPiece: self.draw.drawImage(channel) - with open(f"resources/games/oldImages/connectFour{channel}", "r") as f: + oldImagePath = f"resources/games/oldImages/connectFour{channel}" + with open(oldImagePath, "r") as f: oldImage = await ctx.channel.fetch_message(int(f.read())) if oldImage is not None: @@ -181,56 +255,35 @@ class ConnectFour(): 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)) + oldImage = await ctx.channel.send(file=discord.File(filePath)) if gameWon: - self.endGame(channel) + self._endGame(channel) else: - with open(f"resources/games/oldImages/connectFour{channel}", "w") as f: + with open(oldImagePath, "w") as f: f.write(str(oldImage.id)) if gwendoTurn: - await self.connectFourAI(ctx) + await self._connectFourAI(ctx) else: - reactions = ["1️⃣","2️⃣","3️⃣","4️⃣","5️⃣","6️⃣","7️⃣"] - for reaction in reactions: + for reaction in self.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 + async def surrender(self, ctx: SlashContext): + """ + Surrender a connect four game. - for x, line in enumerate(board): - if line[column] == 0: - placementX = x - - board[placementX][placementY] = player - - 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 - async def surrender(self, ctx): + *Parameters* + ------------ + ctx: SlashContext + The context of the command. + """ await self.bot.defer(ctx) channel = str(ctx.channel_id) - game = self.bot.database["connect 4 games"].find_one({"_id":channel}) + 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 + winnerIndex = (loserIndex+1) % 2 winnerID = game["players"][winnerIndex] winnerName = self.bot.databaseFuncs.getName(winnerID) @@ -242,7 +295,8 @@ class ConnectFour(): sendMessage += f" Adding {reward} to their account" await ctx.send(sendMessage) - with open(f"resources/games/oldImages/connectFour{channel}", "r") as f: + oldImagePath = f"resources/games/oldImages/connectFour{channel}" + with open(oldImagePath, "r") as f: oldImage = await ctx.channel.fetch_message(int(f.read())) if oldImage is not None: @@ -250,142 +304,219 @@ class ConnectFour(): else: self.bot.log("The old image was already deleted") - self.endGame(channel) + self._endGame(channel) else: 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): + def _placeOnBoard(self, board: dict, player: int, column: int): + """ + Place a piece in a given board. + + This differs from placePiece() by not having anything to do + with any actual game. It just places the piece in a game dict. + + *Parameters* + ------------ + board: dict + The board to place the piece in. + player: int + The player who places the piece. + column: int + The column the piece is placed in. + + *Returns* + --------- + board: dict + The board with the placed piece. + """ + placementX, placementY = -1, column + + for x, line in enumerate(board): + if line[column] == 0: + placementX = x + break + + board[placementX][placementY] = player + + if placementX == -1: + return None + else: + return board + + def _endGame(self, channel: str): + 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) + + def _isWon(self, board: dict): won = 0 winDirection = "" - winCoordinates = [0,0] + winCoordinates = [0, 0] - for row in range(self.ROWCOUNT): - for place in range(self.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 <= self.COLUMNCOUNT-4: - pieces = [board[row][place+1],board[row][place+2],board[row][place+3]] + if place <= COLUMNCOUNT-4: + pieceOne = board[row][place+1] + pieceTwo = board[row][place+2] + pieceThree = board[row][place+3] + + pieces = [pieceOne, pieceTwo, pieceThree] else: pieces = [0] if all(x == piecePlayer for x in pieces): won = piecePlayer winDirection = "h" - winCoordinates = [row,place] + winCoordinates = [row, place] # Checks vertical - if row <= self.ROWCOUNT-4: - pieces = [board[row+1][place],board[row+2][place],board[row+3][place]] + if row <= ROWCOUNT-4: + pieceOne = board[row+1][place] + pieceTwo = board[row+2][place] + pieceThree = board[row+3][place] + + pieces = [pieceOne, pieceTwo, pieceThree] else: pieces = [0] if all(x == piecePlayer for x in pieces): won = piecePlayer winDirection = "v" - winCoordinates = [row,place] + winCoordinates = [row, place] # Checks right diagonal - if row <= self.ROWCOUNT-4 and place <= self.COLUMNCOUNT-4: - pieces = [board[row+1][place+1],board[row+2][place+2],board[row+3][place+3]] + goodRow = (row <= ROWCOUNT-4) + goodColumn = (place <= COLUMNCOUNT-4) + if goodRow and goodColumn: + pieceOne = board[row+1][place+1] + pieceTwo = board[row+2][place+2] + pieceThree = board[row+3][place+3] + + pieces = [pieceOne, pieceTwo, pieceThree] else: pieces = [0] if all(x == piecePlayer for x in pieces): won = piecePlayer winDirection = "r" - winCoordinates = [row,place] + winCoordinates = [row, place] # Checks left diagonal - if row <= self.ROWCOUNT-4 and place >= 3: - pieces = [board[row+1][place-1],board[row+2][place-2],board[row+3][place-3]] + if row <= ROWCOUNT-4 and place >= 3: + pieceOne = board[row+1][place-1] + pieceTwo = board[row+2][place-2] + pieceThree = board[row+3][place-3] + + pieces = [pieceOne, pieceTwo, pieceThree] else: pieces = [0] if all(x == piecePlayer for x in pieces): won = piecePlayer winDirection = "l" - winCoordinates = [row,place] - + winCoordinates = [row, place] return won, winDirection, winCoordinates - # Plays as the AI - async def connectFourAI(self, ctx): + async def _connectFourAI(self, ctx: SlashContext): + def outOfRange(possibleScores: list): + allowedRange = max(possibleScores)*(1-0.1) + moreThanOne = len(possibleScores) != 1 + return (min(possibleScores) <= allowedRange) and moreThanOne + channel = str(ctx.channel.id) self.bot.log("Figuring out best move") - game = self.bot.database["connect 4 games"].find_one({"_id":channel}) + game = self.bot.database["connect 4 games"].find_one({"_id": channel}) board = game["board"] player = game["players"].index(f"#{self.bot.user.id}")+1 difficulty = game["difficulty"] - scores = [-math.inf for _ in range(self.COLUMNCOUNT)] - for column in range(self.COLUMNCOUNT): + scores = [int(-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) + testBoard = self._placeOnBoard(testBoard, player, column) + if testBoard is not None: + _minimaxParams = [ + testBoard, + difficulty, + player % 2+1, + player, + int(-math.inf), + int(math.inf), + False + ] + scores[column] = await self._minimax(*_minimaxParams) self.bot.log(f"Best score for column {column} is {scores[column]}") possibleScores = scores.copy() - while (min(possibleScores) <= (max(possibleScores) - max(possibleScores)/10)) and len(possibleScores) != 1: + while outOfRange(possibleScores): possibleScores.remove(min(possibleScores)) - highest_score = random.choice(possibleScores) + highestScore = random.choice(possibleScores) - bestColumns = [i for i, x in enumerate(scores) if x == highest_score] + bestColumns = [i for i, x in enumerate(scores) if x == highestScore] placement = random.choice(bestColumns) await self.placePiece(ctx, f"#{self.bot.user.id}", placement) - # Calculates points for a board - def AICalcPoints(self,board,player): + def _AICalcPoints(self, board: dict, player: int): score = 0 - otherPlayer = player%2+1 + otherPlayer = player % 2+1 # Adds points for middle placement # Checks horizontal - for row in range(self.ROWCOUNT): + for row in range(ROWCOUNT): if board[row][3] == player: score += self.AISCORES["middle"] rowArray = [int(i) for i in list(board[row])] - for place in range(self.COLUMNCOUNT-3): + for place in range(COLUMNCOUNT-3): window = rowArray[place:place+4] - score += self.evaluateWindow(window,player,otherPlayer) + score += self._evaluateWindow(window, player, otherPlayer) # Checks Vertical - for column in range(self.COLUMNCOUNT): + for column in range(COLUMNCOUNT): columnArray = [int(i[column]) for i in list(board)] - for place in range(self.ROWCOUNT-3): + for place in range(ROWCOUNT-3): window = columnArray[place:place+4] - score += self.evaluateWindow(window,player,otherPlayer) + score += self._evaluateWindow(window, player, otherPlayer) # Checks right diagonal - for row in range(self.ROWCOUNT-3): - for place in range(self.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 row in range(ROWCOUNT-3): + for place in range(COLUMNCOUNT-3): + pieceOne = board[row][place] + pieceTwo = board[row+1][place+1] + pieceThree = board[row+2][place+2] + pieceFour = board[row+3][place+3] - for place in range(3,self.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) + window = [pieceOne, pieceTwo, pieceThree, pieceFour] + score += self._evaluateWindow(window, player, otherPlayer) + for place in range(3, COLUMNCOUNT): + pieceOne = board[row][place] + pieceTwo = board[row+1][place-1] + pieceThree = board[row+2][place-2] + pieceFour = board[row+3][place-3] - ## Checks if anyone has won - #won = isWon(board)[0] - - ## Add points if AI wins - #if won == player: - # score += self.AISCORES["win"] + window = [pieceOne, pieceTwo, pieceThree, pieceFour] + score += self._evaluateWindow(window, player, otherPlayer) return score - def evaluateWindow(self, window,player,otherPlayer): + def _evaluateWindow(self, window: list, player: int, otherPlayer: int): if window.count(player) == 4: return self.AISCORES["win"] elif window.count(player) == 3 and window.count(0) == 1: @@ -397,76 +528,498 @@ class ConnectFour(): else: return 0 - async def minimax(self, board, depth, player , originalPlayer, alpha, beta, maximizingPlayer): - #terminal = ((0 not in board[0]) or (isWon(board)[0] != 0)) + async def _minimax(self, board: dict, depth: int, player: int, + originalPlayer: int, alpha: int, beta: int, + maximizingPlayer: bool): + """ + Evaluate the minimax value of a board. + + *Parameters* + ------------ + board: dict + The board to evaluate. + depth: int + How many more levels to check. If 0, the current value + is returned. + player: int + The player who can place a piece. + originalPlayer: int + The AI player. + alpha, beta: int + Used with alpha/beta pruning, in order to prune options + that will never be picked. + maximizingPlayer: bool + Whether the current player is the one trying to + maximize their score. + + *Returns* + --------- + value: int + The minimax value of the board. + """ terminal = 0 not in board[0] - points = self.AICalcPoints(board,originalPlayer) - # The depth is how many moves ahead the computer checks. This value is the difficulty. + points = self._AICalcPoints(board, originalPlayer) + # The depth is how many moves ahead the computer checks. This + # value is the difficulty. if depth == 0 or terminal or (points > 5000 or points < -6000): return points if maximizingPlayer: - value = -math.inf - for column in range(0,self.COLUMNCOUNT): + value = int(-math.inf) + 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 += self.AISCORES["avoid losing"] - value = max(value,evaluation) - alpha = max(alpha,evaluation) - if beta <= alpha: break + testBoard = self._placeOnBoard(testBoard, player, column) + if testBoard is not None: + _minimaxParams = [ + testBoard, + depth-1, + (player % 2)+1, + originalPlayer, + alpha, + beta, + False + ] + evaluation = await self._minimax(*_minimaxParams) + if evaluation < -9000: + evaluation += self.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,self.COLUMNCOUNT): + value = int(math.inf) + 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 += self.AISCORES["avoid losing"] - value = min(value,evaluation) - beta = min(beta,evaluation) - if beta <= alpha: break + testBoard = self._placeOnBoard(testBoard, player, column) + if testBoard is not None: + _minimaxParams = [ + testBoard, + depth-1, + (player % 2)+1, + originalPlayer, + alpha, + beta, + True + ] + evaluation = await self._minimax(*_minimaxParams) + if evaluation < -9000: + evaluation += self.AISCORES["avoid losing"] + value = min(value, evaluation) + beta = min(beta, evaluation) + if beta <= alpha: + break return value -class drawConnectFour(): + +class DrawConnectFour(): + """ + Functions for drawing the connect four board. + + *Methods* + --------- + drawImage(channel: str) + + *Attributes* + ------------ + BORDER: int + The border around the game board. + GRIDBORDER: int + The border between the edge of the board and the piece + furthest towards the edge. + CORNERSIZE: int + The size of the corner circles. + BOARDOUTLINESIZE: int + How big the outline of the board is. + BOARDOUTLINECOLOR: RGBA tuple + The color of the outline of the board. + PIECEOUTLINESIZE: int + How big the outline of each piece is. + PIECEOUTLINECOLOR: RGB tuple + The color of the outline of each piece. + EMPTYOUTLINESIZE: int + How big the outline of an empty place on the board is. + EMPTYOUTLINECOLOR: RGB tuple + The color of the outline of an empty place on the board. + BOTTOMBORDER: int + The border at the bottom where the names are. + TEXTSIZE: int + The size of the text. + TEXTPADDING: int + How much padding to add to the the text. + WIDTH, HEIGHT: int + The size of the image, minus the bottom border + BACKGROUNDCOLOR: RGBA tuple + The color of the background. + PLAYER1COLOR, PLAYER2COLOR: RGB tuple + The color of each player. + BOARDCOLOR: RGB tuple + The color of the board + WINBARCOLOR: RGBA tuple + The color of the bar placed over the winning pieces. + PIECESIZE: int + The size of each piece. + FONT: FreeTypeFont + The font to use to write text. + PIECEGRIDSIZE: list + The size of each grid cell on the board. + PIECESTARTX, PIECESTARTY: int + Where the top left piece should be placed. + WINBARWHITE: RGBA tuple + White, but with the alpha set to winbarAlpha. + """ + def __init__(self, bot): + """Initialize the class.""" self.bot = bot + self.BORDER = 40 + self.GRIDBORDER = 40 + self.CORNERSIZE = 300 + self.BOARDOUTLINESIZE = 10 + self.BOARDOUTLINECOLOR = (0, 0, 0) + + self.PIECEOUTLINESIZE = 10 + self.PIECEOUTLINECOLOR = (244, 244, 248) + self.EMPTYOUTLINESIZE = 0 + self.EMPTYOUTLINECOLOR = (0, 0, 0) + + self.BOTTOMBORDER = 110 + self.TEXTSIZE = 100 + self.TEXTPADDING = 20 + self.WIDTH, self.HEIGHT = 2800, 2400 + self.BACKGROUNDCOLOR = (230, 230, 234, 255) + self.PLAYER1COLOR = (254, 74, 73) + self.PLAYER2COLOR = (254, 215, 102) + self.BOARDCOLOR = (42, 183, 202) + winbarAlpha = 160 + self.WINBARCOLOR = (250, 250, 250, 255) + self.PIECESIZE = 300 + fontPath = "resources/fonts/futura-bold.ttf" + + self.FONT = ImageFont.truetype(fontPath, self.TEXTSIZE) + + boardWidth = self.WIDTH-(2*(self.BORDER+self.GRIDBORDER)) + boardHeight = self.HEIGHT-(2*(self.BORDER+self.GRIDBORDER)) + boardSize = [boardWidth, boardHeight] + + pieceWidth = boardSize[0]//COLUMNCOUNT + pieceHeight = boardSize[1]//ROWCOUNT + self.PIECEGRIDSIZE = [pieceWidth, pieceHeight] + + pieceStart = (self.BORDER+self.GRIDBORDER)-(self.PIECESIZE//2) + self.PIECESTARTX = pieceStart+(self.PIECEGRIDSIZE[0]//2) + self.PIECESTARTY = pieceStart+(self.PIECEGRIDSIZE[1]//2) + + self.WINBARWHITE = (255, 255, 255, winbarAlpha) + # Draws the whole thing - def drawImage(self, channel): + def drawImage(self, channel: str): + """ + Draw an image of the connect four board. + + *Parameters* + ------------ + channel: str + The id of the channel the game is in. + """ self.bot.log("Drawing connect four board") - game = self.bot.database["connect 4 games"].find_one({"_id":channel}) + game = self.bot.database["connect 4 games"].find_one({"_id": channel}) board = game["board"] - border = 40 - gridBorder = 40 - cornerSize = 300 - boardOutlineSize = 10 - pieceOutlineSize = 10 - emptyOutlineSize = 0 - bottomBorder = 110 - exampleCircles = 100 - w, h = 2800,2400 - backgroundColor = (230,230,234,255) - boardOutlineColor = (0,0,0) - pieceOutlineColor = (244,244,248) - emptyOutlineColor = (0,0,0) - player1Color = (254,74,73) - player2Color = (254,215,102) - boardColor = (42,183,202) - placeSize = 300 - white = (255,255,255,160) - winBarColor = (250,250,250,255) + imageSize = (self.WIDTH, self.HEIGHT+self.BOTTOMBORDER) + background = Image.new("RGB", imageSize, self.BACKGROUNDCOLOR) + d = ImageDraw.Draw(background, "RGBA") - fnt = ImageFont.truetype('resources/fonts/futura-bold.ttf', exampleCircles) + self._drawBoard(d) - boardSize = [w-(2*(border+gridBorder)),h-(2*(border+gridBorder))] - placeGridSize = [math.floor(boardSize[0]/7),math.floor(boardSize[1]/6)] - pieceStartx = (border+gridBorder)+math.floor(placeGridSize[0]/2)-math.floor(placeSize/2) - pieceStarty = (border+gridBorder)+math.floor(placeGridSize[1]/2)-math.floor(placeSize/2) + self._drawPieces(d, board) + if game["winner"] != 0: + self._drawWin(background, game) + + self._drawFooter(d, game) + + background.save(f"resources/games/connect4Boards/board{channel}.png") + + def _drawBoard(self, d: ImageDraw): + # This whole part was the easiest way to make a rectangle with + # rounded corners and an outline + drawParams = { + "fill": self.BOARDCOLOR, + "outline": self.BOARDOUTLINECOLOR, + "width": self.BOARDOUTLINESIZE + } + + # - Corners -: + cornerPositions = [ + [ + ( + self.BORDER, + self.BORDER + ), ( + self.BORDER+self.CORNERSIZE, + self.BORDER+self.CORNERSIZE + ) + ], [ + ( + self.WIDTH-(self.BORDER+self.CORNERSIZE), + self.HEIGHT-(self.BORDER+self.CORNERSIZE) + ), ( + self.WIDTH-self.BORDER, + self.HEIGHT-self.BORDER + ) + ], [ + ( + self.BORDER, + self.HEIGHT-(self.BORDER+self.CORNERSIZE) + ), ( + self.BORDER+self.CORNERSIZE, + self.HEIGHT-self.BORDER + ) + ], [ + ( + self.WIDTH-(self.BORDER+self.CORNERSIZE), + self.BORDER + ), ( + self.WIDTH-self.BORDER, + self.BORDER+self.CORNERSIZE + ) + ] + ] + + d.ellipse(cornerPositions[0], **drawParams) + d.ellipse(cornerPositions[1], **drawParams) + d.ellipse(cornerPositions[2], **drawParams) + d.ellipse(cornerPositions[3], **drawParams) + + # - Rectangle -: + rectanglePositions = [ + [ + ( + self.BORDER+(self.CORNERSIZE//2), + self.BORDER + ), ( + self.WIDTH-(self.BORDER+(self.CORNERSIZE//2)), + self.HEIGHT-self.BORDER + ) + ], [ + ( + self.BORDER, + self.BORDER+(self.CORNERSIZE//2) + ), ( + self.WIDTH-self.BORDER, + self.HEIGHT-(self.BORDER+(self.CORNERSIZE//2)) + ) + ] + ] + d.rectangle(rectanglePositions[0], **drawParams) + d.rectangle(rectanglePositions[1], **drawParams) + + # - Removing outline on the inside -: + cleanUpPositions = [ + [ + ( + self.BORDER+(self.CORNERSIZE//2), + self.BORDER+(self.CORNERSIZE//2) + ), ( + self.WIDTH-(self.BORDER+(self.CORNERSIZE//2)), + self.HEIGHT-(self.BORDER+(self.CORNERSIZE//2)) + ) + ], [ + ( + self.BORDER+(self.CORNERSIZE/2), + self.BORDER+self.BOARDOUTLINESIZE + ), ( + self.WIDTH-(self.BORDER+(self.CORNERSIZE//2)), + self.HEIGHT-(self.BORDER+self.BOARDOUTLINESIZE) + ) + ], [ + ( + self.BORDER+self.BOARDOUTLINESIZE, + self.BORDER+(self.CORNERSIZE//2) + ), ( + self.WIDTH-(self.BORDER+self.BOARDOUTLINESIZE), + self.HEIGHT-(self.BORDER+(self.CORNERSIZE//2)) + ) + ] + ] + d.rectangle(cleanUpPositions[0], fill=self.BOARDCOLOR) + d.rectangle(cleanUpPositions[1], fill=self.BOARDCOLOR) + d.rectangle(cleanUpPositions[2], fill=self.BOARDCOLOR) + + def _drawPieces(self, d: ImageDraw, board: list): + for x, line in enumerate(board): + for y, piece in enumerate(line): + + if piece == 1: + pieceColor = self.PLAYER1COLOR + outlineWidth = self.PIECEOUTLINESIZE + outlineColor = self.PIECEOUTLINECOLOR + elif piece == 2: + pieceColor = self.PLAYER2COLOR + outlineWidth = self.PIECEOUTLINESIZE + outlineColor = self.PIECEOUTLINECOLOR + else: + pieceColor = self.BACKGROUNDCOLOR + outlineWidth = self.EMPTYOUTLINESIZE + outlineColor = self.EMPTYOUTLINECOLOR + + startX = self.PIECESTARTX + self.PIECEGRIDSIZE[0]*y + startY = self.PIECESTARTY + self.PIECEGRIDSIZE[1]*x + + position = [ + (startX, startY), + (startX+self.PIECESIZE, startY+self.PIECESIZE) + ] + + pieceParams = { + "fill": pieceColor, + "outline": outlineColor, + "width": outlineWidth + } + d.ellipse(position, **pieceParams) + + def _drawWin(self, background: Image, game: dict): + """ + Draw the bar that shows the winning pieces. + + *Parameters* + ------------ + background: Image + The image of the board. + game: dict + The game data. + """ + coordinates = game["win coordinates"] + start = self.BORDER + self.GRIDBORDER + startX = start + self.PIECEGRIDSIZE[0]*coordinates[1] + startY = start + self.PIECEGRIDSIZE[1]*coordinates[0] + a = (self.PIECEGRIDSIZE[0]*4-self.GRIDBORDER-self.BORDER)**2 + b = (self.PIECEGRIDSIZE[1]*4-self.GRIDBORDER-self.BORDER)**2 + diagonalLength = (math.sqrt(a+b))/self.PIECEGRIDSIZE[0] + sizeRatio = self.PIECEGRIDSIZE[1]/self.PIECEGRIDSIZE[0] + diagonalAngle = math.degrees(math.atan(sizeRatio)) + + if game["win direction"] == "h": + imageSize = (self.PIECEGRIDSIZE[0]*4, self.PIECEGRIDSIZE[1]) + winBar = Image.new("RGBA", imageSize, (0, 0, 0, 0)) + winD = ImageDraw.Draw(winBar) + drawPositions = [ + [ + (0, 0), + (self.PIECEGRIDSIZE[0], self.PIECEGRIDSIZE[1]) + ], [ + ((self.PIECEGRIDSIZE[0]*3), 0), + (self.PIECEGRIDSIZE[0]*4, self.PIECEGRIDSIZE[1]) + ], [ + (int(self.PIECEGRIDSIZE[0]*0.5), 0), + (int(self.PIECEGRIDSIZE[0]*3.5), self.PIECEGRIDSIZE[1]) + ] + ] + winD.ellipse(drawPositions[0], fill=self.WINBARWHITE) + winD.ellipse(drawPositions[1], fill=self.WINBARWHITE) + winD.rectangle(drawPositions[2], fill=self.WINBARWHITE) + + elif game["win direction"] == "v": + imageSize = (self.PIECEGRIDSIZE[0], self.PIECEGRIDSIZE[1]*4) + winBar = Image.new("RGBA", imageSize, (0, 0, 0, 0)) + winD = ImageDraw.Draw(winBar) + drawPositions = [ + [ + (0, 0), + (self.PIECEGRIDSIZE[0], self.PIECEGRIDSIZE[1]) + ], [ + (0, (self.PIECEGRIDSIZE[1]*3)), + (self.PIECEGRIDSIZE[0], self.PIECEGRIDSIZE[1]*4) + ], [ + (0, (int(self.PIECEGRIDSIZE[1]*0.5))), + (self.PIECEGRIDSIZE[0], int(self.PIECEGRIDSIZE[1]*3.5)) + ] + ] + winD.ellipse(drawPositions[0], fill=self.WINBARWHITE) + winD.ellipse(drawPositions[1], fill=self.WINBARWHITE) + winD.rectangle(drawPositions[2], fill=self.WINBARWHITE) + + elif game["win direction"] == "r": + imageWidth = int(self.PIECEGRIDSIZE[0]*diagonalLength) + imageSize = (imageWidth, self.PIECEGRIDSIZE[1]) + winBar = Image.new("RGBA", imageSize, (0, 0, 0, 0)) + winD = ImageDraw.Draw(winBar) + drawPositions = [ + [ + ( + 0, + 0 + ), ( + self.PIECEGRIDSIZE[0], + self.PIECEGRIDSIZE[1] + ) + ], [ + ( + (self.PIECEGRIDSIZE[0]*(diagonalLength-1)), + 0 + ), ( + self.PIECEGRIDSIZE[0]*diagonalLength, + self.PIECEGRIDSIZE[1] + ) + ], [ + ( + int(self.PIECEGRIDSIZE[0]*0.5), + 0 + ), ( + int(self.PIECEGRIDSIZE[0]*(diagonalLength-0.5)), + self.PIECEGRIDSIZE[1] + ) + ] + ] + winD.ellipse(drawPositions[0], fill=self.WINBARWHITE) + winD.ellipse(drawPositions[1], fill=self.WINBARWHITE) + winD.rectangle(drawPositions[2], fill=self.WINBARWHITE) + winBar = winBar.rotate(-diagonalAngle, expand=1) + startX -= 90 + startY -= 100 + + elif game["win direction"] == "l": + imageWidth = int(self.PIECEGRIDSIZE[0]*diagonalLength) + imageSize = (imageWidth, self.PIECEGRIDSIZE[1]) + winBar = Image.new("RGBA", imageSize, (0, 0, 0, 0)) + winD = ImageDraw.Draw(winBar) + drawPositions = [ + [ + (0, 0), + (self.PIECEGRIDSIZE[0], self.PIECEGRIDSIZE[1]) + ], [ + ( + (self.PIECEGRIDSIZE[0]*(diagonalLength-1)), + 0 + ), ( + self.PIECEGRIDSIZE[0]*diagonalLength, + self.PIECEGRIDSIZE[1] + ) + ], [ + ( + int(self.PIECEGRIDSIZE[0]*0.5), + 0 + ), ( + int(self.PIECEGRIDSIZE[0]*(diagonalLength-0.5)), + self.PIECEGRIDSIZE[1] + ) + ] + ] + winD.ellipse(drawPositions[0], fill=self.WINBARWHITE) + winD.ellipse(drawPositions[1], fill=self.WINBARWHITE) + winD.rectangle(drawPositions[2], fill=self.WINBARWHITE) + winBar = winBar.rotate(diagonalAngle, expand=1) + startX -= self.PIECEGRIDSIZE[0]*3 + 90 + startY -= self.GRIDBORDER + 60 + + mask = winBar.copy() + + winBarImage = Image.new("RGBA", mask.size, color=self.WINBARCOLOR) + background.paste(winBarImage, (startX, startY), mask) + + def _drawFooter(self, d: ImageDraw, game: dict): if game["players"][0] == "Gwendolyn": player1 = "Gwendolyn" else: @@ -477,107 +1030,39 @@ class drawConnectFour(): else: player2 = self.bot.databaseFuncs.getName(game["players"][1]) + exampleHeight = self.HEIGHT - self.BORDER + exampleHeight += (self.BOTTOMBORDER+self.BORDER)//2 - self.TEXTSIZE//2 + textWidth = self.FONT.getsize(player2)[0] + exampleRPadding = self.BORDER+self.TEXTSIZE+textWidth+self.TEXTPADDING + circlePositions = [ + [ + ( + self.BORDER, + exampleHeight + ), ( + self.BORDER+self.TEXTSIZE, + exampleHeight+self.TEXTSIZE + ) + ], [ + ( + self.WIDTH-exampleRPadding, + exampleHeight + ), ( + self.WIDTH-self.BORDER-textWidth-self.TEXTPADDING, + exampleHeight+self.TEXTSIZE + ) + ] + ] + circleParams = { + "outline": self.BOARDOUTLINECOLOR, + "width": 3 + } + d.ellipse(circlePositions[0], fill=self.PLAYER1COLOR, **circleParams) + d.ellipse(circlePositions[1], fill=self.PLAYER2COLOR, **circleParams) - background = Image.new("RGB", (w,h+bottomBorder),backgroundColor) - d = ImageDraw.Draw(background,"RGBA") - - # This whole part was the easiest way to make a rectangle with rounded corners and an outline - # - Corners: - d.ellipse([(border,border),(border+cornerSize,border+cornerSize)],fill=boardColor,outline=boardOutlineColor,width=boardOutlineSize) - d.ellipse([(w-(border+cornerSize),h-(border+cornerSize)),(w-border,h-border)],fill=boardColor,outline=boardOutlineColor,width=boardOutlineSize) - d.ellipse([(border,h-(border+cornerSize)),(border+cornerSize,h-border)],fill=boardColor,outline=boardOutlineColor,width=boardOutlineSize) - d.ellipse([(w-(border+cornerSize),border),(w-border,border+cornerSize)],fill=boardColor,outline=boardOutlineColor,width=boardOutlineSize) - # - Rectangle: - d.rectangle([(border+math.floor(cornerSize/2),border),(w-(border+math.floor(cornerSize/2)),h-border)],fill=boardColor,outline=boardOutlineColor,width=boardOutlineSize) - d.rectangle([(border,border+math.floor(cornerSize/2)),(w-border,h-(border+math.floor(cornerSize/2)))],fill=boardColor,outline=boardOutlineColor,width=boardOutlineSize) - # - Removing outline on the inside: - d.rectangle([(border+math.floor(cornerSize/2),border+math.floor(cornerSize/2)),(w-(border+math.floor(cornerSize/2)),h-(border+math.floor(cornerSize/2)))],fill=boardColor) - d.rectangle([(border+math.floor(cornerSize/2),border+boardOutlineSize),(w-(border+math.floor(cornerSize/2)),h-(border+boardOutlineSize))],fill=boardColor) - d.rectangle([(border+boardOutlineSize,border+math.floor(cornerSize/2)),(w-(border+boardOutlineSize),h-(border+math.floor(cornerSize/2)))],fill=boardColor) - - for x, line in enumerate(board): - for y, piece in enumerate(line): - - if piece == 1: - pieceColor = player1Color - outlineWidth = pieceOutlineSize - outlineColor = pieceOutlineColor - elif piece == 2: - pieceColor = player2Color - outlineWidth = pieceOutlineSize - outlineColor = pieceOutlineColor - else: - pieceColor = backgroundColor - outlineWidth = emptyOutlineSize - outlineColor = emptyOutlineColor - - startx = pieceStartx + placeGridSize[0]*y - starty = pieceStarty + placeGridSize[1]*x - - d.ellipse([(startx,starty),(startx+placeSize,starty+placeSize)],fill=pieceColor,outline=outlineColor,width=outlineWidth) - - if game["winner"] != 0: - coordinates = game["win coordinates"] - startx = border + placeGridSize[0]*coordinates[1] + gridBorder - starty = border + placeGridSize[1]*coordinates[0] + gridBorder - a = (placeGridSize[0]*4-gridBorder-border)**2 - b = (placeGridSize[1]*4-gridBorder-border)**2 - diagonalLength = (math.sqrt(a+b))/placeGridSize[0] - diagonalAngle = math.degrees(math.atan(placeGridSize[1]/placeGridSize[0])) - - if game["win direction"] == "h": - winBar = Image.new("RGBA",(placeGridSize[0]*4,placeGridSize[1]),(0,0,0,0)) - winD = ImageDraw.Draw(winBar) - winD.ellipse([(0,0),(placeGridSize[0],placeGridSize[1])],fill=white) - winD.ellipse([((placeGridSize[0]*3),0),(placeGridSize[0]*4,placeGridSize[1])],fill=white) - winD.rectangle([(int(placeGridSize[0]*0.5),0),(int(placeGridSize[0]*3.5),placeGridSize[1])],fill=white) - - elif game["win direction"] == "v": - winBar = Image.new("RGBA",(placeGridSize[0],placeGridSize[1]*4),(0,0,0,0)) - winD = ImageDraw.Draw(winBar) - winD.ellipse([(0,0),(placeGridSize[0],placeGridSize[1])],fill=white) - winD.ellipse([(0,(placeGridSize[1]*3)),(placeGridSize[0],placeGridSize[1]*4)],fill=white) - winD.rectangle([0,(int(placeGridSize[1]*0.5)),(placeGridSize[0],int(placeGridSize[1]*3.5))],fill=white) - - elif game["win direction"] == "r": - winBar = Image.new("RGBA",(int(placeGridSize[0]*diagonalLength),placeGridSize[1]),(0,0,0,0)) - winD = ImageDraw.Draw(winBar) - winD.ellipse([(0,0),(placeGridSize[0],placeGridSize[1])],fill=white) - winD.ellipse([((placeGridSize[0]*(diagonalLength-1)),0),(placeGridSize[0]*diagonalLength,placeGridSize[1])],fill=white) - winD.rectangle([(int(placeGridSize[0]*0.5),0),(int(placeGridSize[0]*(diagonalLength-0.5)),placeGridSize[1])],fill=white) - winBar = winBar.rotate(-diagonalAngle,expand=1) - startx -= 90 - starty -= 100 - - elif game["win direction"] == "l": - winBar = Image.new("RGBA",(int(placeGridSize[0]*diagonalLength),placeGridSize[1]),(0,0,0,0)) - winD = ImageDraw.Draw(winBar) - winD.ellipse([(0,0),(placeGridSize[0],placeGridSize[1])],fill=white) - winD.ellipse([((placeGridSize[0]*(diagonalLength-1)),0),(placeGridSize[0]*diagonalLength,placeGridSize[1])],fill=white) - winD.rectangle([(int(placeGridSize[0]*0.5),0),(int(placeGridSize[0]*(diagonalLength-0.5)),placeGridSize[1])],fill=white) - winBar = winBar.rotate(diagonalAngle,expand=1) - startx -= placeGridSize[0]*3 + 90 - starty -= gridBorder + 60 - - - mask = winBar.copy()#.convert("L") - #mask.putalpha(128) - #mask.save("test.png") - - winBarImage = Image.new("RGBA",mask.size,color=winBarColor) - background.paste(winBarImage,(startx,starty),mask) - - # Bottom - textPadding = 20 - - exampleHeight = h - border + int((bottomBorder+border)/2) - int(exampleCircles/2) - d.ellipse([(border,exampleHeight),(border+exampleCircles),(exampleHeight+exampleCircles)],fill=player1Color,outline=boardOutlineColor,width=3) - d.text((border+exampleCircles+textPadding,exampleHeight),player1,font=fnt,fill=(0,0,0)) - - textWidth = fnt.getsize(player2)[0] - d.ellipse([(w-border-exampleCircles-textWidth-textPadding,exampleHeight),(w-border-textWidth-textPadding),(exampleHeight+exampleCircles)],fill=player2Color,outline=boardOutlineColor,width=3) - d.text((w-border-textWidth,exampleHeight),player2,font=fnt,fill=(0,0,0)) - - - background.save("resources/games/connect4Boards/board"+channel+".png") - + textPositions = [ + (self.BORDER+self.TEXTSIZE+self.TEXTPADDING, exampleHeight), + (self.WIDTH-self.BORDER-textWidth, exampleHeight) + ] + d.text(textPositions[0], player1, font=self.FONT, fill=(0, 0, 0)) + d.text(textPositions[1], player2, font=self.FONT, fill=(0, 0, 0)) diff --git a/resources/longStrings.json b/resources/longStrings.json index 188fc7f..c0f1de8 100644 --- a/resources/longStrings.json +++ b/resources/longStrings.json @@ -12,5 +12,7 @@ "Stock value": "The current {} stock is valued at **{}** GwendoBucks", "Stock parameters": "You must give both a stock name and an amount of GwendoBucks you wish to spend.", "Trivia going on": "There's already a trivia question going on. Try again in like, a minute", - "Trivia time up": "Time's up! The answer was \"*{}) {}*\". Anyone who answered that has gotten 1 GwendoBuck" + "Trivia time up": "Time's up! The answer was \"*{}) {}*\". Anyone who answered that has gotten 1 GwendoBuck", + "Connect 4 going on": "There's already a connect 4 game going on in this channel", + "Connect 4 placed": "{} placed a piece in column {}. It's now {}'s turn" } \ No newline at end of file From 431f423b4185984ab8381ae55fbabf73a71acc81 Mon Sep 17 00:00:00 2001 From: NikolajDanger Date: Sun, 18 Apr 2021 21:15:41 +0200 Subject: [PATCH 08/23] :sparkles: Hangman --- funcs/games/hangman.py | 261 +++++++++++++++++++++++-------------- resources/longStrings.json | 5 +- 2 files changed, 169 insertions(+), 97 deletions(-) diff --git a/funcs/games/hangman.py b/funcs/games/hangman.py index f330000..8da9592 100644 --- a/funcs/games/hangman.py +++ b/funcs/games/hangman.py @@ -1,32 +1,59 @@ -import json, urllib, datetime, string, discord -import math, random +import json +import requests +import datetime +import string +import discord +import math +import random +from discord_slash.context import SlashContext from PIL import ImageDraw, Image, ImageFont + class Hangman(): - def __init__(self,bot): + def __init__(self, bot): self.bot = bot self.draw = DrawHangman(bot) - self.APIURL = "https://api.wordnik.com/v4/words.json/randomWords?hasDictionaryDef=true&minCorpusCount=5000&maxCorpusCount=-1&minDictionaryCount=1&maxDictionaryCount=-1&minLength=3&maxLength=11&limit=1&api_key=" + self.APIURL = "https://api.wordnik.com/v4/words.json/randomWords?" + apiKey = self.bot.credentials.wordnikKey + self.APIPARAMS = { + "hasDictionaryDef": True, + "minCorpusCount": 5000, + "maxCorpusCount": -1, + "minDictionaryCount": 1, + "maxDictionaryCount": -1, + "minLength": 3, + "maxLength": 11, + "limit": 1, + "api_key": apiKey + } - async def start(self, ctx): + async def start(self, ctx: SlashContext): await self.bot.defer(ctx) channel = str(ctx.channel_id) user = f"#{ctx.author.id}" - game = self.bot.database["hangman games"].find_one({"_id":channel}) + 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 + if game is None: word = "-" while "-" in word or "." in word: - with urllib.request.urlopen(self.APIURL+apiKey) as p: - word = list(json.load(p)[0]["word"].upper()) + response = requests.get(self.APIURL, params=self.APIPARAMS) + word = list(response.json()[0]["word"].upper()) + self.bot.log("Found the word \""+"".join(word)+"\"") guessed = [False] * len(word) - gameID = datetime.datetime.now().strftime('%Y%m%d%H%M%S') - newGame = {"_id":channel,"player" : user,"guessed letters" : [],"word" : word,"game ID" : gameID,"misses" : 0,"guessed" : guessed} + gameID = datetime.datetime.now().strftime("%Y%m%d%H%M%S") + newGame = { + "_id": channel, + "player": user, + "guessed letters": [], + "word": word, + "game ID": gameID, + "misses": 0, + "guessed": guessed + } self.bot.database["hangman games"].insert_one(newGame) remainingLetters = list(string.ascii_uppercase) @@ -38,17 +65,21 @@ class Hangman(): startedGame = True else: logMessage = "There was already a game going on" - sendMessage = "There's already a Hangman game going on in the channel" + sendMessage = self.bot.longStrings["Hangman going on"] self.bot.log(logMessage) await ctx.send(sendMessage) if startedGame: - filePath = f"resources/games/hangmanBoards/hangmanBoard{channel}.png" - newImage = await ctx.channel.send(file = discord.File(filePath)) + boardsPath = "resources/games/hangmanBoards/" + filePath = f"{boardsPath}hangmanBoard{channel}.png" + newImage = await ctx.channel.send(file=discord.File(filePath)) - blankMessage = await ctx.channel.send("_ _") - reactionMessages = {newImage : remainingLetters[:15], blankMessage : remainingLetters[15:]} + blankMessage = await ctx.channel.send("_ _") + reactionMessages = { + newImage: remainingLetters[:15], + blankMessage: remainingLetters[15:] + } oldMessages = f"{newImage.id}\n{blankMessage.id}" @@ -60,7 +91,7 @@ class Hangman(): emoji = chr(ord(letter)+127397) await message.add_reaction(emoji) - async def stop(self, ctx): + async def stop(self, ctx: SlashContext): channel = str(ctx.channel.id) game = self.bot.database["hangman games"].find_one({"_id": channel}) @@ -69,7 +100,7 @@ class Hangman(): 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}) + self.bot.database["hangman games"].delete_one({"_id": channel}) with open(f"resources/games/oldImages/hangman{channel}", "r") as f: messages = f.read().splitlines() @@ -81,11 +112,12 @@ class Hangman(): await ctx.send("Game stopped") - async def guess(self, message, user, guess): + async def guess(self, message: discord.Message, user: str, guess: str): channel = str(message.channel.id) - game = self.bot.database["hangman games"].find_one({"_id":channel}) + hangmanGames = self.bot.database["hangman games"] + game = hangmanGames.find_one({"_id": channel}) - gameExists = (game != None) + gameExists = (game is not None) singleLetter = (len(guess) == 1 and guess.isalpha()) newGuess = (guess not in game["guessed letters"]) validGuess = (gameExists and singleLetter and newGuess) @@ -97,35 +129,40 @@ class Hangman(): 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}}) + updater = {"$set": {f"guessed.{x}": True}} + hangmanGames.update_one({"_id": channel}, updater) if correctGuess == 0: - self.bot.database["hangman games"].update_one({"_id":channel},{"$inc":{"misses":1}}) + updater = {"$inc": {"misses": 1}} + hangmanGames.update_one({"_id": channel}, updater) - self.bot.database["hangman games"].update_one({"_id":channel},{"$push":{"guessed letters":guess}}) + updater = {"$push": {"guessed letters": guess}} + hangmanGames.update_one({"_id": channel}, updater) remainingLetters = list(string.ascii_uppercase) - game = self.bot.database["hangman games"].find_one({"_id":channel}) + game = hangmanGames.find_one({"_id": channel}) for letter in game["guessed letters"]: remainingLetters.remove(letter) if correctGuess == 1: - sendMessage = f"Guessed {guess}. There was 1 {guess} in the word." + sendMessage = "Guessed {}. There was 1 {} in the word." + sendMessage = sendMessage.format(guess, guess) else: - sendMessage = f"Guessed {guess}. There were {correctGuess} {guess}s in the word." + sendMessage = "Guessed {}. There were {} {}s in the word." + sendMessage = sendMessage.format(guess, correctGuess, guess) 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." + hangmanGames.delete_one({"_id": channel}) + sendMessage += self.bot.longStrings["Hangman lost 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" + elif all(game["guessed"]): + hangmanGames.delete_one({"_id": channel}) + self.bot.money.addMoney(user, 15) + sendMessage += self.bot.longStrings["Hangman guessed word"] remainingLetters = [] await message.channel.send(sendMessage) @@ -138,23 +175,28 @@ class Hangman(): 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)) + boardsPath = "resources/games/hangmanBoards/" + filePath = f"{boardsPath}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:]} + blankMessage = await message.channel.send("_ _") + reactionMessages = { + newImage: remainingLetters[:15], + blankMessage: remainingLetters[15:] + } else: blankMessage = "" - reactionMessages = {newImage : remainingLetters} + 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: + oldImagePath = f"resources/games/oldImages/hangman{channel}" + with open(oldImagePath, "w") as f: f.write(oldMessages) for message, letters in reactionMessages.items(): @@ -162,58 +204,85 @@ class Hangman(): emoji = chr(ord(letter)+127397) await message.add_reaction(emoji) + class DrawHangman(): - def __init__(self,bot): + def __init__(self, bot): self.bot = bot self.CIRCLESIZE = 120 self.LINEWIDTH = 12 - self.BODYSIZE =210 + self.BODYSIZE = 210 self.LIMBSIZE = 60 self.ARMPOSITION = 60 - self.MANX = (self.LIMBSIZE*2)+self.LINEWIDTH*4 - self.MANY = (self.CIRCLESIZE+self.BODYSIZE+self.LIMBSIZE)+self.LINEWIDTH*4 + MANPADDING = self.LINEWIDTH*4 + self.MANX = (self.LIMBSIZE*2)+MANPADDING + self.MANY = (self.CIRCLESIZE+self.BODYSIZE+self.LIMBSIZE)+MANPADDING self.LETTERLINELENGTH = 90 self.LETTERLINEDISTANCE = 30 - self.GALLOWX, self.GALLOWY = 360,600 + self.GALLOWX, self.GALLOWY = 360, 600 self.GOLDENRATIO = 1-(1 / ((1 + 5 ** 0.5) / 2)) LETTERSIZE = 75 TEXTSIZE = 70 - self.FONT = ImageFont.truetype('resources/fonts/comic-sans-bold.ttf', LETTERSIZE) - self.SMALLFONT = ImageFont.truetype('resources/fonts/comic-sans-bold.ttf', TEXTSIZE) - def calcDeviance(self,preDev,preDevAcc,posChange,maxmin,maxAcceleration): - devAcc = preDevAcc + random.uniform(-posChange,posChange) - if devAcc > maxmin * maxAcceleration: devAcc = maxmin * maxAcceleration - elif devAcc < -maxmin * maxAcceleration: devAcc = -maxmin * maxAcceleration + FONTPATH = "resources/fonts/comic-sans-bold.ttf" + self.FONT = ImageFont.truetype(FONTPATH, LETTERSIZE) + self.SMALLFONT = ImageFont.truetype(FONTPATH, TEXTSIZE) - dev = preDev + devAcc - if dev > maxmin: dev = maxmin - elif dev < -maxmin: dev = -maxmin - return dev, devAcc + def calcDeviance(self, preDeviance, preDevianceAccuracy, positionChange, + maxmin, maxAcceleration): + randomDeviance = random.uniform(-positionChange, positionChange) + devianceAccuracy = preDevianceAccuracy + randomDeviance + if devianceAccuracy > maxmin * maxAcceleration: + devianceAccuracy = maxmin * maxAcceleration + elif devianceAccuracy < -maxmin * maxAcceleration: + devianceAccuracy = -maxmin * maxAcceleration + + deviance = preDeviance + devianceAccuracy + if deviance > maxmin: + deviance = maxmin + elif deviance < -maxmin: + deviance = -maxmin + return deviance, devianceAccuracy def badCircle(self): - background = Image.new("RGBA",(self.CIRCLESIZE+(self.LINEWIDTH*3),self.CIRCLESIZE+(self.LINEWIDTH*3)),color=(0,0,0,0)) + circlePadding = (self.LINEWIDTH*3) + imageWidth = self.CIRCLESIZE+circlePadding + imageSize = (imageWidth, imageWidth) + background = Image.new("RGBA", imageSize, color=(0, 0, 0, 0)) - d = ImageDraw.Draw(background,"RGBA") + d = ImageDraw.Draw(background, "RGBA") middle = (self.CIRCLESIZE+(self.LINEWIDTH*3))/2 - devx = 0 - devy = 0 - devAccx = 0 - devAccy = 0 - start = random.randint(-100,-80) - degreesAmount = 360 + random.randint(-10,30) + devianceX = 0 + devianceY = 0 + devianceAccuracyX = 0 + devianceAccuracyY = 0 + start = random.randint(-100, -80) + degreesAmount = 360 + random.randint(-10, 30) for degree in range(degreesAmount): - devx, devAccx = self.calcDeviance(devx,devAccx,self.LINEWIDTH/100,self.LINEWIDTH,0.03) - devy, devAccy = self.calcDeviance(devy,devAccy,self.LINEWIDTH/100,self.LINEWIDTH,0.03) + devianceXParams = [ + devianceX, + devianceAccuracyX, + self.LINEWIDTH/100, + self.LINEWIDTH, + 0.03 + ] + devianceYParams = [ + devianceY, + devianceAccuracyY, + self.LINEWIDTH/100, + self.LINEWIDTH, + 0.03 + ] + devianceX, devianceAccuracyX = self.calcDeviance(*devianceXParams) + devianceY, devianceAccuracyY = self.calcDeviance(*devianceYParams) - x = middle + (math.cos(math.radians(degree+start)) * (self.CIRCLESIZE/2)) - (self.LINEWIDTH/2) + devx - y = middle + (math.sin(math.radians(degree+start)) * (self.CIRCLESIZE/2)) - (self.LINEWIDTH/2) + devy + x = middle + (math.cos(math.radians(degree+start)) * (self.CIRCLESIZE/2)) - (self.LINEWIDTH/2) + devianceX + y = middle + (math.sin(math.radians(degree+start)) * (self.CIRCLESIZE/2)) - (self.LINEWIDTH/2) + devianceY d.ellipse([(x,y),(x+self.LINEWIDTH,y+self.LINEWIDTH)],fill=(0,0,0,255)) @@ -227,21 +296,21 @@ class DrawHangman(): background = Image.new("RGBA",(w,h),color=(0,0,0,0)) d = ImageDraw.Draw(background,"RGBA") - devx = random.randint(-int(self.LINEWIDTH/3),int(self.LINEWIDTH/3)) - devy = 0 - devAccx = 0 - devAccy = 0 + devianceX = random.randint(-int(self.LINEWIDTH/3),int(self.LINEWIDTH/3)) + devianceY = 0 + devianceAccuracyX = 0 + devianceAccuracyY = 0 for pixel in range(length): - devx, devAccx = self.calcDeviance(devx,devAccx,self.LINEWIDTH/1000,self.LINEWIDTH,0.004) - devy, devAccy = self.calcDeviance(devy,devAccy,self.LINEWIDTH/1000,self.LINEWIDTH,0.004) + devianceX, devianceAccuracyX = self.calcDeviance(devianceX,devianceAccuracyX,self.LINEWIDTH/1000,self.LINEWIDTH,0.004) + devianceY, devianceAccuracyY = self.calcDeviance(devianceY,devianceAccuracyY,self.LINEWIDTH/1000,self.LINEWIDTH,0.004) if rotated: - x = self.LINEWIDTH + pixel + devx - y = self.LINEWIDTH + devy + x = self.LINEWIDTH + pixel + devianceX + y = self.LINEWIDTH + devianceY else: - x = self.LINEWIDTH + devx - y = self.LINEWIDTH + pixel + devy + x = self.LINEWIDTH + devianceX + y = self.LINEWIDTH + pixel + devianceY d.ellipse([(x,y),(x+self.LINEWIDTH,y+self.LINEWIDTH)],fill=(0,0,0,255)) @@ -265,33 +334,33 @@ class DrawHangman(): if limb == "ra": limbDrawing = self.badLine(self.LIMBSIZE,True) rotation = random.randint(-45,45) - xpos = int((self.MANX-(self.LINEWIDTH*3))/2) + xPosition = int((self.MANX-(self.LINEWIDTH*3))/2) rotationCompensation = min(-int(math.sin(math.radians(rotation))*(self.LIMBSIZE+(self.LINEWIDTH*3))),0) - ypos = self.CIRCLESIZE+self.ARMPOSITION + rotationCompensation + yPosition = self.CIRCLESIZE+self.ARMPOSITION + rotationCompensation limbDrawing = limbDrawing.rotate(rotation,expand=1) - background.paste(limbDrawing,(xpos,ypos),limbDrawing) + background.paste(limbDrawing,(xPosition,yPosition),limbDrawing) elif limb == "la": limbDrawing = self.badLine(self.LIMBSIZE,True) rotation = random.randint(-45,45) - xpos = int((self.MANX-(self.LINEWIDTH*3))/2)-self.LIMBSIZE + xPosition = int((self.MANX-(self.LINEWIDTH*3))/2)-self.LIMBSIZE rotationCompensation = min(int(math.sin(math.radians(rotation))*(self.LIMBSIZE+(self.LINEWIDTH*3))),0) - ypos = self.CIRCLESIZE+self.ARMPOSITION + rotationCompensation + yPosition = self.CIRCLESIZE+self.ARMPOSITION + rotationCompensation limbDrawing = limbDrawing.rotate(rotation,expand=1) - background.paste(limbDrawing,(xpos,ypos),limbDrawing) + background.paste(limbDrawing,(xPosition,yPosition),limbDrawing) elif limb == "rl": limbDrawing = self.badLine(self.LIMBSIZE,True) rotation = random.randint(-15,15) - xpos = int((self.MANX-(self.LINEWIDTH*3))/2)-self.LINEWIDTH - ypos = self.CIRCLESIZE+self.BODYSIZE-self.LINEWIDTH + xPosition = int((self.MANX-(self.LINEWIDTH*3))/2)-self.LINEWIDTH + yPosition = self.CIRCLESIZE+self.BODYSIZE-self.LINEWIDTH limbDrawing = limbDrawing.rotate(rotation-45,expand=1) - background.paste(limbDrawing,(xpos,ypos),limbDrawing) + background.paste(limbDrawing,(xPosition,yPosition),limbDrawing) elif limb == "ll": limbDrawing = self.badLine(self.LIMBSIZE,True) rotation = random.randint(-15,15) limbDrawing = limbDrawing.rotate(rotation+45,expand=1) - xpos = int((self.MANX-(self.LINEWIDTH*3))/2)-limbDrawing.size[0]+self.LINEWIDTH*3 - ypos = self.CIRCLESIZE+self.BODYSIZE - background.paste(limbDrawing,(xpos,ypos),limbDrawing) + xPosition = int((self.MANX-(self.LINEWIDTH*3))/2)-limbDrawing.size[0]+self.LINEWIDTH*3 + yPosition = self.CIRCLESIZE+self.BODYSIZE + background.paste(limbDrawing,(xPosition,yPosition),limbDrawing) return background @@ -329,13 +398,13 @@ class DrawHangman(): if guessed[x]: letterDrawing = self.badText(letter,True) letterWidth = self.FONT.getsize(letter)[0] - letterx = int(x*(self.LETTERLINELENGTH+self.LETTERLINEDISTANCE)-(letterWidth/2)+(self.LETTERLINELENGTH*0.5)+(self.LINEWIDTH*2)) - letterLines.paste(letterDrawing,(letterx,0),letterDrawing) + letterX = int(x*(self.LETTERLINELENGTH+self.LETTERLINEDISTANCE)-(letterWidth/2)+(self.LETTERLINELENGTH*0.5)+(self.LINEWIDTH*2)) + letterLines.paste(letterDrawing,(letterX,0),letterDrawing) elif misses == 6: letterDrawing = self.badText(letter,True,(242,66,54)) letterWidth = self.FONT.getsize(letter)[0] - letterx = int(x*(self.LETTERLINELENGTH+self.LETTERLINEDISTANCE)-(letterWidth/2)+(self.LETTERLINELENGTH*0.5)+(self.LINEWIDTH*2)) - letterLines.paste(letterDrawing,(letterx,0),letterDrawing) + letterX = int(x*(self.LETTERLINELENGTH+self.LETTERLINEDISTANCE)-(letterWidth/2)+(self.LETTERLINELENGTH*0.5)+(self.LINEWIDTH*2)) + letterLines.paste(letterDrawing,(letterX,0),letterDrawing) return letterLines @@ -343,9 +412,9 @@ class DrawHangman(): shortestDist = math.inf x, y = newPosition for i, j in positions: - xdist = abs(i-x) - ydist = abs(j-y) - dist = math.sqrt(xdist**2+ydist**2) + xDistance = abs(i-x) + yDistance = abs(j-y) + dist = math.sqrt(xDistance**2+yDistance**2) if shortestDist > dist: shortestDist = dist return shortestDist diff --git a/resources/longStrings.json b/resources/longStrings.json index c0f1de8..b5d9c11 100644 --- a/resources/longStrings.json +++ b/resources/longStrings.json @@ -14,5 +14,8 @@ "Trivia going on": "There's already a trivia question going on. Try again in like, a minute", "Trivia time up": "Time's up! The answer was \"*{}) {}*\". Anyone who answered that has gotten 1 GwendoBuck", "Connect 4 going on": "There's already a connect 4 game going on in this channel", - "Connect 4 placed": "{} placed a piece in column {}. It's now {}'s turn" + "Connect 4 placed": "{} placed a piece in column {}. It's now {}'s turn", + "Hangman going on": "There's already a Hangman game going on in the channel", + "Hangman lost game": " You've guessed wrong six times and have lost the game.", + "Hangman guessed word": " You've guessed the word! Congratulations! Adding 15 GwendoBucks to your account" } \ No newline at end of file From 350c805519f401aa1add9781643e8fe0d80e6313 Mon Sep 17 00:00:00 2001 From: Nikolaj Danger Date: Wed, 21 Apr 2021 20:25:14 +0200 Subject: [PATCH 09/23] :Sparkles: Hangman --- funcs/games/hangman.py | 247 +++++++++++++++++++++++------------------ 1 file changed, 141 insertions(+), 106 deletions(-) diff --git a/funcs/games/hangman.py b/funcs/games/hangman.py index 8da9592..b00c727 100644 --- a/funcs/games/hangman.py +++ b/funcs/games/hangman.py @@ -281,29 +281,50 @@ class DrawHangman(): devianceX, devianceAccuracyX = self.calcDeviance(*devianceXParams) devianceY, devianceAccuracyY = self.calcDeviance(*devianceYParams) - x = middle + (math.cos(math.radians(degree+start)) * (self.CIRCLESIZE/2)) - (self.LINEWIDTH/2) + devianceX - y = middle + (math.sin(math.radians(degree+start)) * (self.CIRCLESIZE/2)) - (self.LINEWIDTH/2) + devianceY + radians = math.radians(degree+start) + circleX = (math.cos(radians) * (self.CIRCLESIZE/2)) + circleY = (math.sin(radians) * (self.CIRCLESIZE/2)) - d.ellipse([(x,y),(x+self.LINEWIDTH,y+self.LINEWIDTH)],fill=(0,0,0,255)) + x = middle + circleX - (self.LINEWIDTH/2) + devianceX + y = middle + circleY - (self.LINEWIDTH/2) + devianceY + + circlePosition = [(x, y), (x+self.LINEWIDTH, y+self.LINEWIDTH)] + d.ellipse(circlePosition, fill=(0, 0, 0, 255)) return background - def badLine(self, length, rotated = False): + def badLine(self, length: int, rotated: bool = False): if rotated: w, h = length+self.LINEWIDTH*3, self.LINEWIDTH*3 else: - w, h = self.LINEWIDTH*3,length+self.LINEWIDTH*3 - background = Image.new("RGBA",(w,h),color=(0,0,0,0)) + w, h = self.LINEWIDTH*3, length+self.LINEWIDTH*3 + background = Image.new("RGBA", (w, h), color=(0, 0, 0, 0)) - d = ImageDraw.Draw(background,"RGBA") - devianceX = random.randint(-int(self.LINEWIDTH/3),int(self.LINEWIDTH/3)) + d = ImageDraw.Draw(background, "RGBA") + + possibleDeviance = int(self.LINEWIDTH/3) + devianceX = random.randint(-possibleDeviance, possibleDeviance) devianceY = 0 devianceAccuracyX = 0 devianceAccuracyY = 0 for pixel in range(length): - devianceX, devianceAccuracyX = self.calcDeviance(devianceX,devianceAccuracyX,self.LINEWIDTH/1000,self.LINEWIDTH,0.004) - devianceY, devianceAccuracyY = self.calcDeviance(devianceY,devianceAccuracyY,self.LINEWIDTH/1000,self.LINEWIDTH,0.004) + devianceParamsX = [ + devianceX, + devianceAccuracyX, + self.LINEWIDTH/1000, + self.LINEWIDTH, + 0.004 + ] + devianceParamsY = [ + devianceY, + devianceAccuracyY, + self.LINEWIDTH/1000, + self.LINEWIDTH, + 0.004 + ] + devianceX, devianceAccuracyX = self.calcDeviance(*devianceParamsX) + devianceY, devianceAccuracyY = self.calcDeviance(*devianceParamsY) if rotated: x = self.LINEWIDTH + pixel + devianceX @@ -312,165 +333,179 @@ class DrawHangman(): x = self.LINEWIDTH + devianceX y = self.LINEWIDTH + pixel + devianceY - d.ellipse([(x,y),(x+self.LINEWIDTH,y+self.LINEWIDTH)],fill=(0,0,0,255)) + circlePosition = [(x, y), (x+self.LINEWIDTH, y+self.LINEWIDTH)] + d.ellipse(circlePosition, fill=(0, 0, 0, 255)) return background - def drawMan(self, misses): - background = Image.new("RGBA",(self.MANX,self.MANY),color=(0,0,0,0)) + def drawMan(self, misses: int): + manSize = (self.MANX, self.MANY) + background = Image.new("RGBA", manSize, color=(0, 0, 0, 0)) if misses >= 1: head = self.badCircle() - background.paste(head,(int((self.MANX-(self.CIRCLESIZE+(self.LINEWIDTH*3)))/2),0),head) + pasteX = (self.MANX-(self.CIRCLESIZE+(self.LINEWIDTH*3)))//2 + pastePosition = (pasteX, 0) + background.paste(head, pastePosition, head) if misses >= 2: body = self.badLine(self.BODYSIZE) - background.paste(body,(int((self.MANX-(self.LINEWIDTH*3))/2),self.CIRCLESIZE),body) + pasteX = (self.MANX-(self.LINEWIDTH*3))//2 + pastePosition = (pasteX, self.CIRCLESIZE) + background.paste(body, pastePosition, body) if misses >= 3: - limbs = random.sample(["rl","ll","ra","la"],min(misses-2,4)) - else: limbs = [] + limbs = random.sample(["rl", "ll", "ra", "la"], min(misses-2, 4)) + else: + limbs = [] for limb in limbs: - if limb == "ra": - limbDrawing = self.badLine(self.LIMBSIZE,True) - rotation = random.randint(-45,45) - xPosition = int((self.MANX-(self.LINEWIDTH*3))/2) - rotationCompensation = min(-int(math.sin(math.radians(rotation))*(self.LIMBSIZE+(self.LINEWIDTH*3))),0) - yPosition = self.CIRCLESIZE+self.ARMPOSITION + rotationCompensation - limbDrawing = limbDrawing.rotate(rotation,expand=1) - background.paste(limbDrawing,(xPosition,yPosition),limbDrawing) - elif limb == "la": - limbDrawing = self.badLine(self.LIMBSIZE,True) - rotation = random.randint(-45,45) - xPosition = int((self.MANX-(self.LINEWIDTH*3))/2)-self.LIMBSIZE - rotationCompensation = min(int(math.sin(math.radians(rotation))*(self.LIMBSIZE+(self.LINEWIDTH*3))),0) - yPosition = self.CIRCLESIZE+self.ARMPOSITION + rotationCompensation - limbDrawing = limbDrawing.rotate(rotation,expand=1) - background.paste(limbDrawing,(xPosition,yPosition),limbDrawing) - elif limb == "rl": - limbDrawing = self.badLine(self.LIMBSIZE,True) - rotation = random.randint(-15,15) - xPosition = int((self.MANX-(self.LINEWIDTH*3))/2)-self.LINEWIDTH - yPosition = self.CIRCLESIZE+self.BODYSIZE-self.LINEWIDTH - limbDrawing = limbDrawing.rotate(rotation-45,expand=1) - background.paste(limbDrawing,(xPosition,yPosition),limbDrawing) - elif limb == "ll": - limbDrawing = self.badLine(self.LIMBSIZE,True) - rotation = random.randint(-15,15) - limbDrawing = limbDrawing.rotate(rotation+45,expand=1) - xPosition = int((self.MANX-(self.LINEWIDTH*3))/2)-limbDrawing.size[0]+self.LINEWIDTH*3 + limbDrawing = self.badLine(self.LIMBSIZE, True) + xPosition = (self.MANX-(self.LINEWIDTH*3))//2 + + if limb[1] == "a": + rotation = random.randint(-45, 45) + shift = math.sin(math.radians(rotation)) + compensation = int(shift*(self.LIMBSIZE+(self.LINEWIDTH*3))) + limbDrawing = limbDrawing.rotate(rotation, expand=1) + yPosition = self.CIRCLESIZE + self.ARMPOSITION + compensation + if limb == "ra": + compensation = min(-compensation, 0) + else: + xPosition -= self.LIMBSIZE + compensation = min(compensation, 0) + else: + rotation = random.randint(-15, 15) yPosition = self.CIRCLESIZE+self.BODYSIZE - background.paste(limbDrawing,(xPosition,yPosition),limbDrawing) + if limb == "rl": + xPosition -= self.LINEWIDTH + limbDrawing = limbDrawing.rotate(rotation-45, expand=1) + else: + yPosition -= self.LINEWIDTH + xPosition += -limbDrawing.size[0]+self.LINEWIDTH*3 + limbDrawing = limbDrawing.rotate(rotation+45, expand=1) + + pastePosition = (xPosition, yPosition) + background.paste(limbDrawing, pastePosition, limbDrawing) return background - def badText(self, text, big, color=(0,0,0,255)): - if big: font = self.FONT - else: font = self.SMALLFONT + def badText(self, text: str, big: bool, color: tuple = (0, 0, 0, 255)): + if big: + font = self.FONT + else: + font = self.SMALLFONT w, h = font.getsize(text) - img = Image.new("RGBA",(w,h),color=(0,0,0,0)) - d = ImageDraw.Draw(img,"RGBA") + img = Image.new("RGBA", (w, h), color=(0, 0, 0, 0)) + d = ImageDraw.Draw(img, "RGBA") - d.text((0,0),text,font=font,fill=color) + d.text((0, 0), text, font=font, fill=color) return img def drawGallows(self): - background = Image.new("RGBA",(self.GALLOWX,self.GALLOWY),color=(0,0,0,0)) + gallowSize = (self.GALLOWX, self.GALLOWY) + background = Image.new("RGBA", gallowSize, color=(0, 0, 0, 0)) - bottomLine = self.badLine(int(self.GALLOWX*0.75),True) - background.paste(bottomLine,(int(self.GALLOWX*0.125),self.GALLOWY-(self.LINEWIDTH*4)),bottomLine) + bottomLine = self.badLine(int(self.GALLOWX * 0.75), True) + bottomLineX = int(self.GALLOWX * 0.125) + bottomLineY = self.GALLOWY-(self.LINEWIDTH*4) + pastePosition = (bottomLineX, bottomLineY) + background.paste(bottomLine, pastePosition, bottomLine) lineTwo = self.badLine(self.GALLOWY-self.LINEWIDTH*6) - background.paste(lineTwo,(int(self.GALLOWX*(0.75*self.GOLDENRATIO)),self.LINEWIDTH*2),lineTwo) + lineTwoX = int(self.GALLOWX*(0.75*self.GOLDENRATIO)) + lineTwoY = self.LINEWIDTH*2 + pastePosition = (lineTwoX, lineTwoY) + background.paste(lineTwo, pastePosition, lineTwo) - topLine = self.badLine(int(self.GALLOWY*0.30),True) - background.paste(topLine,(int(self.GALLOWX*(0.75*self.GOLDENRATIO))-self.LINEWIDTH,self.LINEWIDTH*3),topLine) + topLine = self.badLine(int(self.GALLOWY*0.30), True) + pasteX = int(self.GALLOWX*(0.75*self.GOLDENRATIO))-self.LINEWIDTH + pastePosition = (pasteX, self.LINEWIDTH*3) + background.paste(topLine, pastePosition, topLine) lastLine = self.badLine(int(self.GALLOWY*0.125)) - background.paste(lastLine,((int(self.GALLOWX*(0.75*self.GOLDENRATIO))+int(self.GALLOWY*0.30)-self.LINEWIDTH),self.LINEWIDTH*3),lastLine) + pasteX += int(self.GALLOWY*0.30) + background.paste(lastLine, (pasteX, self.LINEWIDTH*3), lastLine) return background - def drawLetterLines(self, word,guessed,misses): - letterLines = Image.new("RGBA",((self.LETTERLINELENGTH+self.LETTERLINEDISTANCE)*len(word),self.LETTERLINELENGTH+self.LINEWIDTH*3),color=(0,0,0,0)) + def drawLetterLines(self, word: str, guessed: list, misses: int): + imageWidth = (self.LETTERLINELENGTH+self.LETTERLINEDISTANCE)*len(word) + imageSize = (imageWidth, self.LETTERLINELENGTH+self.LINEWIDTH*3) + letterLines = Image.new("RGBA", imageSize, color=(0, 0, 0, 0)) for x, letter in enumerate(word): - line = self.badLine(self.LETTERLINELENGTH,True) - letterLines.paste(line,(x*(self.LETTERLINELENGTH+self.LETTERLINEDISTANCE),self.LETTERLINELENGTH),line) + line = self.badLine(self.LETTERLINELENGTH, True) + pasteX = x*(self.LETTERLINELENGTH+self.LETTERLINEDISTANCE) + pastePosition = (pasteX, self.LETTERLINELENGTH) + letterLines.paste(line, pastePosition, line) if guessed[x]: - letterDrawing = self.badText(letter,True) + letterDrawing = self.badText(letter, True) letterWidth = self.FONT.getsize(letter)[0] - letterX = int(x*(self.LETTERLINELENGTH+self.LETTERLINEDISTANCE)-(letterWidth/2)+(self.LETTERLINELENGTH*0.5)+(self.LINEWIDTH*2)) - letterLines.paste(letterDrawing,(letterX,0),letterDrawing) + letterX = x*(self.LETTERLINELENGTH+self.LETTERLINEDISTANCE) + letterX -= (letterWidth//2) + letterX += (self.LETTERLINELENGTH//2)+(self.LINEWIDTH*2) + letterLines.paste(letterDrawing, (letterX, 0), letterDrawing) elif misses == 6: - letterDrawing = self.badText(letter,True,(242,66,54)) + letterDrawing = self.badText(letter, True, (242, 66, 54)) letterWidth = self.FONT.getsize(letter)[0] - letterX = int(x*(self.LETTERLINELENGTH+self.LETTERLINEDISTANCE)-(letterWidth/2)+(self.LETTERLINELENGTH*0.5)+(self.LINEWIDTH*2)) - letterLines.paste(letterDrawing,(letterX,0),letterDrawing) + letterX = x*(self.LETTERLINELENGTH+self.LETTERLINEDISTANCE) + letterX -= (letterWidth//2) + letterX += (self.LETTERLINELENGTH//2)+(self.LINEWIDTH*2) + letterLines.paste(letterDrawing, (letterX, 0), letterDrawing) return letterLines - def shortestDist(self,positions,newPosition): + def shortestDist(self, positions: list, newPosition: tuple): shortestDist = math.inf x, y = newPosition for i, j in positions: xDistance = abs(i-x) yDistance = abs(j-y) dist = math.sqrt(xDistance**2+yDistance**2) - if shortestDist > dist: shortestDist = dist + if shortestDist > dist: + shortestDist = dist return shortestDist - def drawMisses(self,guesses,word): - background = Image.new("RGBA",(600,400),color=(0,0,0,0)) + def drawMisses(self, guesses: list, word: str): + background = Image.new("RGBA", (600, 400), color=(0, 0, 0, 0)) pos = [] for guess in guesses: if guess not in word: placed = False - while placed == False: - letter = self.badText(guess,True) + while not placed: + letter = self.badText(guess, True) w, h = self.FONT.getsize(guess) - x = random.randint(0,600-w) - y = random.randint(0,400-h) - if self.shortestDist(pos,(x,y)) > 70: - pos.append((x,y)) - background.paste(letter,(x,y),letter) + x = random.randint(0, 600-w) + y = random.randint(0, 400-h) + if self.shortestDist(pos, (x, y)) > 70: + pos.append((x, y)) + background.paste(letter, (x, y), letter) placed = True return background - def drawImage(self,channel): + def drawImage(self, channel: str): self.bot.log("Drawing hangman image", channel) - game = self.bot.database["hangman games"].find_one({"_id":channel}) + game = self.bot.database["hangman games"].find_one({"_id": channel}) random.seed(game["game ID"]) background = Image.open("resources/paper.jpg") - try: - gallow = self.drawGallows() - except: - self.bot.log("Error drawing gallows (error code 1711)") - - try: - man = self.drawMan(game["misses"]) - except: - self.bot.log("Error drawing stick figure (error code 1712)") + gallow = self.drawGallows() + man = self.drawMan(game["misses"]) random.seed(game["game ID"]) - try: - letterLines = self.drawLetterLines(game["word"],game["guessed"],game["misses"]) - except: - self.bot.log("error drawing letter lines (error code 1713)") + letterLineParams = [game["word"], game["guessed"], game["misses"]] + letterLines = self.drawLetterLines(*letterLineParams) random.seed(game["game ID"]) - try: - misses = self.drawMisses(game["guessed letters"],game["word"]) - except: - self.bot.log("Error drawing misses (error code 1714)") + misses = self.drawMisses(game["guessed letters"], game["word"]) - background.paste(gallow,(100,100),gallow) - background.paste(man,(300,210),man) - background.paste(letterLines,(120,840),letterLines) - background.paste(misses,(600,150),misses) + background.paste(gallow, (100, 100), gallow) + background.paste(man, (300, 210), man) + background.paste(letterLines, (120, 840), letterLines) + background.paste(misses, (600, 150), misses) - missesText = self.badText("MISSES",False) + missesText = self.badText("MISSES", False) missesTextWidth = missesText.size[0] - background.paste(missesText,(850-int(missesTextWidth/2),50),missesText) + background.paste(missesText, (850-missesTextWidth//2, 50), missesText) - background.save("resources/games/hangmanBoards/hangmanBoard"+channel+".png") + boardPath = f"resources/games/hangmanBoards/hangmanBoard{channel}.png" + background.save(boardPath) From 45511d83887ee0ec1d2482087d83394f9b670877 Mon Sep 17 00:00:00 2001 From: NikolajDanger Date: Thu, 22 Apr 2021 20:36:06 +0200 Subject: [PATCH 10/23] :sparkles: hangman --- funcs/games/hangman.py | 391 ++++++++++++++++++++++++++--------------- 1 file changed, 246 insertions(+), 145 deletions(-) diff --git a/funcs/games/hangman.py b/funcs/games/hangman.py index b00c727..d702688 100644 --- a/funcs/games/hangman.py +++ b/funcs/games/hangman.py @@ -1,22 +1,53 @@ -import json -import requests -import datetime -import string -import discord -import math -import random +""" +Deals with commands and logic for hangman games. -from discord_slash.context import SlashContext -from PIL import ImageDraw, Image, ImageFont +*Classes* +--------- + Hangman() + Deals with the game logic of hangman. + DrawHangman() + Draws the image shown to the player. +""" +import requests # Used for getting the word in Hangman.start() +import datetime # Used for generating the game id +import string # string.ascii_uppercase used +import discord # Used for discord.file and type hints +import math # Used by DrawHangman(), mainly for drawing circles +import random # Used to draw poorly + +from discord_slash.context import SlashContext # Used for typehints +from PIL import ImageDraw, Image, ImageFont # Used to draw the image class Hangman(): + """ + Controls hangman commands and game logic. + + *Methods* + --------- + start(ctx: SlashContext) + stop(ctx: SlashContext) + guess(message: discord.message, user: str, guess: str) + """ + def __init__(self, bot): - self.bot = bot - self.draw = DrawHangman(bot) - self.APIURL = "https://api.wordnik.com/v4/words.json/randomWords?" - apiKey = self.bot.credentials.wordnikKey - self.APIPARAMS = { + """ + Initialize the class. + + *Attributes* + ------------ + draw: DrawHangman + The DrawHangman used to draw the hangman image. + APIURL: str + The url to get the words from. + APIPARAMS: dict + The parameters to pass to every api call. + """ + self.__bot = bot + self.__draw = DrawHangman(bot) + self.__APIURL = "https://api.wordnik.com/v4/words.json/randomWords?" + apiKey = self.__bot.credentials.wordnikKey + self.__APIPARAMS = { "hasDictionaryDef": True, "minCorpusCount": 5000, "maxCorpusCount": -1, @@ -29,20 +60,28 @@ class Hangman(): } async def start(self, ctx: SlashContext): - await self.bot.defer(ctx) + """ + Start a game of hangman. + + *Parameters* + ------------ + ctx: SlashContext + The context of the command. + """ + await self.__bot.defer(ctx) 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) + game = self.__bot.database["hangman games"].find_one({"_id": channel}) + userName = self.__bot.databaseFuncs.getName(user) startedGame = False if game is None: word = "-" while "-" in word or "." in word: - response = requests.get(self.APIURL, params=self.APIPARAMS) + response = requests.get(self.__APIURL, params=self.__APIPARAMS) word = list(response.json()[0]["word"].upper()) - self.bot.log("Found the word \""+"".join(word)+"\"") + self.__bot.log("Found the word \""+"".join(word)+"\"") guessed = [False] * len(word) gameID = datetime.datetime.now().strftime("%Y%m%d%H%M%S") newGame = { @@ -54,20 +93,20 @@ class Hangman(): "misses": 0, "guessed": guessed } - self.bot.database["hangman games"].insert_one(newGame) + self.__bot.database["hangman games"].insert_one(newGame) remainingLetters = list(string.ascii_uppercase) - self.draw.drawImage(channel) + self.__draw.drawImage(channel) logMessage = "Game started" sendMessage = f"{userName} started game of hangman." startedGame = True else: logMessage = "There was already a game going on" - sendMessage = self.bot.longStrings["Hangman going on"] + sendMessage = self.__bot.longStrings["Hangman going on"] - self.bot.log(logMessage) + self.__bot.log(logMessage) await ctx.send(sendMessage) if startedGame: @@ -92,29 +131,49 @@ class Hangman(): await message.add_reaction(emoji) async def stop(self, ctx: SlashContext): + """ + Stop the game of hangman. + + *Parameters* + ------------ + ctx: SlashContext + The context of the command. + """ channel = str(ctx.channel.id) - game = self.bot.database["hangman games"].find_one({"_id": channel}) + 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}) + 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") + self.__bot.log("Deleting old message") await oldMessage.delete() await ctx.send("Game stopped") async def guess(self, message: discord.Message, user: str, guess: str): + """ + Guess a letter. + + *Parameters* + ------------ + message: discord.Message + The message that the reaction was placed on. + user: str + The id of the user. + guess: str + The guess. + """ channel = str(message.channel.id) - hangmanGames = self.bot.database["hangman games"] + hangmanGames = self.__bot.database["hangman games"] game = hangmanGames.find_one({"_id": channel}) gameExists = (game is not None) @@ -123,7 +182,7 @@ class Hangman(): validGuess = (gameExists and singleLetter and newGuess) if validGuess: - self.bot.log("Guessed the letter") + self.__bot.log("Guessed the letter") correctGuess = 0 for x, letter in enumerate(game["word"]): @@ -153,16 +212,16 @@ class Hangman(): sendMessage = "Guessed {}. There were {} {}s in the word." sendMessage = sendMessage.format(guess, correctGuess, guess) - self.draw.drawImage(channel) + self.__draw.drawImage(channel) if game["misses"] == 6: hangmanGames.delete_one({"_id": channel}) - sendMessage += self.bot.longStrings["Hangman lost game"] + sendMessage += self.__bot.longStrings["Hangman lost game"] remainingLetters = [] elif all(game["guessed"]): hangmanGames.delete_one({"_id": channel}) - self.bot.money.addMoney(user, 15) - sendMessage += self.bot.longStrings["Hangman guessed word"] + self.__bot.money.addMoney(user, 15) + sendMessage += self.__bot.longStrings["Hangman guessed word"] remainingLetters = [] await message.channel.send(sendMessage) @@ -172,7 +231,7 @@ class Hangman(): for oldID in oldMessageIDs: oldMessage = await message.channel.fetch_message(int(oldID)) - self.bot.log("Deleting old message") + self.__bot.log("Deleting old message") await oldMessage.delete() boardsPath = "resources/games/hangmanBoards/" @@ -206,34 +265,63 @@ class Hangman(): class DrawHangman(): + """ + Draws the image of the hangman game. + + *Methods* + --------- + drawImage(channel: str) + """ + def __init__(self, bot): - self.bot = bot - self.CIRCLESIZE = 120 - self.LINEWIDTH = 12 + """ + Initialize the class. - self.BODYSIZE = 210 - self.LIMBSIZE = 60 - self.ARMPOSITION = 60 + *Attributes* + ------------ + CIRCLESIZE + LINEWIDTH + BODYSIZE + LIMBSIZE + ARMPOSITION + MANX, MANY + LETTERLINELENGTH + LETTERLINEDISTANCE + GALLOWX, GALLOWY + PHI + FONT + SMALLFONT + """ + self.__bot = bot + self.__CIRCLESIZE = 120 + self.__LINEWIDTH = 12 - MANPADDING = self.LINEWIDTH*4 - self.MANX = (self.LIMBSIZE*2)+MANPADDING - self.MANY = (self.CIRCLESIZE+self.BODYSIZE+self.LIMBSIZE)+MANPADDING + self.__BODYSIZE = 210 + self.__LIMBSIZE = 60 + self.__ARMPOSITION = 60 - self.LETTERLINELENGTH = 90 - self.LETTERLINEDISTANCE = 30 + self.__MANX = (self.__LIMBSIZE*2) + self.__MANY = (self.__CIRCLESIZE+self.__BODYSIZE+self.__LIMBSIZE) + MANPADDING = self.__LINEWIDTH*4 + self.__MANX += MANPADDING + self.__MANY += MANPADDING - self.GALLOWX, self.GALLOWY = 360, 600 - self.GOLDENRATIO = 1-(1 / ((1 + 5 ** 0.5) / 2)) + self.__LETTERLINELENGTH = 90 + self.__LETTERLINEDISTANCE = 30 - LETTERSIZE = 75 - TEXTSIZE = 70 + self.__GALLOWX, self.__GALLOWY = 360, 600 + self.__PHI = 1-(1 / ((1 + 5 ** 0.5) / 2)) + + LETTERSIZE = 75 # Wrong guesses letter size + WORDSIZE = 70 # Correct guesses letter size FONTPATH = "resources/fonts/comic-sans-bold.ttf" - self.FONT = ImageFont.truetype(FONTPATH, LETTERSIZE) - self.SMALLFONT = ImageFont.truetype(FONTPATH, TEXTSIZE) + self.__FONT = ImageFont.truetype(FONTPATH, LETTERSIZE) + self.__SMALLFONT = ImageFont.truetype(FONTPATH, WORDSIZE) - def calcDeviance(self, preDeviance, preDevianceAccuracy, positionChange, - maxmin, maxAcceleration): + def __deviate(self, preDeviance: int, preDevianceAccuracy: int, + positionChange: float, maxmin: int, + maxAcceleration: float): randomDeviance = random.uniform(-positionChange, positionChange) devianceAccuracy = preDevianceAccuracy + randomDeviance if devianceAccuracy > maxmin * maxAcceleration: @@ -248,14 +336,14 @@ class DrawHangman(): deviance = -maxmin return deviance, devianceAccuracy - def badCircle(self): - circlePadding = (self.LINEWIDTH*3) - imageWidth = self.CIRCLESIZE+circlePadding + def __badCircle(self): + circlePadding = (self.__LINEWIDTH*3) + imageWidth = self.__CIRCLESIZE+circlePadding imageSize = (imageWidth, imageWidth) background = Image.new("RGBA", imageSize, color=(0, 0, 0, 0)) d = ImageDraw.Draw(background, "RGBA") - middle = (self.CIRCLESIZE+(self.LINEWIDTH*3))/2 + middle = (self.__CIRCLESIZE+(self.__LINEWIDTH*3))/2 devianceX = 0 devianceY = 0 devianceAccuracyX = 0 @@ -267,42 +355,42 @@ class DrawHangman(): devianceXParams = [ devianceX, devianceAccuracyX, - self.LINEWIDTH/100, - self.LINEWIDTH, + self.__LINEWIDTH/100, + self.__LINEWIDTH, 0.03 ] devianceYParams = [ devianceY, devianceAccuracyY, - self.LINEWIDTH/100, - self.LINEWIDTH, + self.__LINEWIDTH/100, + self.__LINEWIDTH, 0.03 ] - devianceX, devianceAccuracyX = self.calcDeviance(*devianceXParams) - devianceY, devianceAccuracyY = self.calcDeviance(*devianceYParams) + devianceX, devianceAccuracyX = self.__deviate(*devianceXParams) + devianceY, devianceAccuracyY = self.__deviate(*devianceYParams) radians = math.radians(degree+start) - circleX = (math.cos(radians) * (self.CIRCLESIZE/2)) - circleY = (math.sin(radians) * (self.CIRCLESIZE/2)) + circleX = (math.cos(radians) * (self.__CIRCLESIZE/2)) + circleY = (math.sin(radians) * (self.__CIRCLESIZE/2)) - x = middle + circleX - (self.LINEWIDTH/2) + devianceX - y = middle + circleY - (self.LINEWIDTH/2) + devianceY + x = middle + circleX - (self.__LINEWIDTH/2) + devianceX + y = middle + circleY - (self.__LINEWIDTH/2) + devianceY - circlePosition = [(x, y), (x+self.LINEWIDTH, y+self.LINEWIDTH)] + circlePosition = [(x, y), (x+self.__LINEWIDTH, y+self.__LINEWIDTH)] d.ellipse(circlePosition, fill=(0, 0, 0, 255)) return background - def badLine(self, length: int, rotated: bool = False): + def __badLine(self, length: int, rotated: bool = False): if rotated: - w, h = length+self.LINEWIDTH*3, self.LINEWIDTH*3 + w, h = length+self.__LINEWIDTH*3, self.__LINEWIDTH*3 else: - w, h = self.LINEWIDTH*3, length+self.LINEWIDTH*3 + w, h = self.__LINEWIDTH*3, length+self.__LINEWIDTH*3 background = Image.new("RGBA", (w, h), color=(0, 0, 0, 0)) d = ImageDraw.Draw(background, "RGBA") - possibleDeviance = int(self.LINEWIDTH/3) + possibleDeviance = int(self.__LINEWIDTH/3) devianceX = random.randint(-possibleDeviance, possibleDeviance) devianceY = 0 devianceAccuracyX = 0 @@ -312,45 +400,46 @@ class DrawHangman(): devianceParamsX = [ devianceX, devianceAccuracyX, - self.LINEWIDTH/1000, - self.LINEWIDTH, + self.__LINEWIDTH/1000, + self.__LINEWIDTH, 0.004 ] devianceParamsY = [ devianceY, devianceAccuracyY, - self.LINEWIDTH/1000, - self.LINEWIDTH, + self.__LINEWIDTH/1000, + self.__LINEWIDTH, 0.004 ] - devianceX, devianceAccuracyX = self.calcDeviance(*devianceParamsX) - devianceY, devianceAccuracyY = self.calcDeviance(*devianceParamsY) + devianceX, devianceAccuracyX = self.__deviate(*devianceParamsX) + devianceY, devianceAccuracyY = self.__deviate(*devianceParamsY) if rotated: - x = self.LINEWIDTH + pixel + devianceX - y = self.LINEWIDTH + devianceY + x = self.__LINEWIDTH + pixel + devianceX + y = self.__LINEWIDTH + devianceY else: - x = self.LINEWIDTH + devianceX - y = self.LINEWIDTH + pixel + devianceY + x = self.__LINEWIDTH + devianceX + y = self.__LINEWIDTH + pixel + devianceY - circlePosition = [(x, y), (x+self.LINEWIDTH, y+self.LINEWIDTH)] + circlePosition = [(x, y), (x+self.__LINEWIDTH, y+self.__LINEWIDTH)] d.ellipse(circlePosition, fill=(0, 0, 0, 255)) return background - def drawMan(self, misses: int): - manSize = (self.MANX, self.MANY) + def __drawMan(self, misses: int, seed: str): + random.seed(seed) + manSize = (self.__MANX, self.__MANY) background = Image.new("RGBA", manSize, color=(0, 0, 0, 0)) if misses >= 1: - head = self.badCircle() - pasteX = (self.MANX-(self.CIRCLESIZE+(self.LINEWIDTH*3)))//2 + head = self.__badCircle() + pasteX = (self.__MANX-(self.__CIRCLESIZE+(self.__LINEWIDTH*3)))//2 pastePosition = (pasteX, 0) background.paste(head, pastePosition, head) if misses >= 2: - body = self.badLine(self.BODYSIZE) - pasteX = (self.MANX-(self.LINEWIDTH*3))//2 - pastePosition = (pasteX, self.CIRCLESIZE) + body = self.__badLine(self.__BODYSIZE) + pasteX = (self.__MANX-(self.__LINEWIDTH*3))//2 + pastePosition = (pasteX, self.__CIRCLESIZE) background.paste(body, pastePosition, body) if misses >= 3: @@ -358,30 +447,33 @@ class DrawHangman(): else: limbs = [] + random.seed(seed) + for limb in limbs: - limbDrawing = self.badLine(self.LIMBSIZE, True) - xPosition = (self.MANX-(self.LINEWIDTH*3))//2 + limbDrawing = self.__badLine(self.__LIMBSIZE, True) + xPosition = (self.__MANX-(self.__LINEWIDTH*3))//2 if limb[1] == "a": rotation = random.randint(-45, 45) shift = math.sin(math.radians(rotation)) - compensation = int(shift*(self.LIMBSIZE+(self.LINEWIDTH*3))) + lineLength = self.__LIMBSIZE+(self.__LINEWIDTH*3) + compensation = int(shift*lineLength) limbDrawing = limbDrawing.rotate(rotation, expand=1) - yPosition = self.CIRCLESIZE + self.ARMPOSITION + compensation + yPosition = self.__CIRCLESIZE + self.__ARMPOSITION if limb == "ra": compensation = min(-compensation, 0) else: - xPosition -= self.LIMBSIZE + xPosition -= self.__LIMBSIZE compensation = min(compensation, 0) + + yPosition += compensation else: rotation = random.randint(-15, 15) - yPosition = self.CIRCLESIZE+self.BODYSIZE + yPosition = self.__CIRCLESIZE+self.__BODYSIZE-self.__LINEWIDTH if limb == "rl": - xPosition -= self.LINEWIDTH limbDrawing = limbDrawing.rotate(rotation-45, expand=1) else: - yPosition -= self.LINEWIDTH - xPosition += -limbDrawing.size[0]+self.LINEWIDTH*3 + xPosition += -limbDrawing.size[0]+self.__LINEWIDTH*3 limbDrawing = limbDrawing.rotate(rotation+45, expand=1) pastePosition = (xPosition, yPosition) @@ -389,11 +481,11 @@ class DrawHangman(): return background - def badText(self, text: str, big: bool, color: tuple = (0, 0, 0, 255)): + def __badText(self, text: str, big: bool, color: tuple = (0, 0, 0, 255)): if big: - font = self.FONT + font = self.__FONT else: - font = self.SMALLFONT + font = self.__SMALLFONT w, h = font.getsize(text) img = Image.new("RGBA", (w, h), color=(0, 0, 0, 0)) d = ImageDraw.Draw(img, "RGBA") @@ -401,109 +493,118 @@ class DrawHangman(): d.text((0, 0), text, font=font, fill=color) return img - def drawGallows(self): - gallowSize = (self.GALLOWX, self.GALLOWY) + def __drawGallows(self): + gallowSize = (self.__GALLOWX, self.__GALLOWY) background = Image.new("RGBA", gallowSize, color=(0, 0, 0, 0)) - bottomLine = self.badLine(int(self.GALLOWX * 0.75), True) - bottomLineX = int(self.GALLOWX * 0.125) - bottomLineY = self.GALLOWY-(self.LINEWIDTH*4) + bottomLine = self.__badLine(int(self.__GALLOWX * 0.75), True) + bottomLineX = int(self.__GALLOWX * 0.125) + bottomLineY = self.__GALLOWY-(self.__LINEWIDTH*4) pastePosition = (bottomLineX, bottomLineY) background.paste(bottomLine, pastePosition, bottomLine) - lineTwo = self.badLine(self.GALLOWY-self.LINEWIDTH*6) - lineTwoX = int(self.GALLOWX*(0.75*self.GOLDENRATIO)) - lineTwoY = self.LINEWIDTH*2 + lineTwo = self.__badLine(self.__GALLOWY-self.__LINEWIDTH*6) + lineTwoX = int(self.__GALLOWX*(0.75*self.__PHI)) + lineTwoY = self.__LINEWIDTH*2 pastePosition = (lineTwoX, lineTwoY) background.paste(lineTwo, pastePosition, lineTwo) - topLine = self.badLine(int(self.GALLOWY*0.30), True) - pasteX = int(self.GALLOWX*(0.75*self.GOLDENRATIO))-self.LINEWIDTH - pastePosition = (pasteX, self.LINEWIDTH*3) + topLine = self.__badLine(int(self.__GALLOWY*0.30), True) + pasteX = int(self.__GALLOWX*(0.75*self.__PHI))-self.__LINEWIDTH + pastePosition = (pasteX, self.__LINEWIDTH*3) background.paste(topLine, pastePosition, topLine) - lastLine = self.badLine(int(self.GALLOWY*0.125)) - pasteX += int(self.GALLOWY*0.30) - background.paste(lastLine, (pasteX, self.LINEWIDTH*3), lastLine) + lastLine = self.__badLine(int(self.__GALLOWY*0.125)) + pasteX += int(self.__GALLOWY*0.30) + background.paste(lastLine, (pasteX, self.__LINEWIDTH*3), lastLine) return background - def drawLetterLines(self, word: str, guessed: list, misses: int): - imageWidth = (self.LETTERLINELENGTH+self.LETTERLINEDISTANCE)*len(word) - imageSize = (imageWidth, self.LETTERLINELENGTH+self.LINEWIDTH*3) + def __drawLetterLines(self, word: str, guessed: list, misses: int): + letterWidth = self.__LETTERLINELENGTH+self.__LETTERLINEDISTANCE + imageWidth = letterWidth*len(word) + imageSize = (imageWidth, self.__LETTERLINELENGTH+self.__LINEWIDTH*3) letterLines = Image.new("RGBA", imageSize, color=(0, 0, 0, 0)) for x, letter in enumerate(word): - line = self.badLine(self.LETTERLINELENGTH, True) - pasteX = x*(self.LETTERLINELENGTH+self.LETTERLINEDISTANCE) - pastePosition = (pasteX, self.LETTERLINELENGTH) + line = self.__badLine(self.__LETTERLINELENGTH, True) + pasteX = x*(self.__LETTERLINELENGTH+self.__LETTERLINEDISTANCE) + pastePosition = (pasteX, self.__LETTERLINELENGTH) letterLines.paste(line, pastePosition, line) if guessed[x]: - letterDrawing = self.badText(letter, True) - letterWidth = self.FONT.getsize(letter)[0] - letterX = x*(self.LETTERLINELENGTH+self.LETTERLINEDISTANCE) + letterDrawing = self.__badText(letter, True) + letterWidth = self.__FONT.getsize(letter)[0] + letterX = x*(self.__LETTERLINELENGTH+self.__LETTERLINEDISTANCE) letterX -= (letterWidth//2) - letterX += (self.LETTERLINELENGTH//2)+(self.LINEWIDTH*2) + letterX += (self.__LETTERLINELENGTH//2)+(self.__LINEWIDTH*2) letterLines.paste(letterDrawing, (letterX, 0), letterDrawing) elif misses == 6: - letterDrawing = self.badText(letter, True, (242, 66, 54)) - letterWidth = self.FONT.getsize(letter)[0] - letterX = x*(self.LETTERLINELENGTH+self.LETTERLINEDISTANCE) + letterDrawing = self.__badText(letter, True, (242, 66, 54)) + letterWidth = self.__FONT.getsize(letter)[0] + letterX = x*(self.__LETTERLINELENGTH+self.__LETTERLINEDISTANCE) letterX -= (letterWidth//2) - letterX += (self.LETTERLINELENGTH//2)+(self.LINEWIDTH*2) + letterX += (self.__LETTERLINELENGTH//2)+(self.__LINEWIDTH*2) letterLines.paste(letterDrawing, (letterX, 0), letterDrawing) return letterLines - def shortestDist(self, positions: list, newPosition: tuple): - shortestDist = math.inf + def __shortestDist(self, positions: list, newPosition: tuple): + __shortestDist = math.inf x, y = newPosition for i, j in positions: xDistance = abs(i-x) yDistance = abs(j-y) dist = math.sqrt(xDistance**2+yDistance**2) - if shortestDist > dist: - shortestDist = dist - return shortestDist + if __shortestDist > dist: + __shortestDist = dist + return __shortestDist - def drawMisses(self, guesses: list, word: str): + def __drawMisses(self, guesses: list, word: str): background = Image.new("RGBA", (600, 400), color=(0, 0, 0, 0)) pos = [] for guess in guesses: if guess not in word: placed = False while not placed: - letter = self.badText(guess, True) - w, h = self.FONT.getsize(guess) + letter = self.__badText(guess, True) + w, h = self.__FONT.getsize(guess) x = random.randint(0, 600-w) y = random.randint(0, 400-h) - if self.shortestDist(pos, (x, y)) > 70: + if self.__shortestDist(pos, (x, y)) > 70: pos.append((x, y)) background.paste(letter, (x, y), letter) placed = True return background def drawImage(self, channel: str): - self.bot.log("Drawing hangman image", channel) - game = self.bot.database["hangman games"].find_one({"_id": channel}) + """ + Draw a hangman Image. + + *Parameters* + ------------ + channel: str + The id of the channel the game is in. + """ + self.__bot.log("Drawing hangman image", channel) + game = self.__bot.database["hangman games"].find_one({"_id": channel}) random.seed(game["game ID"]) background = Image.open("resources/paper.jpg") - gallow = self.drawGallows() - man = self.drawMan(game["misses"]) + gallow = self.__drawGallows() + man = self.__drawMan(game["misses"], game["game ID"]) random.seed(game["game ID"]) letterLineParams = [game["word"], game["guessed"], game["misses"]] - letterLines = self.drawLetterLines(*letterLineParams) + letterLines = self.__drawLetterLines(*letterLineParams) random.seed(game["game ID"]) - misses = self.drawMisses(game["guessed letters"], game["word"]) + misses = self.__drawMisses(game["guessed letters"], game["word"]) background.paste(gallow, (100, 100), gallow) background.paste(man, (300, 210), man) background.paste(letterLines, (120, 840), letterLines) background.paste(misses, (600, 150), misses) - missesText = self.badText("MISSES", False) + missesText = self.__badText("MISSES", False) missesTextWidth = missesText.size[0] background.paste(missesText, (850-missesTextWidth//2, 50), missesText) From 06b5d881ea0c224f407a3c40a2d23d8ae767851b Mon Sep 17 00:00:00 2001 From: NikolajDanger Date: Thu, 15 Apr 2021 15:16:56 +0200 Subject: [PATCH 11/23] :twisted_rightwards_arrows: Merging with master --- .gitignore | 4 +- Gwendolyn.py | 137 +++++++++++++++------ cogs/EventCog.py | 20 ++-- cogs/GameCogs.py | 112 ++++++++++------- cogs/LookupCog.py | 18 ++- cogs/MiscCog.py | 53 +++++---- cogs/StarWarsCog.py | 31 ++--- resources/longStrings.json | 4 + utils/__init__.py | 9 +- utils/eventHandlers.py | 184 ++++++++++++++++++---------- utils/helperClasses.py | 238 ++++++++++++++++++++++++++++++++----- utils/utilFunctions.py | 178 ++++++++++++++++++++++----- 12 files changed, 729 insertions(+), 259 deletions(-) create mode 100644 resources/longStrings.json diff --git a/.gitignore b/.gitignore index aa5010b..e10cdc3 100644 --- a/.gitignore +++ b/.gitignore @@ -153,11 +153,11 @@ static token.txt credentials.txt options.txt -resources/starWars/destinyPoints.txt +resources/star_wars/destinyPoints.txt resources/bedreNetflix/ resources/games/hilo/ resources/games/blackjackTables/ -resources/games/oldImages/ +resources/games/old_images/ resources/games/connect4Boards/ resources/games/hexBoards/ resources/games/hangmanBoards/ diff --git a/Gwendolyn.py b/Gwendolyn.py index 5a48420..20342c5 100644 --- a/Gwendolyn.py +++ b/Gwendolyn.py @@ -1,63 +1,138 @@ -import os, finnhub, platform, asyncio, discord +""" +Contains the Gwendolyn class, and runs it when run as script. -from discord.ext import commands -from discord_slash import SlashCommand -from pymongo import MongoClient +*Classes* +--------- + Gwendolyn(discord.ext.commands.Bot) +""" +import os # Used for loading cogs in Gwendolyn.addCogs +import finnhub # Used to add a finhub client to the bot +import platform # Used to test if the bot is running on windows, in +# order to fix a bug with asyncio +import asyncio # used to set change the loop policy if the bot is +# running on windows +import discord # Used for discord.Intents and discord.Status +import discord_slash # Used to initialized SlashCommands object + +from discord.ext import commands # Used to inherit from commands.bot +from pymongo import MongoClient # Used for database management from funcs import Money, StarWars, Games, Other, LookupFuncs -from utils import Options, Credentials, logThis, makeFiles, databaseFuncs, EventHandler, ErrorHandler +from utils import (Options, Credentials, logThis, makeFiles, databaseFuncs, + EventHandler, ErrorHandler, longStrings) + class Gwendolyn(commands.Bot): + """ + A multifunctional Discord bot. + + *Methods* + --------- + log(messages: Union[str, list], channel: str = "", + level: int = 20) + stop(ctx: discord_slash.context.SlashContext) + defer(ctx: discord_slash.context.SlashContext) + """ + def __init__(self): + """Initialize the bot.""" + intents = discord.Intents.default() + intents.members = True + initParams = { + "command_prefix": " ", + "case_insensitive": True, + "intents": intents, + "status": discord.Status.dnd + } + super().__init__(**initParams) + + self._addClientsAndOptions() + self._addUtilClasses() + self._addFunctionContainers() + self._addCogs() + + def _addClientsAndOptions(self): + """Add all the client, option and credentials objects.""" + self.longStrings = longStrings() self.options = Options() self.credentials = Credentials() - self.finnhubClient = finnhub.Client(api_key = self.credentials.finnhubKey) - self.MongoClient = MongoClient(f"mongodb+srv://{self.credentials.mongoDBUser}:{self.credentials.mongoDBPassword}@gwendolyn.qkwfy.mongodb.net/Gwendolyn?retryWrites=true&w=majority") + finnhubKey = self.credentials.finnhubKey + self.finnhubClient = finnhub.Client(api_key=finnhubKey) + mongoUser = self.credentials.mongoDBUser + mongoPassword = self.credentials.mongoDBPassword + mongoLink = f"mongodb+srv://{mongoUser}:{mongoPassword}@gwendolyn" + mongoLink += ".qkwfy.mongodb.net/Gwendolyn?retryWrites=true&w=majority" + dataBaseClient = MongoClient(mongoLink) if self.options.testing: self.log("Testing mode") - self.database = self.MongoClient["Gwendolyn-Test"] + self.database = dataBaseClient["Gwendolyn-Test"] else: - self.database = self.MongoClient["Gwendolyn"] + self.database = dataBaseClient["Gwendolyn"] + def _addUtilClasses(self): + """Add all the classes used as utility.""" + self.databaseFuncs = databaseFuncs(self) + self.eventHandler = EventHandler(self) + self.errorHandler = ErrorHandler(self) + slashParams = { + "sync_commands": True, + "sync_on_cog_reload": True, + "override_type": True + } + self.slash = discord_slash.SlashCommand(self, **slashParams) + + def _addFunctionContainers(self): + """Add all the function containers used for commands.""" self.starWars = StarWars(self) self.other = Other(self) self.lookupFuncs = LookupFuncs(self) 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 + def _addCogs(self): + """Load cogs.""" + for filename in os.listdir("./cogs"): + if filename.endswith(".py"): + self.load_extension(f"cogs.{filename[:-3]}") - super().__init__(command_prefix=" ", case_insensitive=True, intents = intents, status = discord.Status.dnd) - - def log(self, messages, channel : str = "", level : int = 20): + def log(self, messages, channel: str = "", level: int = 20): + """Log a message. Described in utils/utilFunctions.py.""" logThis(messages, channel, level) - async def stop(self, ctx): + async def stop(self, ctx: discord_slash.context.SlashContext): + """ + Stop the bot, and stop running games. + + Only stops the bot if the user in ctx.author is one of the + admins given in options.txt. + + *parameters* + ------------ + ctx: discord_slash.context.SlashContext + The context of the "/stop" slash command. + """ 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) + await self.change_presence(status=discord.Status.offline) - self.databaseFuncs.stopServer() + self.databaseFuncs.wipeGames() - self.log("Logging out", level = 25) + 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)") + logMessage = f"{ctx.author.display_name} tried to stop me!" + self.log(logMessage, str(ctx.channel_id)) + await ctx.send(f"I don't think I will, {ctx.author.display_name}") - async def defer(self, ctx): + async def defer(self, ctx: discord_slash.context.SlashContext): + """Send a "Gwendolyn is thinking" message to the user.""" try: await ctx.defer() - except: + except discord_slash.error.AlreadyResponded: self.log("defer failed") - if __name__ == "__main__": if platform.system() == "Windows": asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) @@ -67,15 +142,9 @@ if __name__ == "__main__": # Creates the Bot bot = Gwendolyn() - bot.slash = SlashCommand(bot, sync_commands=True, sync_on_cog_reload=True, override_type=True) - - #Loads cogs - for filename in os.listdir("./cogs"): - if filename.endswith(".py"): - bot.load_extension(f"cogs.{filename[:-3]}") try: # Runs the whole shabang bot.run(bot.credentials.token) - except: - bot.log("Could not log in. Remember to write your bot token in the credentials.txt file") \ No newline at end of file + except Exception: + bot.log(bot.longStrings["Can't log in"]) diff --git a/cogs/EventCog.py b/cogs/EventCog.py index 7b72f6b..50fc3b9 100644 --- a/cogs/EventCog.py +++ b/cogs/EventCog.py @@ -1,34 +1,40 @@ -from discord.ext import commands +"""Contains the EventCog, which runs code for specific bot events.""" +from discord.ext import commands # Has the cog class + class EventCog(commands.Cog): + """Handles bot events.""" + def __init__(self, bot): + """Initialize the 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): + """Log and set bot status when bot logs in.""" await self.bot.eventHandler.on_ready() - # Logs when user sends a command @commands.Cog.listener() async def on_slash_command(self, ctx): + """Log when a slash command is run.""" 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): + """Log when a slash error occurs.""" await self.bot.errorHandler.on_slash_command_error(ctx, error) - # Logs if on error occurs async def on_error(self, method, *args, **kwargs): + """Log when an error occurs.""" await self.bot.errorHandler.on_error(method) - # 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_reaction_add(self, reaction, user): + """Handle when someone reacts to a message.""" await self.bot.eventHandler.on_reaction_add(reaction, user) + def setup(bot): + """Add the eventcog to the bot.""" bot.add_cog(EventCog(bot)) diff --git a/cogs/GameCogs.py b/cogs/GameCogs.py index 4714437..c813e24 100644 --- a/cogs/GameCogs.py +++ b/cogs/GameCogs.py @@ -1,155 +1,177 @@ -from discord.ext import commands -from discord_slash import cog_ext +"""Contains all the cogs that deal with game commands.""" +from discord.ext import commands # Has the cog class +from discord_slash import cog_ext # Used for slash commands -from utils import getParams +from utils import getParams # pylint: disable=import-error params = getParams() + class GamesCog(commands.Cog): - def __init__(self,bot): - """Runs game stuff.""" + """Contains miscellaneous game commands.""" + + def __init__(self, bot): + """Initialize the cog.""" self.bot = bot - # Checks user balance @cog_ext.cog_slash(**params["balance"]) async def balance(self, ctx): + """Check user balance.""" 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): + """Give another user an amount of GwendoBucks.""" 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"): + async def invest(self, ctx, parameters="check"): + """Invest GwendoBucks in the stock market.""" 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 = ""): + async def trivia(self, ctx, answer=""): + """Run a game of trivia.""" await self.bot.games.trivia.triviaParse(ctx, answer) class BlackjackCog(commands.Cog): - def __init__(self,bot): - """Runs game stuff.""" + """Contains the blackjack commands.""" + + def __init__(self, bot): + """Initialize the cog.""" self.bot = bot - # Starts a game of blackjack @cog_ext.cog_subcommand(**params["blackjackStart"]) async def blackjackStart(self, ctx): + """Start a game of blackjack.""" await self.bot.games.blackjack.start(ctx) @cog_ext.cog_subcommand(**params["blackjackBet"]) async def blackjackBet(self, ctx, bet): + """Enter the game of blackjack with a bet.""" await self.bot.games.blackjack.playerDrawHand(ctx, bet) @cog_ext.cog_subcommand(**params["blackjackStand"]) - async def blackjackStand(self, ctx, hand = ""): + async def blackjackStand(self, ctx, hand=""): + """Stand on your hand in blackjack.""" await self.bot.games.blackjack.stand(ctx, hand) @cog_ext.cog_subcommand(**params["blackjackHit"]) - async def blackjackHit(self, ctx, hand = 0): + async def blackjackHit(self, ctx, hand=0): + """Hit on your hand in blackjack.""" await self.bot.games.blackjack.hit(ctx, hand) @cog_ext.cog_subcommand(**params["blackjackDouble"]) - async def blackjackDouble(self, ctx, hand = 0): + async def blackjackDouble(self, ctx, hand=0): + """Double in blackjack.""" await self.bot.games.blackjack.double(ctx, hand) @cog_ext.cog_subcommand(**params["blackjackSplit"]) - async def blackjackSplit(self, ctx, hand = 0): + async def blackjackSplit(self, ctx, hand=0): + """Split your hand in blackjack.""" await self.bot.games.blackjack.split(ctx, hand) @cog_ext.cog_subcommand(**params["blackjackHilo"]) async def blackjackHilo(self, ctx): + """Get the hilo value for the deck in blackjack.""" await self.bot.games.blackjack.hilo(ctx) @cog_ext.cog_subcommand(**params["blackjackShuffle"]) async def blackjackShuffle(self, ctx): + """Shuffle the blackjack game.""" await self.bot.games.blackjack.shuffle(ctx) @cog_ext.cog_subcommand(**params["blackjackCards"]) async def blackjackCards(self, ctx): + """Get the amount of cards left in the blackjack deck.""" await self.bot.games.blackjack.cards(ctx) class ConnectFourCog(commands.Cog): - def __init__(self,bot): - """Runs game stuff.""" + """Contains all the connect four commands.""" + + def __init__(self, bot): + """Initialize the cog.""" self.bot = bot - # Start a game of connect four against a user @cog_ext.cog_subcommand(**params["connectFourStartUser"]) async def connectFourStartUser(self, ctx, user): + """Start a game of connect four against another user.""" 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): + async def connectFourStartGwendolyn(self, ctx, difficulty=3): + """Start a game of connect four against Gwendolyn.""" await self.bot.games.connectFour.start(ctx, difficulty) - # Stop the current game of connect four @cog_ext.cog_subcommand(**params["connectFourSurrender"]) async def connectFourSurrender(self, ctx): + """Surrender the game of connect four.""" await self.bot.games.connectFour.surrender(ctx) class HangmanCog(commands.Cog): - def __init__(self,bot): - """Runs game stuff.""" + """Contains all the hangman commands.""" + + def __init__(self, bot): + """Initialize the cog.""" self.bot = bot - # Starts a game of Hangman @cog_ext.cog_subcommand(**params["hangmanStart"]) async def hangmanStart(self, ctx): + """Start a game of hangman.""" await self.bot.games.hangman.start(ctx) - # Stops a game of Hangman @cog_ext.cog_subcommand(**params["hangmanStop"]) async def hangmanStop(self, ctx): + """Stop the current game of hangman.""" await self.bot.games.hangman.stop(ctx) class HexCog(commands.Cog): - def __init__(self,bot): - """Runs game stuff.""" - self.bot = bot + """Contains all the hex commands.""" + + def __init__(self, bot): + """Initialize the cog.""" + self.bot = bot + self.hex = self.bot.games.hex - # Start a game of Hex against another user @cog_ext.cog_subcommand(**params["hexStartUser"]) async def hexStartUser(self, ctx, user): - await self.bot.games.hex.start(ctx, user) + """Start a game of hex against another player.""" + await self.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 self.bot.games.hex.start(ctx, difficulty) + async def hexStartGwendolyn(self, ctx, difficulty=2): + """Start a game of hex against Gwendolyn.""" + await self.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.hex.placeHex(ctx, coordinates, f"#{ctx.author.id}") + """Place a piece in the hex game.""" + await self.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) + """Undo your last hex move.""" + await self.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) + """Perform a hex swap.""" + await self.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) + """Surrender the hex game.""" + await self.hex.surrender(ctx) def setup(bot): + """Add all the cogs to the bot.""" bot.add_cog(GamesCog(bot)) bot.add_cog(BlackjackCog(bot)) bot.add_cog(ConnectFourCog(bot)) bot.add_cog(HangmanCog(bot)) - bot.add_cog(HexCog(bot)) \ No newline at end of file + bot.add_cog(HexCog(bot)) diff --git a/cogs/LookupCog.py b/cogs/LookupCog.py index 1b23cd1..f41d38b 100644 --- a/cogs/LookupCog.py +++ b/cogs/LookupCog.py @@ -1,24 +1,32 @@ -from discord.ext import commands -from discord_slash import cog_ext +"""Contains the LookupCog, which deals with the lookup commands.""" +from discord.ext import commands # Has the cog class +from discord_slash import cog_ext # Used for slash commands -from utils import getParams +from utils import getParams # pylint: disable=import-error params = getParams() + class LookupCog(commands.Cog): + """Contains the lookup commands.""" + def __init__(self, bot): - """Runs lookup commands.""" + """Initialize the cog.""" self.bot = bot # Looks up a spell @cog_ext.cog_slash(**params["spell"]) async def spell(self, ctx, query): + """Look up a spell.""" await self.bot.lookupFuncs.spellFunc(ctx, query) # Looks up a monster @cog_ext.cog_slash(**params["monster"]) async def monster(self, ctx, query): + """Look up a monster.""" await self.bot.lookupFuncs.monsterFunc(ctx, query) + def setup(bot): - bot.add_cog(LookupCog(bot)) \ No newline at end of file + """Add the cog to the bot.""" + bot.add_cog(LookupCog(bot)) diff --git a/cogs/MiscCog.py b/cogs/MiscCog.py index 136c0fb..ab6e146 100644 --- a/cogs/MiscCog.py +++ b/cogs/MiscCog.py @@ -1,94 +1,99 @@ -import discord, codecs, string, json -from discord.ext import commands -from discord_slash import cog_ext +"""Contains the MiscCog, which deals with miscellaneous commands.""" +from discord.ext import commands # Has the cog class +from discord_slash import cog_ext # Used for slash commands -from utils import getParams # pylint: disable=import-error +from utils import getParams # pylint: disable=import-error params = getParams() + class MiscCog(commands.Cog): + """Contains the miscellaneous commands.""" + def __init__(self, bot): - """Runs misc commands.""" + """Initialize the cog.""" self.bot = bot self.bot.remove_command("help") self.generators = bot.other.generators self.bedreNetflix = bot.other.bedreNetflix self.nerdShit = bot.other.nerdShit - # Sends the bot's latency @cog_ext.cog_slash(**params["ping"]) async def ping(self, ctx): + """Send the bot's latency.""" await ctx.send(f"Pong!\nLatency is {round(self.bot.latency * 1000)}ms") - # Restarts the bot @cog_ext.cog_slash(**params["stop"]) async def stop(self, ctx): + """Stop the bot.""" await self.bot.stop(ctx) - # Gets help for specific command @cog_ext.cog_slash(**params["help"]) - async def helpCommand(self, ctx, command = ""): + async def helpCommand(self, ctx, command=""): + """Get help for commands.""" await self.bot.other.helpFunc(ctx, command) - # Lets you thank the bot @cog_ext.cog_slash(**params["thank"]) async def thank(self, ctx): + """Thank the bot.""" await ctx.send("You're welcome :blush:") - # Sends a friendly message @cog_ext.cog_slash(**params["hello"]) async def hello(self, ctx): + """Greet the bot.""" await self.bot.other.helloFunc(ctx) - # Rolls dice @cog_ext.cog_slash(**params["roll"]) - async def roll(self, ctx, dice = "1d20"): + async def roll(self, ctx, dice="1d20"): + """Roll dice.""" await self.bot.other.rollDice(ctx, dice) - # Sends a random image @cog_ext.cog_slash(**params["image"]) async def image(self, ctx): + """Get a random image from Bing.""" await self.bot.other.imageFunc(ctx) - # Finds a random movie @cog_ext.cog_slash(**params["movie"]) async def movie(self, ctx): + """Get a random movie from the Plex server.""" await self.bot.other.movieFunc(ctx) - # Generates a random name @cog_ext.cog_slash(**params["name"]) async def name(self, ctx): + """Generate a random name.""" await self.generators.nameGen(ctx) - # Generates a random tavern name @cog_ext.cog_slash(**params["tavern"]) async def tavern(self, ctx): + """Generate a random tavern name.""" await self.generators.tavernGen(ctx) - # Finds a page on the Senkulpa wiki @cog_ext.cog_slash(**params["wiki"]) - async def wiki(self, ctx, wikiPage = ""): + async def wiki(self, ctx, wikiPage=""): + """Get a page on a fandom wiki.""" 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): + """Search for a movie and add it to the Plex server.""" 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): + """Search for a show and add it to the Plex server.""" await self.bedreNetflix.requestShow(ctx, show) - #Returns currently downloading torrents @cog_ext.cog_slash(**params["downloading"]) - async def downloading(self, ctx, parameters = "-d"): + async def downloading(self, ctx, parameters="-d"): + """Get the current downloading torrents.""" await self.bedreNetflix.downloading(ctx, parameters) - #Looks up on Wolfram Alpha @cog_ext.cog_slash(**params["wolf"]) async def wolf(self, ctx, query): + """Perform a search on Wolfram Alpha.""" await self.nerdShit.wolfSearch(ctx, query) + def setup(bot): + """Add the cog to the bot.""" bot.add_cog(MiscCog(bot)) diff --git a/cogs/StarWarsCog.py b/cogs/StarWarsCog.py index 57e39f0..72516d4 100644 --- a/cogs/StarWarsCog.py +++ b/cogs/StarWarsCog.py @@ -1,37 +1,40 @@ -import discord, string, json +"""Contains the StarWarsCog, which deals with Star Wars commands.""" from discord.ext import commands from discord_slash import cog_ext -from utils import getParams +from utils import getParams # pylint: disable=import-error params = getParams() -class starWarsCog(commands.Cog): + +class StarWarsCog(commands.Cog): + """Contains the Star Wars commands.""" def __init__(self, bot): - """Runs star wars commands.""" + """Initialize the cog.""" self.bot = bot - # Rolls star wars dice @cog_ext.cog_slash(**params["starWarsRoll"]) - async def starWarsRoll(self, ctx, dice = ""): + async def starWarsRoll(self, ctx, dice=""): + """Roll Star Wars dice.""" await self.bot.starWars.roll.parseRoll(ctx, dice) - # Controls destiny points @cog_ext.cog_slash(**params["starWarsDestiny"]) - async def starWarsDestiny(self, ctx, parameters = ""): + async def starWarsDestiny(self, ctx, parameters=""): + """Control Star Wars destiny points.""" 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): + async def starWarsCrit(self, ctx, severity: int = 0): + """Roll for critical injuries.""" 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 = ""): + async def starWarsCharacter(self, ctx, parameters=""): + """Access and change Star Wars character sheet data.""" await self.bot.starWars.character.parseChar(ctx, parameters) + def setup(bot): - bot.add_cog(starWarsCog(bot)) \ No newline at end of file + """Add the cog to the bot.""" + bot.add_cog(StarWarsCog(bot)) diff --git a/resources/longStrings.json b/resources/longStrings.json new file mode 100644 index 0000000..a7b418f --- /dev/null +++ b/resources/longStrings.json @@ -0,0 +1,4 @@ +{ + "missing parameters" : "Missing command parameters. Try using `!help [command]` to find out how to use the command.", + "Can't log in": "Could not log in. Remember to write your bot token in the credentials.txt file" +} \ No newline at end of file diff --git a/utils/__init__.py b/utils/__init__.py index 7d2a46c..43fa506 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -1,7 +1,10 @@ -"""A collections of utilities used by Gwendolyn and her functions""" +"""A collections of utilities used by Gwendolyn and her functions.""" -__all__ = ["Options", "Credentials", "databaseFuncs", "EventHandler", "ErrorHandler", "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 +from .utilFunctions import (getParams, logThis, cap, makeFiles, + replaceMultiple, emojiToCommand, longStrings) diff --git a/utils/eventHandlers.py b/utils/eventHandlers.py index 491846b..1ec9810 100644 --- a/utils/eventHandlers.py +++ b/utils/eventHandlers.py @@ -1,13 +1,52 @@ -import discord, traceback, discord_slash, sys -from discord.ext import commands +""" +Classes used to handle bot events and errors. + +*Classes* +--------- + EventHandler + ErrorHandler +""" +import discord # Used to init discord.Game and discord.Status, as well +# as compare errors to discord errors and as typehints +import traceback # Used to get the traceback of errors +import sys # Used to get traceback when the specific error is not +# available +from discord.ext import commands # Used to compare errors with command +# errors + +from discord_slash.context import SlashContext +from utils.utilFunctions import emojiToCommand -from .utilFunctions import emojiToCommand class EventHandler(): + """ + Handles bot events. + + *Methods* + --------- + on_ready() + on_slash_command(ctx: discord_slash.context.SlashContext) + on_reaction_add(ctx: discord_slash.context.SlashContext) + """ + def __init__(self, bot): + """Initialize the handler.""" self.bot = bot - async def on_slash_command(self, ctx): + async def on_ready(self): + """Log and sets status when it logs in.""" + await self.bot.databaseFuncs.syncCommands() + name = self.bot.user.name + userid = str(self.bot.user.id) + loggedInMessage = f"Logged in as {name}, {userid}" + self.bot.log(loggedInMessage, level=25) + game = discord.Game("Use /help for commands") + + onlineStatus = discord.Status.online + await self.bot.change_presence(activity=game, status=onlineStatus) + + async def on_slash_command(self, ctx: SlashContext): + """Log when a slash command is given.""" if ctx.subcommand_name is not None: subcommand = f" {ctx.subcommand_name} " else: @@ -21,100 +60,117 @@ class EventHandler(): args = " ".join([str(i) for i in ctx.args]) fullCommand = f"/{ctx.command}{subcommand}{subcommandGroup}{args}" logMessage = f"{ctx.author.display_name} ran {fullCommand}" - self.bot.log(logMessage, str(ctx.channel_id), level = 25) + 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 : discord.Reaction, user): - if user.bot == False: + async def on_reaction_add(self, reaction: discord.Reaction, + user: discord.User): + """Take action if the reaction is on a command message.""" + if not user.bot: + tests = self.bot.databaseFuncs 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 + reactedMessage = f"{user.display_name} reacted to a message" + self.bot.log(reactedMessage, str(channel.id)) + plexData = tests.bedreNetflixReactionTest(message) + # plexData is a list containing 3 elements: whether it was + # the addshow/addmovie command message the reaction was to + # (bool), whether it's a movie (bool) (if false, it's a + # show), and the imdb ids/names for the for the movies or + # shows listed in the message (list). - bedreNetflixMessage, addMovie, imdbIds = self.bot.databaseFuncs.bedreNetflixReactionTest(channel, message) + reactionTestParams = [message, f"#{str(user.id)}"] - if connectFourTheirTurn: + if tests.connectFourReactionTest(*reactionTestParams): column = emojiToCommand(reaction.emoji) - await self.bot.games.connectFour.placePiece(message, f"#{user.id}", column-1) - elif bedreNetflixMessage and addMovie: - moviePick = emojiToCommand(reaction.emoji) - if moviePick == "none": - imdbID = None - else: - imdbID = imdbIds[moviePick-1] + params = [message, f"#{user.id}", column-1] + await self.bot.games.connectFour.placePiece(*params) - if isinstance(channel, discord.DMChannel): - await message.delete() - await self.bot.other.bedreNetflix.addMovie(message, imdbID, False) - else: - await message.clear_reactions() - await self.bot.other.bedreNetflix.addMovie(message, imdbID) - elif bedreNetflixMessage and not addMovie: - showPick = emojiToCommand(reaction.emoji) - if showPick == "none": - imdbName = None - else: - imdbName = imdbIds[showPick-1] + if plexData[0]: + plexFuncs = self.bot.other.bedreNetflix + if plexData[1]: + moviePick = emojiToCommand(reaction.emoji) + if moviePick == "none": + imdbID = None + else: + imdbID = plexData[2][moviePick-1] - if isinstance(channel, discord.DMChannel): - await message.delete() - await self.bot.other.bedreNetflix.addShow(message, imdbName, False) + if isinstance(channel, discord.DMChannel): + await message.delete() + await plexFuncs.addMovie(message, imdbID, False) + else: + await message.clear_reactions() + await plexFuncs.addMovie(message, imdbID) else: - await message.clear_reactions() - await self.bot.other.bedreNetflix.addShow(message, imdbName) + showPick = emojiToCommand(reaction.emoji) + if showPick == "none": + imdbName = None + else: + imdbName = plexData[2][showPick-1] - elif self.bot.databaseFuncs.hangmanReactionTest(channel, message, f"#{user.id}"): + if isinstance(channel, discord.DMChannel): + await message.delete() + await plexFuncs.addShow(message, imdbName, False) + else: + await message.clear_reactions() + await plexFuncs.addShow(message, imdbName) + + elif tests.hangmanReactionTest(*reactionTestParams): self.bot.log("They reacted to the hangman message") - if ord(reaction.emoji) in range(127462,127488): + if ord(reaction.emoji) in range(127462, 127488): + # The range is letter-emojis guess = chr(ord(reaction.emoji)-127397) - await self.bot.games.hangman.guess(message, f"#{user.id}", guess) + # Converts emoji to letter + params = [message, f"#{user.id}", guess] + await self.bot.games.hangman.guess(*params) else: self.bot.log("Bot they didn't react with a valid guess") + class ErrorHandler(): + """ + Handles errors. + + *Methods* + --------- + on_slash_command_error(ctx: discord_slash.context.SlashContext, + error: Exception) + on_error(method: str) + """ + def __init__(self, bot): + """Initialize the handler.""" self.bot = bot - async def on_slash_command_error(self, ctx, error): + async def on_slash_command_error(self, ctx: SlashContext, + error: Exception): + """Log when there's a slash command.""" if isinstance(error, commands.CommandNotFound): - await ctx.send("That's not a command (error code 001)") + await ctx.send("That's not a command") 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.") + self.bot.log(f"{error}", str(ctx.channel_id)) + await ctx.send(self.bot.longStrings["missing parameters"]) 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] + params = [type(error), error, error.__traceback__] + exception = traceback.format_exception(*params) exceptionString = "".join(exception) - self.bot.log([f"exception in /{ctx.name}", f"{exceptionString}"],str(ctx.channel_id), 40) + logMessages = [f"exception in /{ctx.name}", f"{exceptionString}"] + self.bot.log(logMessages, str(ctx.channel_id), 40) if isinstance(error, discord.errors.NotFound): - self.bot.log("Context is non-existant", level = 40) + 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): + async def on_error(self, method: str): + """Log when there's an error.""" 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 + logMessages = [f"exception in {method}", f"{exceptionString}"] + self.bot.log(logMessages, level=40) diff --git a/utils/helperClasses.py b/utils/helperClasses.py index b643071..ca3ef41 100644 --- a/utils/helperClasses.py +++ b/utils/helperClasses.py @@ -1,6 +1,44 @@ -import re, git, os, json, time +""" +Contains classes used for utilities. -def sanitize(data : str, options : bool = False): +*Functions* +----------- + Sanitize(data: str, lowerCaseValue: bool = false) -> dict + +*Classes* +--------- + Options() + Credentials() + DatabaseFuncs() +""" +import re # Used in getID +import git # Used to pull when stopping +import os # Used to test if files exist +import json # Used to read the data about addmovie/addshow +import time # Used to test how long it's been since commands were synced +import discord # Used for type hints + + +def sanitize(data: str, lowerCaseValue: bool = False): + """ + Sanitize and create a dictionary from a string. + + Each element is created from a line with a : in it. The key is left + of the :, the value is right of it. + + *Parameters* + ------------ + data: str + The string to create a dict from. + lowerCaseValue: bool = False + Whether the value of each element should be lowercase. + + *Returns* + --------- + dct: dict + The sanitized dictionary of elements. + + """ data = data.splitlines() dct = {} for line in data: @@ -8,7 +46,7 @@ def sanitize(data : str, options : bool = False): lineValues = line.split(":") lineValues[0] = lineValues[0].lower() lineValues[1] = lineValues[1].replace(" ", "") - if options: + if lowerCaseValue: lineValues[1] = lineValues[1].lower() if lineValues[0] in ["testing guild ids", "admins"]: @@ -23,18 +61,26 @@ def sanitize(data : str, options : bool = False): return dct + class Options(): + """Contains the options for the bot.""" + def __init__(self): - with open("options.txt","r") as f: + """Initialize the options.""" + with open("options.txt", "r") as f: data = sanitize(f.read(), True) self.testing = data["testing"] self.guildIds = data["testing guild ids"] self.admins = data["admins"] + class Credentials(): + """Contains the credentials for the bot and apis.""" + def __init__(self): - with open("credentials.txt","r") as f: + """Initialize the credentials.""" + with open("credentials.txt", "r") as f: data = sanitize(f.read()) self.token = data["bot token"] @@ -46,34 +92,99 @@ class Credentials(): self.radarrKey = data["radarr api key"] self.sonarrKey = data["sonarr api key"] + class databaseFuncs(): + """ + Manages database functions. + + *Methods* + --------- + getName(userID: str) -> str + getID(userName: str) -> str + deleteGame(gameType: str, channel: str) + wipeGames() + connectFourReactionTest(message: discord.Message, + user: discord.User) -> bool + hangmanReactionTest(message: discord.Message, + user: discord.User) -> bool + BedreNetflixReactionTest(message: discord.Message, + user: discord.User) -> bool, bool, + list + syncCommands() + """ + def __init__(self, bot): + """Initialize the class.""" self.bot = bot - def getName(self, userID): - user = self.bot.database["users"].find_one({"_id":userID}) + def getName(self, userID: str): + """ + Get the name of a user you have the # id of. + + *Parameters: + ------------ + userID: str + The id of the user you want the name of. The format is + "#" + str(discord.User.id) + + *Returns* + --------- + userName: str + The name of the user. If the user couldn't be found, + returns the userID. + """ + user = self.bot.database["users"].find_one({"_id": userID}) if userID == f"#{self.bot.user.id}": return "Gwendolyn" - elif user != None: + elif user is not None: return user["user name"] else: self.bot.log(f"Couldn't find user {userID}") return userID - def getID(self,userName): - user = self.bot.database["users"].find_one({"user name":re.compile(userName, re.IGNORECASE)}) + def getID(self, userName: str): + """ + Get the id of a user you have the username of. - if user != None: + *Parameters: + ------------ + userName: str + The name of the user you want the id of. + + *Returns* + --------- + userID: str + The id of the user in the format "#" + + str(discord.User.id). If the user couldn't be found, + returns the userName. + """ + userSearch = {"user name": re.compile(userName, re.IGNORECASE)} + user = self.bot.database["users"].find_one(userSearch) + + if user is not None: return user["_id"] else: self.bot.log("Couldn't find user "+userName) return None - def deleteGame(self, gameType, channel): - self.bot.database[gameType].delete_one({"_id":channel}) + def deleteGame(self, gameType: str, channel: str): + """ + Remove a game from the database. - def stopServer(self): + *Parameters* + ------------ + gameType: str + The name of the collection the game is in, like + "hangman games", "blackjack games" etc. + channel: str + The channel id of the channel the game is on as a + string. + """ + self.bot.database[gameType].delete_one({"_id": channel}) + + def wipeGames(self): + """Delete all running games and pull from git.""" self.bot.database["trivia questions"].delete_many({}) self.bot.database["blackjack games"].delete_many({}) self.bot.database["connect 4 games"].delete_many({}) @@ -84,35 +195,80 @@ class databaseFuncs(): g = git.cmd.Git("") g.pull() - def connectFourReactionTest(self,channel,message,user): - game = self.bot.database["connect 4 games"].find_one({"_id":str(channel.id)}) + def connectFourReactionTest(self, message: discord.Message, + user: discord.User): + """ + Test if the given message is the current connect four game. - with open("resources/games/oldImages/connectFour"+str(channel.id), "r") as f: + Also tests if the given user is the one who's turn it is. + + *Parameters* + ------------ + message: discord.Message + The message to test. + user: discord.User + The user to test. + *Returns* + --------- + : bool + Whether the given message is the current connect four + game and if the user who reacted is the user who's turn + it is. + """ + channel = message.channel + channelSearch = {"_id": str(channel.id)} + game = self.bot.database["connect 4 games"].find_one(channelSearch) + + filePath = f"resources/games/oldImages/connectFour{channel.id}" + with open(filePath, "r") as f: oldImage = int(f.read()) if message.id == oldImage: self.bot.log("They reacted to the connectFour game") turn = game["turn"] if user == game["players"][turn]: - return True, turn+1 + return True else: self.bot.log("It wasn't their turn") - return False, 0 + return False else: - return False, 0 + return False - def hangmanReactionTest(self, channel, message, user): - try: - with open("resources/games/oldImages/hangman"+str(channel.id), "r") as f: + def hangmanReactionTest(self, message: discord.Message, + user: discord.User): + """ + Test if the given message is the current hangman game. + + Also tests if the given user is the one who's playing hangman. + + *Parameters* + ------------ + message: discord.Message + The message to test. + user: discord.User + The user to test. + *Returns* + --------- + : bool + Whether the given message is the current hangman game + and if the user who reacted is the user who's playing + hangman. + """ + channel = message.channel + filePath = f"resources/games/oldImages/hangman{channel.id}" + if os.path.isfile(filePath): + with open(filePath, "r") as f: oldMessages = f.read().splitlines() - except: + else: return False gameMessage = False for oldMessage in oldMessages: oldMessageID = int(oldMessage) if message.id == oldMessageID: - game = self.bot.database["hangman games"].find_one({"_id":str(channel.id)}) + database = self.bot.database["hangman games"] + channelSearch = {"_id": str(channel.id)} + game = database.find_one(channelSearch) if user == game["player"]: gameMessage = True @@ -120,9 +276,30 @@ class databaseFuncs(): return gameMessage - def bedreNetflixReactionTest(self, channel, message): - if os.path.isfile(f"resources/bedreNetflix/oldMessage{str(channel.id)}"): - with open("resources/bedreNetflix/oldMessage"+str(channel.id),"r") as f: + def bedreNetflixReactionTest(self, message: discord.Message): + """ + Test if the given message is the response to a plex request. + + *Parameters* + ------------ + message: discord.Message + The message to test. + + *Returns* + --------- + : bool + Whether the message is the response to a plex request. + : bool + Whether it was a movie request (false for a show + request) + : list + A list of ids or names of the shows or movies that + Gwendolyn presented after the request. + """ + channel = message.channel + filePath = f"resources/bedreNetflix/oldMessage{str(channel.id)}" + if os.path.isfile(filePath): + with open(filePath, "r") as f: data = json.load(f) else: return False, None, None @@ -136,6 +313,7 @@ class databaseFuncs(): return False, None, None async def syncCommands(self): + """Sync the slash commands with the discord API.""" collection = self.bot.database["last synced"] lastSynced = collection.find_one() now = time.time() @@ -144,6 +322,6 @@ class databaseFuncs(): self.bot.log(f"Updating commands: {slashCommandList}") await self.bot.slash.sync_all_commands() idNumber = lastSynced["_id"] - queryFilter = {"_id" : idNumber} - update = {"$set" : {"last synced" : now}} + queryFilter = {"_id": idNumber} + update = {"$set": {"last synced": now}} collection.update_one(queryFilter, update) diff --git a/utils/utilFunctions.py b/utils/utilFunctions.py index 1d0600b..6fea627 100644 --- a/utils/utilFunctions.py +++ b/utils/utilFunctions.py @@ -1,3 +1,18 @@ +""" +Contains utility functions used by parts of the bot. + +*Functions* +----------- + longstrings() -> dict + getParams() -> dict + logThis(messages: Union[str, list], channel: str = "", + level: int = 20) + cap(s: str) -> str + makeFiles() + replaceMultiple(mainString: str, toBeReplaced: list, + newString: str) -> str + emojiToCommand(emoji: str) -> str +""" import json import logging import os @@ -5,22 +20,55 @@ import sys import imdb from .helperClasses import Options + +# All of this is logging configuration 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(format=FORMAT, datefmt=DATEFORMAT, level=logging.INFO, filename="gwendolyn.log") +loggingConfigParams = { + "format": FORMAT, + "datefmt": DATEFORMAT, + "level": logging.INFO, + "filename": "gwendolyn.log" +} +logging.basicConfig(**loggingConfigParams) logger = logging.getLogger("Gwendolyn") printer = logging.getLogger("printer") handler = logging.StreamHandler(sys.stdout) -handler.setFormatter(logging.Formatter(fmt = PRINTFORMAT, datefmt=DATEFORMAT)) +handler.setFormatter(logging.Formatter(fmt=PRINTFORMAT, datefmt=DATEFORMAT)) printer.addHandler(handler) printer.propagate = False -imdb._logging.setLevel("CRITICAL") +imdb._logging.setLevel("CRITICAL") # Basically disables imdbpy +# logging, since it's printed to the terminal. + + +def longStrings(): + """ + Get the data from resources/longStrings.json. + + *Returns* + --------- + data: dict + The long strings and their keys. + """ + with open("resources/longStrings.json", "r") as f: + data = json.load(f) + + return data + def getParams(): + """ + Get the slash command parameters. + + *Returns* + --------- + params: dict + The parameters for every slash command. + """ with open("resources/slashParameters.json", "r") as f: params = json.load(f) @@ -32,8 +80,25 @@ def getParams(): return params -def logThis(messages, channel : str = "", level : int = 20): - channel = channel.replace("Direct Message with ","") + +def logThis(messages, channel: str = "", level: int = 20): + """ + Log something in Gwendolyn's logs. + + *Parameters* + ------------ + messages: Union[str, list] + A string or list of strings to be logged. If there are + multiple strings and the level is PRINT (25) or higher, + only the first string will be printed. + channel: str = "" + The channel the event to be logged occurred in. Will be + logged along with the message(s). + level: int = 20 + The level to log the message(s) at. If PRINT (25) or + higher, the first message will be printed to the console. + """ + channel = channel.replace("Direct Message with ", "") if type(messages) is str: messages = [messages] @@ -41,9 +106,11 @@ def logThis(messages, channel : str = "", level : int = 20): for x, msg in enumerate(messages): if channel != "": - messages[x] = f"{msg} - ({channel})" + messages[x] = f"{msg} - ({channel})" # Adds channel to log + # messages - if len(messages) > 1: + if len(messages) > 1: # Tells user to check the log if there are + # more messages there printMessage += " (details in log)" if level >= 25: @@ -52,10 +119,24 @@ def logThis(messages, channel : str = "", level : int = 20): for logMessage in messages: logger.log(level, logMessage) -# Capitalizes all words except some of them -def cap(s): - no_caps_list = ["of","the"] - # Capitalizes a strink like a movie title + +def cap(s: str): + """ + Capitalize a string like a movie title. + + That means "of" and "the" are not capitalized. + + *Parameters* + ------------ + s: str + The string to capitalized. + + *Returns* + --------- + res: str + The capitalized string. + """ + no_caps_list = ["of", "the"] word_number = 0 lst = s.split() res = '' @@ -67,22 +148,25 @@ def cap(s): res = res[:-1] return res -def makeFiles(): - def makeJsonFile(path,content): - # Creates json file if it doesn't exist - if not os.path.isfile(path): - logThis(path.split("/")[-1]+" didn't exist. Making it now.") - with open(path,"w") as f: - json.dump(content,f,indent = 4) - def makeTxtFile(path,content): - # Creates txt file if it doesn't exist +def makeFiles(): + """Create all the files and directories needed by Gwendolyn.""" + def makeJsonFile(path, content): + """Create json file if it doesn't exist.""" if not os.path.isfile(path): logThis(path.split("/")[-1]+" didn't exist. Making it now.") - with open(path,"w") as f: + with open(path, "w") as f: + json.dump(content, f, indent=4) + + def makeTxtFile(path, content): + """Create txt file if it doesn't exist.""" + if not os.path.isfile(path): + logThis(path.split("/")[-1]+" didn't exist. Making it now.") + with open(path, "w") as f: f.write(content) def directory(path): + """Create directory if it doesn't exist.""" if not os.path.isdir(path): os.makedirs(path) logThis("The "+path.split("/")[-1]+" directory didn't exist") @@ -90,27 +174,58 @@ def makeFiles(): with open("resources/startingFiles.json") as f: data = json.load(f) + for path, content in data["json"].items(): + makeJsonFile(path, content) + + for path, content in data["txt"].items(): + makeTxtFile(path, content) + for path in data["folder"]: directory(path) - for path, content in data["json"].items(): - makeJsonFile(path,content) - for path, content in data["txt"].items(): - makeTxtFile(path,content) +def replaceMultiple(mainString: str, toBeReplaced: list, newString: str): + """ + Replace multiple substrings in a string with the same substring. -# Replaces multiple things with the same thing -def replaceMultiple(mainString, toBeReplaces, newString): + *Parameters* + ------------ + mainString: str + The string to replace substrings in. + toBeReplaced: list + The substrings to replace. + newString: str + The string to replace the substrings with. + + *Returns* + --------- + mainString: str + The string with the substrings replaced. + """ # Iterate over the strings to be replaced - for elem in toBeReplaces : + for elem in toBeReplaced: # Check if string is in the main string - if elem in mainString : + if elem in mainString: # Replace the string mainString = mainString.replace(elem, newString) return mainString -def emojiToCommand(emoji): + +def emojiToCommand(emoji: str): + """ + Convert emoji to text. + + *Parameters* + ------------ + emoji: str + The emoji to decipher. + + *Returns* + --------- + : str + The deciphered string. + """ if emoji == "1️⃣": return 1 elif emoji == "2️⃣": @@ -131,4 +246,5 @@ def emojiToCommand(emoji): return "none" elif emoji == "✔️": return 1 - else: return "" + else: + return "" From a3cd19af5cacecc3c7b1f75715acd5c5b77d1b9c Mon Sep 17 00:00:00 2001 From: NikolajDanger Date: Sat, 17 Apr 2021 20:59:47 +0200 Subject: [PATCH 12/23] :black_joker: Blackjack --- cogs/GameCogs.py | 2 +- funcs/__init__.py | 2 +- funcs/games/blackjack.py | 1489 ++++++++++++++++++++++++------------ requirements.txt | 6 +- resources/longStrings.json | 10 +- utils/utilFunctions.py | 12 +- 6 files changed, 1016 insertions(+), 505 deletions(-) diff --git a/cogs/GameCogs.py b/cogs/GameCogs.py index c813e24..ebd1f54 100644 --- a/cogs/GameCogs.py +++ b/cogs/GameCogs.py @@ -50,7 +50,7 @@ class BlackjackCog(commands.Cog): @cog_ext.cog_subcommand(**params["blackjackBet"]) async def blackjackBet(self, ctx, bet): """Enter the game of blackjack with a bet.""" - await self.bot.games.blackjack.playerDrawHand(ctx, bet) + await self.bot.games.blackjack.enterGame(ctx, bet) @cog_ext.cog_subcommand(**params["blackjackStand"]) async def blackjackStand(self, ctx, hand=""): diff --git a/funcs/__init__.py b/funcs/__init__.py index 729f445..597efd8 100644 --- a/funcs/__init__.py +++ b/funcs/__init__.py @@ -1,6 +1,6 @@ """A collection of all Gwendolyn functions.""" -__all__ = ["Games" , "Money", "LookupFuncs", "StarWars"] +__all__ = ["Games", "Money", "LookupFuncs", "StarWars"] from .games import Money, Games diff --git a/funcs/games/blackjack.py b/funcs/games/blackjack.py index 1d68b0d..075e33d 100644 --- a/funcs/games/blackjack.py +++ b/funcs/games/blackjack.py @@ -1,47 +1,109 @@ -import random -import math -import datetime -import asyncio -import discord +""" +Runs commands, game logic and imaging for blackjack games. + +*Classes* +--------- + Blackjack + Contains the blackjack game logic. + DrawBlackjack + Draws images of the blackjack table. +""" +import random # Used to shuffle the blackjack cards +import math # Used for flooring decimal numbers +import datetime # Used to generate the game id +import asyncio # Used for sleeping +import discord # Used for discord.file +import discord_slash # Used for typehints from PIL import Image, ImageDraw, ImageFont from shutil import copyfile from utils import replaceMultiple + class Blackjack(): - def __init__(self,bot): + """ + Deals with blackjack commands and gameplay logic. + + *Methods* + --------- + hit(ctx: discord_slash.context.SlashContext, + handNumber: int = 0) + double(ctx: discord_slash.context.SlashContext, + handNumber: int = 0) + stand(ctx: discord_slash.context.SlashContext, + handNumber: int = 0) + split(ctx: discord_slash.context.SlashContext, + handNumber: int = 0) + enterGame(ctx: discord_slash.context.SlashContext, bet: int) + start(ctx: discord_slash.context.SlashContext) + hilo(ctx: discord_slash.context.SlashContext) + shuffle(ctx: discord_slash.context.SlashContext) + cards(ctx: discord_slash.context.SlashContext) + """ + + def __init__(self, bot): + """Initialize the class.""" self.bot = bot self.draw = DrawBlackjack(bot) + self.decks = 4 - # Shuffles the blackjack cards - def blackjackShuffle(self, decks, channel): + def _blackjackShuffle(self, channel: str): + """ + Shuffle an amount of decks equal to self.decks. + + The shuffled cards are placed in the database as the cards that + are used by other blackjack functions. + + *Parameters* + ------------ + channel: str + The id of the channel where the cards will be used. + """ 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 + allDecks = deck.split("\n") * self.decks random.shuffle(allDecks) - self.bot.database["blackjack cards"].update_one({"_id":channel},{"$set":{"_id":channel,"cards":allDecks}},upsert=True) + blackjackCards = self.bot.database["blackjack cards"] + cards = {"_id": channel} + cardUpdater = {"$set": {"_id": channel, "cards": allDecks}} + blackjackCards.update_one(cards, cardUpdater, upsert=True) # Creates hilo file - self.bot.log("creating hilo doc for "+channel) + self.bot.log(f"creating hilo doc for {channel}") data = 0 - self.bot.database["hilo"].update_one({"_id":channel},{"$set":{"_id":channel,"hilo":data}},upsert=True) + blackjackHilo = self.bot.database["hilo"] + hiloUpdater = {"$set": {"_id": channel, "hilo": data}} + blackjackHilo.update_one({"_id": channel}, hiloUpdater, upsert=True) - return + def _calcHandValue(self, hand: list): + """ + Calculate the value of a blackjack hand. - # Calculates the value of a blackjack hand - def calcHandValue(self, hand : list): - self.bot.log("Calculating hand value") + *Parameters* + ------------ + hand: list + The hand to calculate the value of. Each element in the + list is a card represented as a string in the format of + "xy" where x is the number of the card (0, k, q, and j + for 10s, kings, queens and jacks) and y is the suit (d, + c, h or s). + + *Returns* + --------- + handValue: int + The blackjack value of the hand. + """ values = [] values.append(0) for card in hand: cardValue = card[0] - cardValue = replaceMultiple(cardValue,["0","k","q","j"],"10") + cardValue = replaceMultiple(cardValue, ["0", "k", "q", "j"], "10") if cardValue == "a": length = len(values) for x in range(length): @@ -58,102 +120,180 @@ class Blackjack(): if value <= 21: handValue = value - self.bot.log("Calculated "+str(hand)+" to be "+str(handValue)) + self.bot.log(f"Calculated the value of {hand} to be {handValue}") return handValue - # Draws a card from the deck - def drawCard(self, channel): + def _drawCard(self, channel: str): + """ + Draw a card from the stack. + + *Parameters* + ------------ + channel: str + The id of the channel the card is drawn in. + """ self.bot.log("drawing a card") - drawnCard = self.bot.database["blackjack cards"].find_one({"_id":channel})["cards"][0] - self.bot.database["blackjack cards"].update_one({"_id":channel},{"$pop":{"cards":-1}}) - value = self.calcHandValue([drawnCard]) + blackjackCards = self.bot.database["blackjack cards"] + drawnCard = blackjackCards.find_one({"_id": channel})["cards"][0] + blackjackCards.update_one({"_id": channel}, {"$pop": {"cards": -1}}) + value = self._calcHandValue([drawnCard]) + + blackjackHilo = self.bot.database["hilo"] if value <= 6: - self.bot.database["hilo"].update_one({"_id":channel},{"$inc":{"hilo":1}}) + blackjackHilo.update_one({"_id": channel}, {"$inc": {"hilo": 1}}) elif value >= 10: - self.bot.database["hilo"].update_one({"_id":channel},{"$inc":{"hilo":-1}}) + blackjackHilo.update_one({"_id": channel}, {"$inc": {"hilo": -1}}) return drawnCard - # Dealer draws a card and checks if they should draw another one - def dealerDraw(self,channel): - game = self.bot.database["blackjack games"].find_one({"_id":channel}) + def _dealerDraw(self, channel: str): + """ + Draw a card for the dealer. + + *Parameters* + ------------ + channel: str + The id of the channel to draw a card for the dealer in. + + *Returns* + --------- + done: bool + Whether the dealer is done drawing cards. + """ + game = self.bot.database["blackjack games"].find_one({"_id": channel}) done = False dealerHand = game["dealer hand"] - if self.calcHandValue(dealerHand) < 17: - dealerHand.append(self.drawCard(channel)) - self.bot.database["blackjack games"].update_one({"_id":channel},{"$set":{"dealer hand":dealerHand}}) + blackjackGames = self.bot.database["blackjack games"] + + if self._calcHandValue(dealerHand) < 17: + dealerHand.append(self._drawCard(channel)) + dealerUpdater = {"$set": {"dealer hand": dealerHand}} + blackjackGames.update_one({"_id": channel}, dealerUpdater) else: done = True - if self.calcHandValue(dealerHand) > 21: - self.bot.database["blackjack games"].update_one({"_id":channel},{"$set":{"dealer busted":True}}) + if self._calcHandValue(dealerHand) > 21: + dealerUpdater = {"$set": {"dealer busted": True}} + blackjackGames.update_one({"_id": channel}, dealerUpdater) return done - # Goes to the next round and calculates some stuff - def blackjackContinue(self, channel): + def _blackjackContinue(self, channel: str): + """ + Continues the blackjack game to the next round. + + *Parameters* + ------------ + channel: str + The id of the channel the blackjack game is in + + *Returns* + --------- + sendMessage: str + The message to send to the channel. + allStanding: bool + If all players are standing. + gameDone: bool + If the game has finished. + """ self.bot.log("Continuing blackjack game") - game = self.bot.database["blackjack games"].find_one({"_id":channel}) + game = self.bot.database["blackjack games"].find_one({"_id": channel}) done = False - self.bot.database["blackjack games"].update_one({"_id":channel},{"$inc":{"round":1}}) + blackjackGames = self.bot.database["blackjack games"] + blackjackGames.update_one({"_id": channel}, {"$inc": {"round": 1}}) allStanding = True preAllStanding = True - message = "All players are standing. The dealer now shows his cards and draws." + message = self.bot.longStrings["Blackjack all players standing"] if game["all standing"]: self.bot.log("All are standing") - done = self.dealerDraw(channel) + done = self._dealerDraw(channel) message = "The dealer draws a card." - game = self.bot.database["blackjack games"].find_one({"_id":channel}) + blackjackGames.find_one({"_id": channel}) self.bot.log("Testing if all are standing") for user in game["user hands"]: - try: - newUser, allStanding, preAllStanding = self.testIfStanding(game["user hands"][user],allStanding,preAllStanding,True) - self.bot.database["blackjack games"].update_one({"_id":channel},{"$set":{"user hands."+user:newUser}}) - except: - self.bot.log("Error in testing if all are standing (error code 1331)") + userHand = game["user hands"][user] + testParams = [userHand, allStanding, preAllStanding, True] + standingTest = (self._testIfStanding(*testParams)) + newUser, allStanding, preAllStanding = standingTest + handUpdater = {"$set": {"user hands."+user: newUser}} + blackjackGames.update_one({"_id": channel}, handUpdater) if allStanding: - self.bot.database["blackjack games"].update_one({"_id":channel},{"$set":{"all standing":True}}) + gameUpdater = {"$set": {"all standing": True}} + blackjackGames.update_one({"_id": channel}, gameUpdater) - try: - self.draw.drawImage(channel) - except: - self.bot.log("Error drawing blackjack table (error code 1340)") + self.draw.drawImage(channel) if allStanding: - if done == False: + if not done: return message, True, done else: return "The dealer is done drawing cards", True, done elif preAllStanding: return "", True, done else: - if game["round"] == 1: - firstRoundMessage = ". You can also double down with \"/blackjack double\" or split with \"/blackjack split\"" + if game["round"] == 0: + firstRoundMsg = self.bot.longStrings["Blackjack first round"] else: - firstRoundMessage = "" - return "You have 2 minutes to either hit or stand with \"/blackjack hit\" or \"/blackjack stand\""+firstRoundMessage+". It's assumed you're standing if you don't make a choice.", False, done + firstRoundMsg = "" - def testIfStanding(self, hand,allStanding,preAllStanding,topLevel): - if hand["hit"] == False: + sendMessage = self.bot.longStrings["Blackjack commands"] + print(firstRoundMsg) + sendMessage = sendMessage.format(firstRoundMsg) + return sendMessage, False, done + + def _testIfStanding(self, hand: dict, allStanding: bool, + preAllStanding: bool, topLevel: bool): + """ + Test if a player is standing on all their hands. + + Also resets the hand if it's not standing + + *Parameters* + ------------ + hand: dict + The hand to test and reset. + allStanding: bool + Is set to True at the top level. If it's false, the + player is not standing on one of the previously tested + hands. + preAllStanding: bool + Is set to True at the top level. + topLevel: bool + If the input hand is _all_ if the player's hands. If + False, it's one of the hands resulting from a split. + + *Returns* + --------- + hand: dict + The reset hand. + allStanding: bool + If the player is standing on all their hands. + preAllStanding: bool + Is true if allStanding is True, or if a player has done + something equivalent to standing but still needs to see + the newly drawn card. + """ + # If the user has not hit, they are by definition standing. + if not hand["hit"]: hand["standing"] = True - if hand["standing"] == False: + if not hand["standing"]: allStanding = False - if self.calcHandValue(hand["hand"]) >= 21 or hand["doubled"]: + if self._calcHandValue(hand["hand"]) >= 21 or hand["doubled"]: hand["standing"] = True else: preAllStanding = False @@ -162,29 +302,344 @@ class Blackjack(): if topLevel: if hand["split"] >= 1: - hand["other hand"], allStanding, preAllStanding = self.testIfStanding(hand["other hand"],allStanding,preAllStanding,False) + testHand = hand["other hand"] + testParams = [testHand, allStanding, preAllStanding, False] + standingTest = (self._testIfStanding(*testParams)) + hand["other hand"], allStanding, preAllStanding = standingTest if hand["split"] >= 2: - hand["third hand"], allStanding, preAllStanding = self.testIfStanding(hand["third hand"],allStanding,preAllStanding,False) + testHand = hand["third hand"] + testParams = [testHand, allStanding, preAllStanding, False] + standingTest = (self._testIfStanding(*testParams)) + hand["third hand"], allStanding, preAllStanding = standingTest if hand["split"] >= 3: - hand["fourth hand"], allStanding, preAllStanding = self.testIfStanding(hand["fourth hand"],allStanding,preAllStanding,False) + testHand = hand["fourth hand"] + testParams = [testHand, allStanding, preAllStanding, False] + standingTest = (self._testIfStanding(*testParams)) + hand["fourth hand"], allStanding, preAllStanding = standingTest return hand, allStanding, preAllStanding - # When players try to hit - async def hit(self, ctx, handNumber = 0): + def _blackjackFinish(self, channel: str): + """ + Generate the winnings message after the blackjack game ends. + + *Parameters* + ------------ + channel: str + The id of the channel the game is in. + + *Returns* + --------- + finalWinnings: str + The winnings message. + """ + finalWinnings = "*Final Winnings:*\n" + + game = self.bot.database["blackjack games"].find_one({"_id": channel}) + + dealerValue = self._calcHandValue(game["dealer hand"]) + dealerBlackjack = game["dealer blackjack"] + dealerBusted = game["dealer busted"] + + for user in game["user hands"]: + _calcWinningsParams = [ + game["user hands"][user], + dealerValue, + True, + dealerBlackjack, + dealerBusted + ] + winningCalc = (self._calcWinnings(*_calcWinningsParams)) + winnings, netWinnings, reason = winningCalc + + userName = self.bot.databaseFuncs.getName(user) + + if winnings < 0: + if winnings == -1: + finalWinnings += f"{userName} lost 1 GwendoBuck {reason}\n" + else: + moneyLost = -1 * winnings + winningText = f"{userName} lost {moneyLost} GwendoBucks" + winningText += f" {reason}\n" + finalWinnings += winningText + else: + if winnings == 1: + finalWinnings += f"{userName} won 1 GwendoBuck {reason}\n" + else: + winningText = f"{userName} won {winnings} GwendoBucks" + winningText += f" {reason}\n" + finalWinnings += winningText + + self.bot.money.addMoney(user, netWinnings) + + self.bot.database["blackjack games"].delete_one({"_id": channel}) + + return finalWinnings + + def _calcWinnings(self, hand: dict, dealerValue: int, topLevel: bool, + dealerBlackjack: bool, dealerBusted: bool): + """ + Calculate how much a user has won/lost in the blackjack game. + + *Parameters* + ------------ + hand: dict + The hand to calculate the winnings of. + dealerValue: int + The dealer's hand value. + topLevel: bool + If the input hand is _all_ if the player's hands. If + False, it's one of the hands resulting from a split. + dealerBlackjack: bool + If the dealer has a blackjack. + dealerBusted: bool + If the dealer busted. + + *Returns* + --------- + winnings: int + How much the player has won/lost. + netWinnings: int + winnings minus the original bet. This is added to the + user's account, since the bet was removed from their + account when they placed the bet. + reason: str + The reason for why they won/lost. + """ + self.bot.log("Calculating winnings") + reason = "" + bet = hand["bet"] + winnings = -1 * bet + netWinnings = 0 + handValue = self._calcHandValue(hand["hand"]) + + if hand["blackjack"] and not dealerBlackjack: + reason += "(blackjack)" + winnings += math.floor(2.5 * bet) + netWinnings += math.floor(2.5 * bet) + elif dealerBlackjack: + reason += "(dealer blackjack)" + elif hand["busted"]: + reason += "(busted)" + else: + if dealerBusted: + reason = "(dealer busted)" + winnings += 2 * bet + netWinnings += 2 * bet + elif handValue > dealerValue: + winnings += 2 * bet + netWinnings += 2 * bet + reason = "(highest value)" + elif handValue == dealerValue: + reason = "(pushed)" + winnings += bet + netWinnings += bet + else: + reason = "(highest value)" + + if topLevel: + if hand["split"] >= 1: + _calcWinningsParams = [ + hand["other hand"], + dealerValue, + False, + dealerBlackjack, + dealerBusted + ] + winningsCalc = self._calcWinnings(*_calcWinningsParams) + winningsTemp, netWinningsTemp, reasonTemp = winningsCalc + winnings += winningsTemp + netWinnings += netWinningsTemp + reason += reasonTemp + if hand["split"] >= 2: + _calcWinningsParams = [ + hand["third hand"], + dealerValue, + False, + dealerBlackjack, + dealerBusted + ] + winningsCalc = self._calcWinnings(*_calcWinningsParams) + winningsTemp, netWinningsTemp, reasonTemp = winningsCalc + winnings += winningsTemp + netWinnings += netWinningsTemp + reason += reasonTemp + if hand["split"] >= 3: + _calcWinningsParams = [ + hand["fourth hand"], + dealerValue, + False, + dealerBlackjack, + dealerBusted + ] + winningsCalc = self._calcWinnings(*_calcWinningsParams) + winningsTemp, netWinningsTemp, reasonTemp = winningsCalc + winnings += winningsTemp + netWinnings += netWinningsTemp + reason += reasonTemp + + return winnings, netWinnings, reason + + def _getHandNumber(self, user: dict, handNumber: int): + """ + Get the hand with the given number. + + *Parameters* + ------------ + user: dict + The full hand dict of the user. + handNumber: int + The number of the hand to get. + + *Returns* + --------- + hand: dict + The hand. + handNumber: int + The same as handNumber, except if the user hasn't + split. If the user hasn't split, returns 0. + """ + hand = None + + if user["split"] == 0: + hand = user + handNumber = 0 + else: + if handNumber != 0: + if handNumber == 1: + hand = user + elif handNumber == 2: + hand = user["other hand"] + elif handNumber == 3: + hand = user["third hand"] + elif handNumber == 4: + hand = user["fourth hand"] + + return hand, handNumber + + def _isRoundDone(self, game: dict): + """ + Find out if the round is done. + + *Parameters* + ------------ + game: dict + The game to check. + + *Returns* + --------- + roundDone: bool + Whether the round is done. + """ + roundDone = True + + for person in game["user hands"].values(): + if (not person["hit"]) and (not person["standing"]): + roundDone = False + + if person["split"] > 0: + if not person["other hand"]["hit"]: + if not person["other hand"]["standing"]: + roundDone = False + + if person["split"] > 1: + if not person["third hand"]["hit"]: + if not person["third hand"]["standing"]: + roundDone = False + + if person["split"] > 2: + if not person["fourth hand"]["hit"]: + if not person["fourth hand"]["standing"]: + roundDone = False + + return roundDone + + async def _blackjackLoop(self, channel, gameRound: int, gameID: str): + """ + Run blackjack logic and continue if enough time passes. + + *Parameters* + ------------ + channel: guildChannel or DMChannel + The channel the game is happening in. + gameRound: int + The round to start. + gameID: str + The ID of the game. + """ + self.bot.log("Loop "+str(gameRound), str(channel.id)) + + oldImagePath = f"resources/games/oldImages/blackjack{channel.id}" + with open(oldImagePath, "r") as f: + oldImage = await channel.fetch_message(int(f.read())) + + continueData = (self._blackjackContinue(str(channel.id))) + new_message, allStanding, gamedone = continueData + if new_message != "": + self.bot.log(new_message, str(channel.id)) + await channel.send(new_message) + + if not gamedone: + await oldImage.delete() + tablesPath = "resources/games/blackjackTables/" + filePath = f"{tablesPath}blackjackTable{channel.id}.png" + oldImage = await channel.send(file=discord.File(filePath)) + with open(oldImagePath, "w") as f: + f.write(str(oldImage.id)) + + if allStanding: + await asyncio.sleep(5) + else: + await asyncio.sleep(120) + + blackjackGames = self.bot.database["blackjack games"] + game = blackjackGames.find_one({"_id": str(channel.id)}) + + if game is None: + rightRound = False + else: + realRound = game["round"] or -1 + realID = game["gameID"] or -1 + rightRound = gameRound == realRound and gameID == realID + + if rightRound: + if not gamedone: + logMessage = f"Loop {gameRound} calling self._blackjackLoop()" + self.bot.log(logMessage, str(channel.id)) + await self._blackjackLoop(channel, gameRound+1, gameID) + else: + new_message = self._blackjackFinish(str(channel.id)) + await channel.send(new_message) + else: + logMessage = f"Ending loop on round {gameRound}" + self.bot.log(logMessage, str(channel.id)) + + async def hit(self, ctx: discord_slash.context.SlashContext, + handNumber: int = 0): + """ + Hit on a hand. + + *Parameters* + ------------ + ctx: discord_slash.context.SlashContext + The context of the command. + handNumber: int = 0 + The number of the hand to hit. + """ await self.bot.defer(ctx) channel = str(ctx.channel_id) user = f"#{ctx.author.id}" roundDone = False - game = self.bot.database["blackjack games"].find_one({"_id":channel}) + blackjackGames = self.bot.database["blackjack games"] + game = blackjackGames.find_one({"_id": channel}) if user in game["user hands"]: userHands = game["user hands"][user] - hand, handNumber = self.getHandNumber(userHands, handNumber) + hand, handNumber = self._getHandNumber(userHands, handNumber) - if hand == None: + if hand is None: logMessage = "They didn't specify a hand" sendMessage = "You need to specify a hand" elif game["round"] <= 0: @@ -197,28 +652,27 @@ class Blackjack(): logMessage = "They're already standing" sendMessage = "You can't hit when you're standing" else: - hand["hand"].append(self.drawCard(channel)) + hand["hand"].append(self._drawCard(channel)) hand["hit"] = True - handValue = self.calcHandValue(hand["hand"]) + 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}}) + handPath = f"user hands.{user}.other hand" elif handNumber == 3: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".third hand":hand}}) + handPath = f"user hands.{user}.third hand" elif handNumber == 4: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".fourth hand":hand}}) + handPath = f"user hands.{user}.fourth hand" else: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user:hand}}) + handPath = f"user hands.{user}" - roundDone = self.isRoundDone(self.bot.database["blackjack games"].find_one({"_id":channel})) + gameUpdater = {"$set": {handPath: hand}} + blackjackGames.update_one({"_id": channel}, gameUpdater) + game = blackjackGames.find_one({"_id": channel}) + roundDone = self._isRoundDone(game) sendMessage = f"{ctx.author.display_name} hit" logMessage = "They succeeded" @@ -231,22 +685,34 @@ class Blackjack(): if roundDone: gameID = game["gameID"] - self.bot.log("Hit calling self.blackjackLoop()", channel) - await self.blackjackLoop(ctx.channel, game["round"]+1, gameID) + self.bot.log("Hit calling self._blackjackLoop()", channel) + await self._blackjackLoop(ctx.channel, game["round"]+1, gameID) - # When players try to double down - async def double(self, ctx, handNumber = 0): + async def double(self, ctx: discord_slash.context.SlashContext, + handNumber: int = 0): + """ + Double a hand. + + *Parameters* + ------------ + ctx: discord_slash.context.SlashContext + The context of the command. + handNumber: int = 0 + The number of the hand to double. + """ await self.bot.defer(ctx) channel = str(ctx.channel_id) user = f"#{ctx.author.id}" roundDone = False + blackjackGames = self.bot.database["blackjack games"] - game = self.bot.database["blackjack games"].find_one({"_id":channel}) + game = blackjackGames.find_one({"_id": channel}) if user in game["user hands"]: - hand, handNumber = self.getHandNumber(game["user hands"][user],handNumber) + handParams = [game["user hands"][user], handNumber] + hand, handNumber = self._getHandNumber(*handParams) - if hand == None: + if hand is None: logMessage = "They didn't specify a hand" sendMessage = "You need to specify a hand" elif game["round"] <= 0: @@ -261,42 +727,42 @@ class Blackjack(): elif len(hand["hand"]) != 2: logMessage = "They tried to double after round 1" sendMessage = "You can only double on the first round" + elif self.bot.money.checkBalance(user) < hand["bet"]: + logMessage = "They tried to double without being in the game" + sendMessage = "You can't double when you're not in the game" else: 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" + 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: + handPath = f"user hands.{user}.other hand" + elif handNumber == 3: + handPath = f"user hands.{user}.third hand" + elif handNumber == 4: + handPath = f"user hands.{user}.fourth hand" else: - self.bot.money.addMoney(user,-1 * bet) + handPath = f"user hands.{user}" - hand["hand"].append(self.drawCard(channel)) - hand["hit"] = True - hand["doubled"] = True - hand["bet"] += bet + gameUpdater = {"$set": {handPath: hand}} + blackjackGames.update_one({"_id": channel}, gameUpdater) - handValue = self.calcHandValue(hand["hand"]) + game = blackjackGames.find_one({"_id": channel}) + roundDone = self._isRoundDone(game) - - 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" + sendMessage = self.bot.longStrings["Blackjack double"] + userName = self.bot.databaseFuncs.getName(user) + sendMessage = sendMessage.format(bet, userName) + logMessage = "They succeeded" else: logMessage = "They tried to double without being in the game" sendMessage = "You can't double when you're not in the game" @@ -306,23 +772,34 @@ class Blackjack(): if roundDone: gameID = game["gameID"] - self.bot.log("Double calling self.blackjackLoop()", channel) - await self.blackjackLoop(ctx.channel, game["round"]+1, gameID) + self.bot.log("Double calling self._blackjackLoop()", channel) + await self._blackjackLoop(ctx.channel, game["round"]+1, gameID) - # When players try to stand - async def stand(self, ctx, handNumber = 0): + async def stand(self, ctx: discord_slash.context.SlashContext, + handNumber: int = 0): + """ + Stand on a hand. + + *Parameters* + ------------ + ctx: discord_slash.context.SlashContext + The context of the command. + handNumber: int = 0 + The number of the hand to stand on. + """ await self.bot.defer(ctx) channel = str(ctx.channel_id) user = f"#{ctx.author.id}" roundDone = False - game = self.bot.database["blackjack games"].find_one({"_id":channel}) + blackjackGames = self.bot.database["blackjack games"] + game = blackjackGames.find_one({"_id": channel}) - if user in game["user hands"]: + if game is not None and user in game["user hands"]: + handParams = [game["user hands"][user], handNumber] + hand, handNumber = self._getHandNumber(*handParams) - hand, handNumber = self.getHandNumber(game["user hands"][user],handNumber) - - if hand == None: + if hand is None: sendMessage = "You need to specify which hand" logMessage = "They didn't specify a hand" elif game["round"] <= 0: @@ -338,19 +815,18 @@ class Blackjack(): hand["standing"] = True if handNumber == 2: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".other hand":hand}}) + handPath = f"user hands.{user}.other hand" elif handNumber == 3: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".third hand":hand}}) + handPath = f"user hands.{user}.third hand" elif handNumber == 4: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".fourth hand":hand}}) + handPath = f"user hands.{user}.fourth hand" else: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user:hand}}) + handPath = f"user hands.{user}" - roundDone = self.isRoundDone(self.bot.database["blackjack games"].find_one({"_id":channel})) + gameUpdater = {"$set": {handPath: hand}} + blackjackGames.update_one({"_id": channel}, gameUpdater) + game = blackjackGames.find_one({"_id": channel}) + roundDone = self._isRoundDone(game) sendMessage = f"{ctx.author.display_name} is standing" logMessage = "They succeeded" @@ -364,18 +840,29 @@ class Blackjack(): if roundDone: gameID = game["gameID"] - self.bot.log("Stand calling self.blackjackLoop()", channel) - await self.blackjackLoop(ctx.channel, game["round"]+1, gameID) + self.bot.log("Stand calling self._blackjackLoop()", channel) + await self._blackjackLoop(ctx.channel, game["round"]+1, gameID) - # When players try to split - async def split(self, ctx, handNumber = 0): + async def split(self, ctx: discord_slash.context.SlashContext, + handNumber: int = 0): + """ + Split a hand. + + *Parameters* + ------------ + ctx: discord_slash.context.SlashContext + The context of the command. + handNumber: int = 0 + The number of the hand to split. + """ await self.bot.defer(ctx) channel = str(ctx.channel_id) user = f"#{ctx.author.id}" roundDone = False handNumberError = False - game = self.bot.database["blackjack games"].find_one({"_id":channel}) + blackjackGames = self.bot.database["blackjack games"] + game = blackjackGames.find_one({"_id": channel}) if game["user hands"][user]["split"] == 0: hand = game["user hands"][user] @@ -410,7 +897,7 @@ class Blackjack(): sendMessage = "You can only split 3 times" elif hand["hit"]: logMessage = "They've already hit" - sendMessage = "You've already hit" + sendMessage = "You've already hit or split this hand." elif hand["standing"]: logMessage = "They're already standing" sendMessage = "You're already standing" @@ -418,34 +905,37 @@ class Blackjack(): logMessage = "They tried to split after the first round" sendMessage = "You can only split on the first round" else: - firstCard = self.calcHandValue([hand["hand"][0]]) - secondCard = self.calcHandValue([hand["hand"][1]]) + 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" + sendMessage = self.bot.longStrings["Blackjack different cards"] 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) + 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} + "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)) + newHand["hand"].append(self._drawCard(channel)) + hand["hand"].append(self._drawCard(channel)) + hand["hit"] = True - handValue = self.calcHandValue(hand["hand"]) - otherHandValue = self.calcHandValue(newHand["hand"]) + handValue = self._calcHandValue(hand["hand"]) + otherHandValue = self._calcHandValue(newHand["hand"]) if handValue > 21: hand["busted"] = True elif handValue == 21: @@ -457,31 +947,34 @@ class Blackjack(): newHand["blackjack"] = True if handNumber == 2: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".other hand":hand}}) + handPath = f"user hands.{user}.other hand" elif handNumber == 3: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".third hand":hand}}) + handPath = f"user hands.{user}.third hand" else: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user:hand}}) + handPath = f"user hands.{user}" + + gameUpdater = {"$set": {handPath: hand}} + blackjackGames.update_one({"_id": channel}, gameUpdater) if otherHand == 3: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".third hand":newHand}}) + otherHandPath = f"user hands.{user}.third hand" elif otherHand == 4: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".fourth hand":newHand}}) + otherHandPath = f"user hands.{user}.fourth hand" else: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".other hand":newHand}}) + otherHandPath = f"user hands.{user}.other hand" - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$inc":{"user hands."+user+".split":1}}) + gameUpdater = {"$set": {otherHandPath: newHand}} + blackjackGames.update_one({"_id": channel}, gameUpdater) - roundDone = self.isRoundDone(self.bot.database["blackjack games"].find_one({"_id":channel})) + splitUpdater = {"$inc": {"user hands."+user+".split": 1}} + blackjackGames.update_one({"_id": channel}, splitUpdater) - 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." + game = blackjackGames.find_one({"_id": channel}) + roundDone = self._isRoundDone(game) + + sendMessage = self.bot.longStrings["Blackjack split"] + userName = self.bot.databaseFuncs.getName(user) + sendMessage = sendMessage.format(userName) logMessage = "They succeeded" await ctx.send(sendMessage) @@ -489,21 +982,31 @@ class Blackjack(): if roundDone: gameID = game["gameID"] - self.bot.log("Stand calling self.blackjackLoop()", channel) - await self.blackjackLoop(ctx.channel, game["round"]+1, 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 - async def playerDrawHand(self, ctx, bet : int): + async def enterGame(self, ctx: discord_slash.context.SlashContext, + bet: int): + """ + Enter the blackjack game. + + *Parameters* + ------------ + ctx: discord_slash.context.SlashContext + The context of the command. + bet: int + The bet to enter with. + """ await self.bot.defer(ctx) channel = str(ctx.channel_id) user = f"#{ctx.author.id}" collection = self.bot.database["blackjack games"] - game = collection.find_one({"_id":channel}) + game = collection.find_one({"_id": channel}) userName = self.bot.databaseFuncs.getName(user) self.bot.log(f"{userName} is trying to join the Blackjack game") - if game == None: + if game is None: sendMessage = "There is no game going on in this channel" logMessage = sendMessage elif user in game["user hands"]: @@ -522,78 +1025,97 @@ class Blackjack(): sendMessage = "You don't have enough GwendoBucks" logMessage = "They didn't have enough GwendoBucks" else: - self.bot.money.addMoney(user,-1 * bet) - playerHand = [self.drawCard(channel) for _ in range(2)] + self.bot.money.addMoney(user, -1 * bet) + playerHand = [self._drawCard(channel) for _ in range(2)] - handValue = self.calcHandValue(playerHand) + 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":{}} + 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) + 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) + self.bot.log(logMessage) + await ctx.send(sendMessage) - # Starts a game of blackjack - async def start(self, ctx): + async def start(self, ctx: discord_slash.context.SlashContext): + """ + Start a blackjack game. + + *Parameters* + ------------ + ctx: discord_slash.context.SlashContext + The context of the command. + """ await self.bot.defer(ctx) channel = str(ctx.channel_id) blackjackMinCards = 50 - blackjackDecks = 4 + self.bot.log("Starting blackjack game") await ctx.send("Starting a new game of blackjack") cardsLeft = 0 - cards = self.bot.database["blackjack cards"].find_one({"_id":channel}) - if cards != None: + cards = self.bot.database["blackjack cards"].find_one({"_id": channel}) + if cards is not None: cardsLeft = len(cards["cards"]) # Shuffles if not enough cards if cardsLeft < blackjackMinCards: - self.blackjackShuffle(blackjackDecks, channel) + self._blackjackShuffle(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}) + 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: + if game is None: - dealerHand = [self.drawCard(channel),self.drawCard(channel)] + dealerHand = [self._drawCard(channel), self._drawCard(channel)] gameID = datetime.datetime.now().strftime('%Y%m%d%H%M%S') - newGame = {"_id":channel,"dealer hand": dealerHand,"dealer busted":False,"dealer blackjack":False,"user hands": {},"all standing":False,"round":0,"gameID":gameID} + newGame = { + "_id": channel, "dealer hand": dealerHand, + "dealer busted": False, "dealer blackjack": False, + "user hands": {}, "all standing": False, "round": 0, + "gameID": gameID + } - if self.calcHandValue(dealerHand) == 21: + if self._calcHandValue(dealerHand) == 21: newGame["dealer blackjack"] = True self.bot.database["blackjack games"].insert_one(newGame) - copyfile("resources/games/blackjackTable.png","resources/games/blackjackTables/blackjackTable"+channel+".png") + tableImagesPath = "resources/games/blackjackTables/" + emptyTableImagePath = f"resources/games/blackjackTable.png" + newTableImagePath = f"{tableImagesPath}blackjackTable{channel}.png" + copyfile(emptyTableImagePath, newTableImagePath) gameStarted = True if gameStarted: - sendMessage = "Blackjack game started. Use \"/blackjack bet [amount]\" to enter the game within the next 30 seconds." + sendMessage = self.bot.longStrings["Blackjack started"] await ctx.channel.send(sendMessage) - filePath = f"resources/games/blackjackTables/blackjackTable{channel}.png" - oldImage = await ctx.channel.send(file = discord.File(filePath)) + tableImagesPath = "resources/games/blackjackTables/" + filePath = f"{tableImagesPath}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)) @@ -602,365 +1124,346 @@ class Blackjack(): gamedone = False - game = self.bot.database["blackjack games"].find_one({"_id":str(channel)}) + blackjackGames = self.bot.database["blackjack games"] + game = blackjackGames.find_one({"_id": channel}) if len(game["user hands"]) == 0: gamedone = True - await ctx.channel.send("No one entered the game. Ending the game.") + sendMessage = "No one entered the game. Ending the game." + await ctx.channel.send(sendMessage) + gameID = game["gameID"] # Loop of game rounds - if gamedone == False: - self.bot.log("start() calling blackjackLoop()", channel) - await self.blackjackLoop(ctx.channel,1,gameID) + if not gamedone: + self.bot.log("start() calling _blackjackLoop()", channel) + await self._blackjackLoop(ctx.channel, 1, gameID) else: - new_message = self.blackjackFinish(channel) + new_message = self._blackjackFinish(channel) await ctx.channel.send(new_message) else: - await ctx.channel.send("There's already a blackjack game going on. Try again in a few minutes.") + sendMessage = self.bot.longStrings["Blackjack going on"] + await ctx.channel.send(sendMessage) self.bot.log("There was already a game going on") - # Ends the game and calculates winnings - def blackjackFinish(self,channel): - finalWinnings = "*Final Winnings:*\n" + async def hilo(self, ctx: discord_slash.context.SlashContext): + """ + Get the hilo of the blackjack game. - game = self.bot.database["blackjack games"].find_one({"_id":channel}) - - dealerValue = self.calcHandValue(game["dealer hand"]) - dealerBlackjack = game["dealer blackjack"] - dealerBusted = game["dealer busted"] - - try: - for user in game["user hands"]: - - winnings, netWinnings, reason = self.calcWinnings(game["user hands"][user],dealerValue,True,dealerBlackjack,dealerBusted) - - if winnings < 0: - if winnings == -1: - finalWinnings += self.bot.databaseFuncs.getName(user)+" lost "+str(-1 * winnings)+" GwendoBuck "+reason+"\n" - else: - finalWinnings += self.bot.databaseFuncs.getName(user)+" lost "+str(-1 * winnings)+" GwendoBucks "+reason+"\n" - else: - if winnings == 1: - finalWinnings += self.bot.databaseFuncs.getName(user)+" won "+str(winnings)+" GwendoBuck "+reason+"\n" - else: - finalWinnings += self.bot.databaseFuncs.getName(user)+" won "+str(winnings)+" GwendoBucks "+reason+"\n" - - self.bot.money.addMoney(user,netWinnings) - - except: - self.bot.log("Error calculating winnings (error code 1311)") - - self.bot.database["blackjack games"].delete_one({"_id":channel}) - - return finalWinnings - - def calcWinnings(self,hand, dealerValue, topLevel, dealerBlackjack, dealerBusted): - self.bot.log("Calculating winnings") - reason = "" - bet = hand["bet"] - winnings = -1 * bet - netWinnings = 0 - handValue = self.calcHandValue(hand["hand"]) - - if hand["blackjack"] and dealerBlackjack == False: - reason += "(blackjack)" - winnings += math.floor(2.5 * bet) - netWinnings += math.floor(2.5 * bet) - elif dealerBlackjack: - reason += "(dealer blackjack)" - elif hand["busted"]: - reason += "(busted)" - else: - if dealerBusted: - reason = "(dealer busted)" - winnings += 2 * bet - netWinnings += 2 * bet - elif handValue > dealerValue: - winnings += 2 * bet - netWinnings += 2 * bet - reason = "(highest value)" - elif handValue == dealerValue: - reason = "(pushed)" - winnings += bet - netWinnings += bet - else: - reason = "(highest value)" - - if topLevel: - if hand["split"] >= 1: - winningsTemp, netWinningsTemp, reasonTemp = self.calcWinnings(hand["other hand"],dealerValue,False,dealerBlackjack,dealerBusted) - winnings += winningsTemp - netWinnings += netWinningsTemp - reason += reasonTemp - if hand["split"] >= 2: - winningsTemp, netWinningsTemp, reasonTemp = self.calcWinnings(hand["third hand"],dealerValue,False,dealerBlackjack,dealerBusted) - winnings += winningsTemp - netWinnings += netWinningsTemp - reason += reasonTemp - if hand["split"] >= 3: - winningsTemp, netWinningsTemp, reasonTemp = self.calcWinnings(hand["fourth hand"],dealerValue,False,dealerBlackjack,dealerBusted) - winnings += winningsTemp - netWinnings += netWinningsTemp - reason += reasonTemp - - return winnings, netWinnings, reason - - def getHandNumber(self, user,handNumber): - try: - hand = None - - if user["split"] == 0: - hand = user - handNumber = 0 - else: - if handNumber != 0: - if handNumber == 1: - hand = user - elif handNumber == 2: - hand = user["other hand"] - elif handNumber == 3: - hand = user["third hand"] - elif handNumber == 4: - hand = user["fourth hand"] - - return hand, handNumber - except: - self.bot.log("Problem with getHandNumber() (error code 1322)") - - def isRoundDone(self,game): - roundDone = True - - for person in game["user hands"].values(): - if person["hit"] == False and person["standing"] == False: - roundDone = False - - if person["split"] > 0: - if person["other hand"]["hit"] == False and person["other hand"]["standing"] == False: - roundDone = False - - if person["split"] > 1: - if person["third hand"]["hit"] == False and person["third hand"]["standing"] == False: - roundDone = False - - if person["split"] > 2: - if person["fourth hand"]["hit"] == False and person["fourth hand"]["standing"] == False: - roundDone = False - - return roundDone - - # Loop of blackjack game rounds - async def blackjackLoop(self,channel,gameRound,gameID): - self.bot.log("Loop "+str(gameRound),str(channel.id)) - - with open("resources/games/oldImages/blackjack"+str(channel.id), "r") as f: - oldImage = await channel.fetch_message(int(f.read())) - - new_message, allStanding, gamedone = self.blackjackContinue(str(channel.id)) - if new_message != "": - self.bot.log(new_message,str(channel.id)) - await channel.send(new_message) - if gamedone == False: - await oldImage.delete() - oldImage = await channel.send(file = discord.File("resources/games/blackjackTables/blackjackTable"+str(channel.id)+".png")) - with open("resources/games/oldImages/blackjack"+str(channel.id), "w") as f: - f.write(str(oldImage.id)) - - try: - if allStanding: - await asyncio.sleep(5) - else: - await asyncio.sleep(120) - except: - self.bot.log("Loop "+str(gameRound)+" interrupted (error code 1321)") - - game = self.bot.database["blackjack games"].find_one({"_id":str(channel.id)}) - - if game != None: - realRound = game["round"] - realGameID = game["gameID"] - - if gameRound == realRound and realGameID == gameID: - if gamedone == False: - self.bot.log("Loop "+str(gameRound)+" calling self.blackjackLoop()",str(channel.id)) - await self.blackjackLoop(channel,gameRound+1,gameID) - else: - try: - new_message = self.blackjackFinish(str(channel.id)) - except: - self.bot.log("Something fucked up (error code 1310)") - await channel.send(new_message) - else: - self.bot.log("Ending loop on round "+str(gameRound),str(channel.id)) - else: - self.bot.log("Ending loop on round "+str(gameRound),str(channel.id)) - - # Returning current hi-lo value - async def hilo(self, ctx): + *Parameters* + ------------ + ctx: discord_slash.context.SlashContext + The context of the command. + """ channel = ctx.channel_id - data = self.bot.database["hilo"].find_one({"_id":str(channel)}) - if data != None: + data = self.bot.database["hilo"].find_one({"_id": str(channel)}) + if data is not None: hilo = str(data["hilo"]) else: hilo = "0" await ctx.send(f"Hi-lo value: {hilo}", hidden=True) - # Shuffles the blackjack deck - async def shuffle(self, ctx): - blackjackDecks = 4 + async def shuffle(self, ctx: discord_slash.context.SlashContext): + """ + Shuffle the cards used for blackjack. + + *Parameters* + ------------ + ctx: discord_slash.context.SlashContext + The context of the command. + """ channel = ctx.channel_id - self.blackjackShuffle(blackjackDecks,str(channel)) - self.bot.log("Shuffling the blackjack deck...",str(channel)) + self._blackjackShuffle(str(channel)) + self.bot.log("Shuffling the blackjack deck...", str(channel)) await ctx.send("Shuffling the deck...") + async def cards(self, ctx: discord_slash.context.SlashContext): + """ + Get how many cards are left for blackjack. - # Tells you the amount of cards left - async def cards(self, ctx): + *Parameters* + ------------ + ctx: discord_slash.context.SlashContext + The context of the command. + """ channel = ctx.channel_id cardsLeft = 0 - cards = self.bot.database["blackjack cards"].find_one({"_id":str(channel)}) - if cards != None: + blackjackGames = self.bot.database["blackjack cards"] + cards = blackjackGames.find_one({"_id": str(channel)}) + if cards is not None: cardsLeft = len(cards["cards"]) - decksLeft = round(cardsLeft/52,1) - await ctx.send(f"Cards left:\n{cardsLeft} cards, {decksLeft} decks", hidden=True) + decksLeft = round(cardsLeft/52, 1) + sendMessage = f"Cards left:\n{cardsLeft} cards, {decksLeft} decks" + await ctx.send(sendMessage, hidden=True) + class DrawBlackjack(): - def __init__(self,bot): + """ + Draws the blackjack image. + + *Methods* + --------- + drawImage(channel: str) + + *Attributes* + ------------ + bot: Gwendolyn + The instance of the bot. + BORDER: int + The size of the border in pixels. + PLACEMENT: list + The order to place the user hands in. + """ + + def __init__(self, bot): + """Initialize the class.""" self.bot = bot self.BORDER = 100 - self.PLACEMENT = [0,0] - self.ROTATION = 0 + self.PLACEMENT = [2, 1, 3, 0, 4] - def drawImage(self,channel): - self.bot.log("Drawing blackjack table",channel) - game = self.bot.database["blackjack games"].find_one({"_id":channel}) + def drawImage(self, channel: str): + """ + Draw the table image. - fnt = ImageFont.truetype('resources/fonts/futura-bold.ttf', 50) - fntSmol = ImageFont.truetype('resources/fonts/futura-bold.ttf', 40) - self.BORDERSmol = int(self.BORDER/3.5) + *Parameters* + ------------ + channel: str + The id of the channel the game is in. + """ + self.bot.log("Drawing blackjack table", channel) + game = self.bot.database["blackjack games"].find_one({"_id": channel}) + + font = ImageFont.truetype('resources/fonts/futura-bold.ttf', 50) + smallFont = ImageFont.truetype('resources/fonts/futura-bold.ttf', 40) + self.SMALLBORDER = int(self.BORDER/3.5) table = Image.open("resources/games/blackjackTable.png") - self.PLACEMENT = [2,1,3,0,4] textImage = ImageDraw.Draw(table) hands = game["user hands"] dealerBusted = game["dealer busted"] dealerBlackjack = game["dealer blackjack"] - try: - if game["all standing"] == False: - dealerHand = self.drawHand(game["dealer hand"],True,False,False) - else: - dealerHand = self.drawHand(game["dealer hand"],False,dealerBusted,dealerBlackjack) - except: - self.bot.log("Error drawing dealer hand (error code 1341a)") + if not game["all standing"]: + handParams = [ + game["dealer hand"], + True, + False, + False + ] + else: + handParams = [ + game["dealer hand"], + False, + dealerBusted, + dealerBlackjack + ] - table.paste(dealerHand,(800-self.BORDERSmol,20-self.BORDERSmol),dealerHand) + dealerHand = self._drawHand(*handParams) + + pastePosition = (800-self.SMALLBORDER, 20-self.SMALLBORDER) + table.paste(dealerHand, pastePosition, dealerHand) for x in range(len(hands)): key, value = list(hands.items())[x] key = self.bot.databaseFuncs.getName(key) - #self.bot.log("Drawing "+key+"'s hand") - userHand = self.drawHand(value["hand"],False,value["busted"],value["blackjack"]) - try: - if value["split"] == 3: - table.paste(userHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),280-self.BORDERSmol),userHand) - userOtherHand = self.drawHand(value["other hand"]["hand"],False,value["other hand"]["busted"],value["other hand"]["blackjack"]) - table.paste(userOtherHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),420-self.BORDERSmol),userOtherHand) - userThirdHand = self.drawHand(value["third hand"]["hand"],False,value["third hand"]["busted"],value["third hand"]["blackjack"]) - table.paste(userThirdHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),560-self.BORDERSmol),userThirdHand) - userFourthHand = self.drawHand(value["fourth hand"]["hand"],False,value["fourth hand"]["busted"],value["fourth hand"]["blackjack"]) - table.paste(userFourthHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),700-self.BORDERSmol),userFourthHand) - elif value["split"] == 2: - table.paste(userHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),420-self.BORDERSmol),userHand) - userOtherHand = self.drawHand(value["other hand"]["hand"],False,value["other hand"]["busted"],value["other hand"]["blackjack"]) - table.paste(userOtherHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),560-self.BORDERSmol),userOtherHand) - userThirdHand = self.drawHand(value["third hand"]["hand"],False,value["third hand"]["busted"],value["third hand"]["blackjack"]) - table.paste(userThirdHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),700-self.BORDERSmol),userThirdHand) - elif value["split"] == 1: - table.paste(userHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),560-self.BORDERSmol),userHand) - userOtherHand = self.drawHand(value["other hand"]["hand"],False,value["other hand"]["busted"],value["other hand"]["blackjack"]) - table.paste(userOtherHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),700-self.BORDERSmol),userOtherHand) - else: - table.paste(userHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),680-self.BORDERSmol),userHand) - except: - self.bot.log("Error drawing player hands (error code 1341b)") + handParams = [ + value["hand"], + False, + value["busted"], + value["blackjack"] + ] + userHand = self._drawHand(*handParams) + positionX = 32-self.SMALLBORDER+(384*self.PLACEMENT[x]) - textWidth = fnt.getsize(key)[0] - if textWidth < 360: - textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2)-3,1010-3),key,fill=(0,0,0), font=fnt) - textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2)+3,1010-3),key,fill=(0,0,0), font=fnt) - textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2)-3,1010+3),key,fill=(0,0,0), font=fnt) - textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2)+3,1010+3),key,fill=(0,0,0), font=fnt) - textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2),1005),key,fill=(255,255,255), font=fnt) + if value["split"] >= 1: + handParamsTwo = [ + value["other hand"]["hand"], + False, + value["other hand"]["busted"], + value["other hand"]["blackjack"] + ] + userOtherHand = self._drawHand(*handParamsTwo) + + if value["split"] >= 2: + handParamsThree = [ + value["third hand"]["hand"], + False, + value["third hand"]["busted"], + value["third hand"]["blackjack"] + ] + userThirdHand = self._drawHand(*handParamsThree) + + if value["split"] >= 3: + handParamsFour = [ + value["fourth hand"]["hand"], + False, + value["fourth hand"]["busted"], + value["fourth hand"]["blackjack"] + ] + userFourthHand = self._drawHand(*handParamsFour) + + if value["split"] == 3: + positionOne = (positionX, 280-self.SMALLBORDER) + positionTwo = (positionX, 420-self.SMALLBORDER) + positionThree = (positionX, 560-self.SMALLBORDER) + positionFour = (positionX, 700-self.SMALLBORDER) + + table.paste(userHand, positionOne, userHand) + table.paste(userOtherHand, positionTwo, userOtherHand) + table.paste(userThirdHand, positionThree, userThirdHand) + table.paste(userFourthHand, positionFour, userFourthHand) + elif value["split"] == 2: + positionOne = (positionX, 420-self.SMALLBORDER) + positionTwo = (positionX, 560-self.SMALLBORDER) + positionThree = (positionX, 700-self.SMALLBORDER) + + table.paste(userHand, positionOne, userHand) + table.paste(userOtherHand, positionTwo, userOtherHand) + table.paste(userThirdHand, positionThree, userThirdHand) + elif value["split"] == 1: + positionOne = (positionX, 560-self.SMALLBORDER) + positionTwo = (positionX, 700-self.SMALLBORDER) + + table.paste(userHand, positionOne, userHand) + table.paste(userOtherHand, positionTwo, userOtherHand) else: - textWidth = fntSmol.getsize(key)[0] - textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2)-2,1020-2),key,fill=(0,0,0), font=fntSmol) - textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2)+2,1020-2),key,fill=(0,0,0), font=fntSmol) - textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2)-2,1020+2),key,fill=(0,0,0), font=fntSmol) - textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2)+2,1020+2),key,fill=(0,0,0), font=fntSmol) - textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2),1015),key,fill=(255,255,255), font=fntSmol) + positionOne = (positionX, 680-self.SMALLBORDER) + table.paste(userHand, positionOne, userHand) + + textWidth = font.getsize(key)[0] + textX = 32+(384*self.PLACEMENT[x])+117-int(textWidth/2) + black = (0, 0, 0) + white = (255, 255, 255) + + if textWidth < 360: + # Black shadow behind and slightly below white text + textImage.text((textX-3, 1010-3), key, fill=black, font=font) + textImage.text((textX+3, 1010-3), key, fill=black, font=font) + textImage.text((textX-3, 1010+3), key, fill=black, font=font) + textImage.text((textX+3, 1010+3), key, fill=black, font=font) + textImage.text((textX, 1005), key, fill=white, font=font) + else: + textWidth = smallFont.getsize(key)[0] + shadows = [ + (textX-2, 1020-2), + (textX+2, 1020-2), + (textX-2, 1020+2), + (textX+2, 1020+2) + ] + textImage.text(shadows[0], key, fill=black, font=smallFont) + textImage.text(shadows[1], key, fill=black, font=smallFont) + textImage.text(shadows[2], key, fill=black, font=smallFont) + textImage.text(shadows[3], key, fill=black, font=smallFont) + textImage.text((textX, 1015), key, fill=white, font=smallFont) self.bot.log("Saving table image") - table.save("resources/games/blackjackTables/blackjackTable"+channel+".png") + tableImagesPath = "resources/games/blackjackTables/" + tableImagePath = f"{tableImagesPath}blackjackTable{channel}.png" + table.save(tableImagePath) return - def drawHand(self, hand, dealer, busted, blackjack): - self.bot.log("Drawing hand "+str(hand)+", "+str(busted)+", "+str(blackjack)) - fnt = ImageFont.truetype('resources/fonts/futura-bold.ttf', 200) - fnt2 = ImageFont.truetype('resources/fonts/futura-bold.ttf', 120) + def _drawHand(self, hand: dict, dealer: bool, busted: bool, + blackjack: bool): + """ + Draw a hand. + + *Parameters* + ------------ + hand: dict + The hand to draw. + dealer: bool + If the hand belongs to the dealer who hasn't shown + their second card. + busted: bool + If the hand is busted. + blackjack: bool + If the hand has a blackjack. + + *Returns* + --------- + background: Image + The image of the hand. + """ + self.bot.log("Drawing hand {hand}, {busted}, {blackjack}") + font = ImageFont.truetype('resources/fonts/futura-bold.ttf', 200) + font2 = ImageFont.truetype('resources/fonts/futura-bold.ttf', 120) length = len(hand) - background = Image.new("RGBA", ((self.BORDER*2)+691+(125*(length-1)),(self.BORDER*2)+1065),(0,0,0,0)) + backgroundWidth = (self.BORDER*2)+691+(125*(length-1)) + backgroundSize = (backgroundWidth, (self.BORDER*2)+1065) + background = Image.new("RGBA", backgroundSize, (0, 0, 0, 0)) textImage = ImageDraw.Draw(background) + cardY = self.BORDER+self.PLACEMENT[1] if dealer: img = Image.open("resources/games/cards/"+hand[0].upper()+".png") - #self.ROTATION = (random.randint(-20,20)/10.0) - img = img.rotate(self.ROTATION,expand = 1) - #self.PLACEMENT = [random.randint(-20,20),random.randint(-20,20)] - background.paste(img,(self.BORDER+self.PLACEMENT[0],self.BORDER+self.PLACEMENT[1]),img) + cardPosition = (self.BORDER+self.PLACEMENT[0], cardY) + background.paste(img, cardPosition, img) img = Image.open("resources/games/cards/red_back.png") - #self.ROTATION = (random.randint(-20,20)/10.0) - img = img.rotate(self.ROTATION,expand = 1) - #self.PLACEMENT = [random.randint(-20,20),random.randint(-20,20)] - background.paste(img,(125+self.BORDER+self.PLACEMENT[0],self.BORDER+self.PLACEMENT[1]),img) + cardPosition = (125+self.BORDER+self.PLACEMENT[0], cardY) + background.paste(img, cardPosition, img) else: for x in range(length): - img = Image.open("resources/games/cards/"+hand[x].upper()+".png") - #self.ROTATION = (random.randint(-20,20)/10.0) - img = img.rotate(self.ROTATION,expand = 1) - #self.PLACEMENT = [random.randint(-20,20),random.randint(-20,20)] - background.paste(img,(self.BORDER+(x*125)+self.PLACEMENT[0],self.BORDER+self.PLACEMENT[1]),img) + cardPath = f"resources/games/cards/{hand[x].upper()}.png" + img = Image.open(cardPath) + cardPosition = (self.BORDER+(x*125)+self.PLACEMENT[0], cardY) + background.paste(img, cardPosition, img) w, h = background.size textHeight = 290+self.BORDER + white = (255, 255, 255) + black = (0, 0, 0) + red = (255, 50, 50) + gold = (155, 123, 0) - #self.bot.log("Drawing busted/blackjack") if busted: - textWidth = fnt.getsize("BUSTED")[0] - textImage.text((int(w/2)-int(textWidth/2)-10,textHeight+20-10),"BUSTED",fill=(0,0,0), font=fnt) - textImage.text((int(w/2)-int(textWidth/2)+10,textHeight+20-10),"BUSTED",fill=(0,0,0), font=fnt) - textImage.text((int(w/2)-int(textWidth/2)-10,textHeight+20+10),"BUSTED",fill=(0,0,0), font=fnt) - textImage.text((int(w/2)-int(textWidth/2)+10,textHeight+20+10),"BUSTED",fill=(0,0,0), font=fnt) - textImage.text((int(w/2)-int(textWidth/2)-5,textHeight-5),"BUSTED",fill=(255,255,255), font=fnt) - textImage.text((int(w/2)-int(textWidth/2)+5,textHeight-5),"BUSTED",fill=(255,255,255), font=fnt) - textImage.text((int(w/2)-int(textWidth/2)-5,textHeight+5),"BUSTED",fill=(255,255,225), font=fnt) - textImage.text((int(w/2)-int(textWidth/2)+5,textHeight+5),"BUSTED",fill=(255,255,255), font=fnt) - textImage.text((int(w/2)-int(textWidth/2),textHeight),"BUSTED",fill=(255,50,50), font=fnt) + textWidth = font.getsize("BUSTED")[0] + textX = int(w/2)-int(textWidth/2) + positions = [ + (textX-10, textHeight+20-10), + (textX+10, textHeight+20-10), + (textX-10, textHeight+20+10), + (textX+10, textHeight+20+10), + (textX-5, textHeight-5), + (textX+5, textHeight-5), + (textX-5, textHeight+5), + (textX+5, textHeight+5), + (textX, textHeight) + ] + textImage.text(positions[0], "BUSTED", fill=black, font=font) + textImage.text(positions[1], "BUSTED", fill=black, font=font) + textImage.text(positions[2], "BUSTED", fill=black, font=font) + textImage.text(positions[3], "BUSTED", fill=black, font=font) + textImage.text(positions[4], "BUSTED", fill=white, font=font) + textImage.text(positions[5], "BUSTED", fill=white, font=font) + textImage.text(positions[6], "BUSTED", fill=white, font=font) + textImage.text(positions[7], "BUSTED", fill=white, font=font) + textImage.text(positions[8], "BUSTED", fill=red, font=font) elif blackjack: - textWidth = fnt2.getsize("BLACKJACK")[0] - textImage.text((int(w/2)-int(textWidth/2)-6,textHeight+20-6),"BLACKJACK",fill=(0,0,0), font=fnt2) - textImage.text((int(w/2)-int(textWidth/2)+6,textHeight+20-6),"BLACKJACK",fill=(0,0,0), font=fnt2) - textImage.text((int(w/2)-int(textWidth/2)-6,textHeight+20+6),"BLACKJACK",fill=(0,0,0), font=fnt2) - textImage.text((int(w/2)-int(textWidth/2)+6,textHeight+20+6),"BLACKJACK",fill=(0,0,0), font=fnt2) - textImage.text((int(w/2)-int(textWidth/2)-3,textHeight-3),"BLACKJACK",fill=(255,255,255), font=fnt2) - textImage.text((int(w/2)-int(textWidth/2)+3,textHeight-3),"BLACKJACK",fill=(255,255,255), font=fnt2) - textImage.text((int(w/2)-int(textWidth/2)-3,textHeight+3),"BLACKJACK",fill=(255,255,255), font=fnt2) - textImage.text((int(w/2)-int(textWidth/2)+3,textHeight+3),"BLACKJACK",fill=(255,255,255), font=fnt2) - textImage.text((int(w/2)-int(textWidth/2),textHeight),"BLACKJACK",fill=(155,123,0), font=fnt2) - - #self.bot.log("Returning resized image") - return background.resize((int(w/3.5),int(h/3.5)),resample=Image.BILINEAR) + textWidth = font2.getsize("BLACKJACK")[0] + textX = int(w/2)-int(textWidth/2) + positions = [ + (textX-6, textHeight+20-6), + (textX+6, textHeight+20-6), + (textX-6, textHeight+20+6), + (textX+6, textHeight+20+6), + (textX-3, textHeight-3), + (textX+3, textHeight-3), + (textX-3, textHeight+3), + (textX+3, textHeight+3), + (textX, textHeight) + ] + textImage.text(positions[0], "BLACKJACK", fill=black, font=font2) + textImage.text(positions[1], "BLACKJACK", fill=black, font=font2) + textImage.text(positions[2], "BLACKJACK", fill=black, font=font2) + textImage.text(positions[3], "BLACKJACK", fill=black, font=font2) + textImage.text(positions[4], "BLACKJACK", fill=white, font=font2) + textImage.text(positions[5], "BLACKJACK", fill=white, font=font2) + textImage.text(positions[6], "BLACKJACK", fill=white, font=font2) + textImage.text(positions[7], "BLACKJACK", fill=white, font=font2) + textImage.text(positions[8], "BLACKJACK", fill=gold, font=font2) + resizedSize = (int(w/3.5), int(h/3.5)) + return background.resize(resizedSize, resample=Image.BILINEAR) diff --git a/requirements.txt b/requirements.txt index e66d93c..ce8b57f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,10 +8,10 @@ certifi==2020.12.5 chardet==4.0.0 colorama==0.4.4 d20==1.1.0 -discord-py-slash-command==1.1.1 +discord-py-slash-command==1.1.2 discord.py==1.7.1 dnspython==2.1.0 -docutils==0.16 +docutils==0.17 fandom-py==0.2.1 finnhub-python==2.4.0 gitdb==4.0.7 @@ -21,7 +21,7 @@ idna==2.10 IMDbPY==2020.9.25 isort==5.8.0 jaraco.context==4.0.0 -lark-parser==0.9.0 +lark-parser==0.11.2 lazy-object-proxy==1.6.0 lxml==4.6.3 mccabe==0.6.1 diff --git a/resources/longStrings.json b/resources/longStrings.json index a7b418f..1458e3d 100644 --- a/resources/longStrings.json +++ b/resources/longStrings.json @@ -1,4 +1,12 @@ { "missing parameters" : "Missing command parameters. Try using `!help [command]` to find out how to use the command.", - "Can't log in": "Could not log in. Remember to write your bot token in the credentials.txt file" + "Can't log in": "Could not log in. Remember to write your bot token in the credentials.txt file", + "Blackjack all players standing": "All players are standing. The dealer now shows his cards and draws.", + "Blackjack first round": ". You can also double down with \"/blackjack double\" or split with \"/blackjack split\"", + "Blackjack commands": "You have 2 minutes to either hit or stand with \"/blackjack hit\" or \"/blackjack stand\"{}. It's assumed you're standing if you don't make a choice.", + "Blackjack double": "Adding another {} GwendoBucks to {}'s bet and drawing another card.", + "Blackjack different cards": "You can only split if your cards have the same value", + "Blackjack split": "Splitting {}'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.", + "Blackjack started": "Blackjack game started. Use \"/blackjack bet [amount]\" to enter the game within the next 30 seconds.", + "Blackjack going on": "There's already a blackjack game going on. Try again in a few minutes." } \ No newline at end of file diff --git a/utils/utilFunctions.py b/utils/utilFunctions.py index 6fea627..a472a21 100644 --- a/utils/utilFunctions.py +++ b/utils/utilFunctions.py @@ -13,12 +13,12 @@ Contains utility functions used by parts of the bot. newString: str) -> str emojiToCommand(emoji: str) -> str """ -import json -import logging -import os -import sys -import imdb -from .helperClasses import Options +import json # Used by longString(), getParams() and makeFiles() +import logging # Used for logging +import os # Used by makeFiles() to check if files exist +import sys # Used to specify printing for logging +import imdb # Used to disable logging for the module +from .helperClasses import Options # Used by getParams() # All of this is logging configuration From e07326d8ce26ea06f77280021995c539c3d74feb Mon Sep 17 00:00:00 2001 From: NikolajDanger Date: Sat, 17 Apr 2021 21:30:46 +0200 Subject: [PATCH 13/23] :dollar: Money --- funcs/games/money.py | 151 +++++++++++++++++++++++++++++++++---------- 1 file changed, 116 insertions(+), 35 deletions(-) diff --git a/funcs/games/money.py b/funcs/games/money.py index dea2812..c626a91 100644 --- a/funcs/games/money.py +++ b/funcs/games/money.py @@ -1,70 +1,151 @@ +""" +Contains the code that deals with money. + +*Classes* +--------- + Money + Deals with money. +""" +import discord_slash # Used for typehints +import discord # Used for typehints + + class Money(): + """ + Deals with money. + + *Methods* + --------- + checkBalance(user: str) + sendBalance(ctx: discord_slash.context.SlashContext) + addMoney(user: str, amount: int) + giveMoney(ctx: discord_slash.context.SlashContext, user: discord.User, + amount: int) + + *Attributes* + ------------ + bot: Gwendolyn + The instance of Gwendolyn + database: pymongo.Client + The mongo database + """ def __init__(self, bot): + """Initialize the class.""" self.bot = bot self.database = bot.database - # Returns the account balance for a user - def checkBalance(self, user): + def checkBalance(self, user: str): + """ + Get the account balance of a user. + + *Parameters* + ------------ + user: str + The user to get the balance of. + + *Returns* + --------- + balance: int + The balance of the user's account. + """ self.bot.log("checking "+user+"'s account balance") - userData = self.database["users"].find_one({"_id":user}) + userData = self.database["users"].find_one({"_id": user}) - if userData != None: + if userData is not None: return userData["money"] - else: return 0 + else: + return 0 - async def sendBalance(self, ctx): + async def sendBalance(self, ctx: discord_slash.context.SlashContext): + """ + Get your own account balance. + + *Parameters* + ------------ + ctx: discord_slash.context.SlashContext + The context of the command. + """ await self.bot.defer(ctx) response = self.checkBalance("#"+str(ctx.author.id)) + userName = ctx.author.display_name if response == 1: - new_message = ctx.author.display_name + " has " + str(response) + " GwendoBuck" + new_message = f"{userName} has {response} GwendoBuck" else: - new_message = ctx.author.display_name + " has " + str(response) + " GwendoBucks" + new_message = f"{userName} has {response} GwendoBucks" await ctx.send(new_message) # Adds money to the account of a user - def addMoney(self,user,amount): + def addMoney(self, user: str, amount: int): + """ + Add money to a user account. + + *Parameters* + ------------ + user: str + The id of the user to give money. + amount: int + The amount to add to the user's account. + """ self.bot.log("adding "+str(amount)+" to "+user+"'s account") - userData = self.database["users"].find_one({"_id":user}) + userData = self.database["users"].find_one({"_id": user}) - if userData != None: - self.database["users"].update_one({"_id":user},{"$inc":{"money":amount}}) + if userData is not None: + updater = {"$inc": {"money": amount}} + self.database["users"].update_one({"_id": user}, updater) else: - self.database["users"].insert_one({"_id":user,"user name":self.bot.databaseFuncs.getName(user),"money":amount}) + newUser = { + "_id": user, + "user name": self.bot.databaseFuncs.getName(user), + "money": amount + } + self.database["users"].insert_one(newUser) # Transfers money from one user to another - async def giveMoney(self, ctx, user, amount): + async def giveMoney(self, ctx: discord_slash.context.SlashContext, + user: discord.User, amount: int): + """ + Give someone else money from your account. + + *Parameters* + ------------ + ctx: discord_slash.context.SlashContext + The context of the command. + user: discord.User + The user to give money. + amount: int + The amount to transfer. + """ await self.bot.defer(ctx) username = user.display_name - if self.bot.databaseFuncs.getID(username) == None: + if self.bot.databaseFuncs.getID(username) is 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} + userID = f"#{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}"}) + userid = f"#{ctx.author.id}" + userData = self.database["users"].find_one({"_id": userid}) targetUser = self.bot.databaseFuncs.getID(username) - if amount > 0: - if targetUser != None: - if userData != None: - if userData["money"] >= amount: - self.addMoney(f"#{ctx.author.id}",-1 * amount) - self.addMoney(targetUser,amount) - await ctx.send(f"Transferred {amount} GwendoBucks to {username}") - else: - 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") - await ctx.send("You don't have that many GwendoBuck") - else: - self.bot.log("They weren't in the system") - await ctx.send("The target doesn't exist") - else: + if amount <= 0: self.bot.log("They tried to steal") await ctx.send("Yeah, no. You can't do that") + elif targetUser is None: + self.bot.log("They weren't in the system") + await ctx.send("The target doesn't exist") + elif userData is None or userData["money"] < amount: + self.bot.log("They didn't have enough GwendoBucks") + await ctx.send("You don't have that many GwendoBuck") + else: + self.addMoney(f"#{ctx.author.id}", -1 * amount) + self.addMoney(targetUser, amount) + await ctx.send(f"Transferred {amount} GwendoBucks to {username}") From 9948f039139d961bcb5a732e28e7fe634450a673 Mon Sep 17 00:00:00 2001 From: NikolajDanger Date: Sat, 17 Apr 2021 21:44:00 +0200 Subject: [PATCH 14/23] :dice: Games container --- funcs/games/gamesContainer.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/funcs/games/gamesContainer.py b/funcs/games/gamesContainer.py index 203209a..0169cc8 100644 --- a/funcs/games/gamesContainer.py +++ b/funcs/games/gamesContainer.py @@ -1,3 +1,13 @@ +""" +Has a container for game functions. + +*Classes* +--------- + Games + Container for game functions. +""" + + from .invest import Invest from .trivia import Trivia from .blackjack import Blackjack @@ -5,8 +15,29 @@ from .connectFour import ConnectFour from .hangman import Hangman from .hex import HexGame + class Games(): + """ + Contains game classes. + + *Attributes* + ------------ + bot: Gwendolyn + The instance of Gwendolyn. + invest + Contains investment functions. + blackjack + Contains blackjack functions. + connectFour + Contains connect four functions. + hangman + Contains hangman functions. + hex + Contains hex functions + """ + def __init__(self, bot): + """Initialize the container.""" self.bot = bot self.invest = Invest(bot) From 883f84f569d180fea51271980ab8c7a6660eff9f Mon Sep 17 00:00:00 2001 From: NikolajDanger Date: Sun, 18 Apr 2021 14:06:39 +0200 Subject: [PATCH 15/23] :moneybag: Invest --- funcs/games/invest.py | 297 +++++++++++++++++++++++++++---------- resources/longStrings.json | 4 +- 2 files changed, 220 insertions(+), 81 deletions(-) diff --git a/funcs/games/invest.py b/funcs/games/invest.py index 75bb33a..2501353 100644 --- a/funcs/games/invest.py +++ b/funcs/games/invest.py @@ -1,111 +1,243 @@ -import discord +""" +Contains functions relating to invest commands. + +*Classes* +--------- + Invest + Contains all the code for the invest commands. +""" +import discord # Used for embeds +from discord_slash.context import SlashContext # Used for type hints + class Invest(): + """ + Contains all the invest functions. + + *Methods* + --------- + getPrice(symbol: str) -> int + getPortfolio(user: str) -> str + buyStock(user: str, stock: str: buyAmount: int) -> str + sellStock(user: str, stock: str, buyAmount: int) -> str + parseInvest(ctx: discord_slash.context.SlashContext, + parameters: str) + """ + def __init__(self, bot): + """Initialize the class.""" self.bot = bot - def getPrice(self, symbol : str): + def getPrice(self, symbol: str): + """ + Get the price of a stock. + + *Parameters* + ------------ + symbol: str + The symbol of the stock to get the price of. + + *Returns* + --------- + price: int + The price of the stock. + """ res = self.bot.finnhubClient.quote(symbol.upper()) if res == {}: return 0 else: return int(res["c"] * 100) - def getPortfolio(self, user : str): - userInvestments = self.bot.database["investments"].find_one({"_id":user}) + def getPortfolio(self, user: str): + """ + Get the stock portfolio of a user. - if userInvestments in [None,{}]: - return f"{self.bot.databaseFuncs.getName(user)} does not have a stock portfolio." + *Parameters* + ------------ + user: str + The id of the user to get the portfolio of. + + *Returns* + --------- + portfolio: str + The portfolio. + """ + investmentsDatabase = self.bot.database["investments"] + userInvestments = investmentsDatabase.find_one({"_id": user}) + + userName = self.bot.databaseFuncs.getName(user) + + if userInvestments in [None, {}]: + return f"{userName} does not have a stock portfolio." else: - portfolio = f"**Stock portfolio for {self.bot.databaseFuncs.getName(user)}**" + portfolio = f"**Stock portfolio for {userName}**" for key, value in list(userInvestments["investments"].items()): purchaseValue = value["purchased for"] - currentValue = int((self.getPrice(key) / value["value at purchase"]) * value["purchased"]) - if purchaseValue == "?": - portfolio += f"\n**{key}**: ___{str(currentValue)} GwendoBucks___" - else: - portfolio += f"\n**{key}**: ___{str(currentValue)} GwendoBucks___ (purchased for {str(purchaseValue)})" + stockPrice = self.getPrice(key) + valueAtPurchase = value["value at purchase"] + purchasedStock = value["purchased"] + valueChange = (stockPrice / valueAtPurchase) + currentValue = int(valueChange * purchasedStock) + portfolio += f"\n**{key}**: ___{currentValue} GwendoBucks___" + + if purchaseValue != "?": + portfolio += f" (purchased for {purchaseValue})" return portfolio - def buyStock(self, user : str, stock : str, buyAmount : int): - if buyAmount >= 100: - if self.bot.money.checkBalance(user) >= buyAmount: - stockPrice = self.getPrice(stock) - if stockPrice > 0: - userInvestments = self.bot.database["investments"].find_one({"_id":user}) + def buyStock(self, user: str, stock: str, buyAmount: int): + """ + Buy an amount of a specific stock. - self.bot.money.addMoney(user,-1*buyAmount) - stock = stock.upper() + *Paramaters* + ------------ + user: str + The id of the user buying. + stock: str + The symbol of the stock to buy. + buyAmount: int + The amount of GwendoBucks to use to buy the stock. - if userInvestments != None: - userInvestments = userInvestments["investments"] - if stock in userInvestments: - value = userInvestments[stock] - newAmount = int((stockPrice / value["value at purchase"]) * value["purchased"]) + buyAmount - - self.bot.database["investments"].update_one({"_id":user}, - {"$set":{"investments."+stock+".value at purchase" : stockPrice}}) - - self.bot.database["investments"].update_one({"_id":user}, - {"$set":{"investments."+stock+".purchased" : newAmount}}) - - if value["purchased for"] != "?": - self.bot.database["investments"].update_one({"_id":user}, - {"$set":{"investments."+stock+".purchased for" : buyAmount}}) - else: - self.bot.database["investments"].update_one({"_id":user}, - {"$set":{"investments."+stock : {"purchased" : buyAmount, "value at purchase" : stockPrice, - "purchased for" : buyAmount}}}) - else: - newUser = {"_id":user,"investments":{stock : {"purchased" : buyAmount, "value at purchase" : stockPrice, "purchased for" : buyAmount}}} - self.bot.database["investments"].insert_one(newUser) - - return f"{self.bot.databaseFuncs.getName(user)} bought {buyAmount} GwendoBucks worth of {stock} stock" - else: - return f"{stock} is not traded on the american market." - else: - return "You don't have enough money for that" - else: + *Returns* + --------- + sendMessage: str + The message to return to the user. + """ + if buyAmount < 100: return "You cannot buy stocks for less than 100 GwendoBucks" + elif self.bot.money.checkBalance(user) < buyAmount: + return "You don't have enough money for that" + elif self.getPrice(stock) <= 0: + return f"{stock} is not traded on the american market." + else: + investmentsDatabase = self.bot.database["investments"] + stockPrice = self.getPrice(stock) + userInvestments = investmentsDatabase.find_one({"_id": user}) - def sellStock(self, user : str, stock : str, sellAmount : int): - if sellAmount > 0: + self.bot.money.addMoney(user, -1*buyAmount) + stock = stock.upper() - userInvestments = self.bot.database["investments"].find_one({"_id":user})["investments"] + if userInvestments is not None: + userInvestments = userInvestments["investments"] + if stock in userInvestments: + value = userInvestments[stock] + valueChange = (stockPrice / value["value at purchase"]) + currentValue = int(valueChange * value["purchased"]) + newAmount = currentValue + buyAmount + + valuePath = f"investments.{stock}.value at purchase" + updater = {"$set": {valuePath: stockPrice}} + investmentsDatabase.update_one({"_id": user}, updater) + + purchasedPath = f"investments.{stock}.purchased" + updater = {"$set": {purchasedPath: newAmount}} + investmentsDatabase.update_one({"_id": user}, updater) + + if value["purchased for"] != "?": + purchasedForPath = f"investments.{stock}.purchased for" + updater = {"$set": {purchasedForPath: buyAmount}} + investmentsDatabase.update_one({"_id": user}, updater) + else: + updater = { + "$set": { + "investments.{stock}": { + "purchased": buyAmount, + "value at purchase": stockPrice, + "purchased for": buyAmount + } + } + } + investmentsDatabase.update_one({"_id": user}, updater) + else: + newUser = { + "_id": user, + "investments": { + stock: { + "purchased": buyAmount, + "value at purchase": stockPrice, + "purchased for": buyAmount + } + } + } + investmentsDatabase.insert_one(newUser) + + userName = self.bot.databaseFuncs.getName(user) + sendMessage = "{} bought {} GwendoBucks worth of {} stock" + sendMessage = sendMessage.format(userName, buyAmount, stock) + return sendMessage + + def sellStock(self, user: str, stock: str, sellAmount: int): + """ + Sell an amount of a specific stock. + + *Paramaters* + ------------ + user: str + The id of the user selling. + stock: str + The symbol of the stock to sell. + buyAmount: int + The amount of GwendoBucks to sell for. + + *Returns* + --------- + sendMessage: str + The message to return to the user. + """ + if sellAmount <= 0: + return "no" + else: + investmentsDatabase = self.bot.database["investments"] + userData = investmentsDatabase.find_one({"_id": user}) + userInvestments = userData["investments"] stock = stock.upper() - if userInvestments != None and stock in userInvestments: + if userInvestments is not None and stock in userInvestments: value = userInvestments[stock] stockPrice = self.getPrice(stock) - self.bot.database["investments"].update_one({"_id":user}, - {"$set":{"investments."+stock+".purchased" : - int((stockPrice / value["value at purchase"]) * value["purchased"])}}) - self.bot.database["investments"].update_one({"_id":user}, - {"$set":{"investments."+stock+".value at purchase" : stockPrice}}) + priceChange = (stockPrice / value["value at purchase"]) + purchasedAmount = int(priceChange * value["purchased"]) + purchasedPath = f"investments.{stock}.purchased" + updater = {"$set": {purchasedPath: purchasedAmount}} + investmentsDatabase.update_one({"_id": user}, updater) + valueAtPurchasePath = f"investments.{stock}.value at purchase" + updater = {"$set": {valueAtPurchasePath: stockPrice}} + investmentsDatabase.update_one({"_id": user}, updater) if value["purchased"] >= sellAmount: - self.bot.money.addMoney(user,sellAmount) + self.bot.money.addMoney(user, sellAmount) if sellAmount < value["purchased"]: - self.bot.database["investments"].update_one({"_id":user}, - {"$inc":{"investments."+stock+".purchased" : -sellAmount}}) + purchasedPath = f"investments.{stock}.purchased" + updater = {"$inc": {purchasedPath: -sellAmount}} + investmentsDatabase.update_one({"_id": user}, updater) - self.bot.database["investments"].update_one({"_id":user}, - {"$set":{"investments."+stock+".purchased for" : "?"}}) + purchasedForPath = f"investments.{stock}.purchased for" + updater = {"$set": {purchasedForPath: "?"}} + investmentsDatabase.update_one({"_id": user}, updater) else: - self.bot.database["investments"].update_one({"_id":user}, - {"$unset":{"investments."+stock:""}}) + updater = {"$unset": {f"investments.{stock}": ""}} + investmentsDatabase.update_one({"_id": user}, updater) - return f"{self.bot.databaseFuncs.getName(user)} sold {sellAmount} GwendoBucks worth of {stock} stock" + userName = self.bot.databaseFuncs.getName(user) + sendMessage = "{} sold {} GwendoBucks worth of {} stock" + return sendMessage.format(userName, sellAmount, stock) else: return f"You don't have enough {stock} stocks to do that" else: return f"You don't have any {stock} stock" - else: - return "no" - async def parseInvest(self, ctx, parameters): + async def parseInvest(self, ctx: SlashContext, parameters: str): + """ + Parse an invest command. TO BE DELETED. + + *Parameters* + ------------ + ctx: discord_slash.context.SlashContext + The context of the slash command. + parameters: str + The parameters of the command. + """ await self.bot.defer(ctx) user = f"#{ctx.author.id}" @@ -116,34 +248,39 @@ class Invest(): else: price = self.getPrice(commands[1]) if price == 0: - response = f"{commands[1].upper()} is not traded on the american market." + response = "{} is not traded on the american market." + response = response.format(commands[0].upper()) else: - price = f"{price:,}".replace(",",".") - response = f"The current {commands[1].upper()} stock is valued at **{price}** GwendoBucks" + price = f"{price:,}".replace(",", ".") + response = self.bot.longStrings["Stock value"] + response = response.format(commands[1].upper(), price) elif parameters.startswith("buy"): commands = parameters.split(" ") if len(commands) == 3: - response = self.buyStock(user,commands[1],int(commands[2])) + response = self.buyStock(user, commands[1], int(commands[2])) else: - response = "You must give both a stock name and an amount of gwendobucks you wish to spend." + response = self.bot.longStrings["Stock parameters"] elif parameters.startswith("sell"): commands = parameters.split(" ") if len(commands) == 3: - try: - response = self.sellStock(user,commands[1],int(commands[2])) - except: - response = "The command must be given as \"/invest sell [stock] [amount of GwendoBucks to sell stocks for]\"" + response = self.sellStock(user, commands[1], int(commands[2])) else: - response = "You must give both a stock name and an amount of GwendoBucks you wish to sell stocks for." + response = self.bot.longStrings["Stock parameters"] else: response = "Incorrect parameters" if response.startswith("**"): responses = response.split("\n") - em = discord.Embed(title=responses[0],description="\n".join(responses[1:]),colour=0x00FF00) + text = "\n".join(responses[1:]) + embedParams = { + "title": responses[0], + "description": text, + "colour": 0x00FF00 + } + em = discord.Embed(*embedParams) await ctx.send(embed=em) else: await ctx.send(response) diff --git a/resources/longStrings.json b/resources/longStrings.json index 1458e3d..d1e1a0e 100644 --- a/resources/longStrings.json +++ b/resources/longStrings.json @@ -8,5 +8,7 @@ "Blackjack different cards": "You can only split if your cards have the same value", "Blackjack split": "Splitting {}'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.", "Blackjack started": "Blackjack game started. Use \"/blackjack bet [amount]\" to enter the game within the next 30 seconds.", - "Blackjack going on": "There's already a blackjack game going on. Try again in a few minutes." + "Blackjack going on": "There's already a blackjack game going on. Try again in a few minutes.", + "Stock value": "The current {} stock is valued at **{}** GwendoBucks", + "Stock parameters": "You must give both a stock name and an amount of GwendoBucks you wish to spend." } \ No newline at end of file From fd9bde73c614304897e8f4c98f1fd225e69978b6 Mon Sep 17 00:00:00 2001 From: NikolajDanger Date: Sun, 18 Apr 2021 14:42:21 +0200 Subject: [PATCH 16/23] :sparkles: Trivia --- funcs/games/trivia.py | 207 ++++++++++++++++++++++++++----------- resources/longStrings.json | 4 +- 2 files changed, 150 insertions(+), 61 deletions(-) diff --git a/funcs/games/trivia.py b/funcs/games/trivia.py index eeb4003..e01fc1d 100644 --- a/funcs/games/trivia.py +++ b/funcs/games/trivia.py @@ -1,94 +1,175 @@ -import json -import urllib -import random -import asyncio +""" +Contains code for trivia games. + +*Classes* +--------- + Trivia + Contains all the code for the trivia commands. +""" +import urllib # Used to get data from api +import json # Used to read data from api +import random # Used to shuffle answers +import asyncio # Used to sleep + +from discord_slash.context import SlashContext # Used for type hints + class Trivia(): + """ + Contains the code for trivia games. + + *Methods* + --------- + triviaStart(channel: str) -> str, str, str + triviaAnswer(user: str, channel: str, command: str) -> str + triviaCountPoints(channel: str) + triviaParse(ctx: SlashContext, answer: str) + """ + def __init__(self, bot): + """Initialize the class.""" self.bot = bot - # Starts a game of trivia. Downloads a question with answers, shuffles the wrong answers with the - # correct answer and returns the questions and answers. Also saves the question in the games.json file. - def triviaStart(self, channel : str): - question = self.bot.database["trivia questions"].find_one({"_id":channel}) + def triviaStart(self, channel: str): + """ + Start a game of trivia. - self.bot.log("Trying to find a trivia question for "+channel) + Downloads a question with answers, shuffles the wrong answers + with the correct answer and returns the questions and answers. + Also saves the question in the database. - if question == None: - with urllib.request.urlopen("https://opentdb.com/api.php?amount=10&type=multiple") as response: + *Parameters* + ------------ + channel: str + The id of the channel to start the game in + + *Returns* + --------- + sendMessage: str + The message to return to the user. + """ + triviaQuestions = self.bot.database["trivia questions"] + question = triviaQuestions.find_one({"_id": channel}) + + self.bot.log(f"Trying to find a trivia question for {channel}") + + if question is None: + apiUrl = "https://opentdb.com/api.php?amount=10&type=multiple" + with urllib.request.urlopen(apiUrl) as response: data = json.loads(response.read()) - self.bot.log("Found the question \""+data["results"][0]["question"]+"\"") + question = data["results"][0]["question"] + self.bot.log(f"Found the question \"{question}\"") answers = data["results"][0]["incorrect_answers"] answers.append(data["results"][0]["correct_answer"]) random.shuffle(answers) - correctAnswer = answers.index(data["results"][0]["correct_answer"]) + 97 + correctAnswer = data["results"][0]["correct_answer"] + correctAnswer = answers.index(correctAnswer) + 97 - self.bot.database["trivia questions"].insert_one({"_id":channel,"answer" : str(chr(correctAnswer)),"players" : {}}) + newQuestion = { + "_id": channel, + "answer": str(chr(correctAnswer)), + "players": {} + } + triviaQuestions.insert_one(newQuestion) - replacements = {"'": "\'", - """: "\"", - "“": "\"", - "”": "\"", - "é": "é"} + replacements = { + "'": "\'", + """: "\"", + "“": "\"", + "”": "\"", + "é": "é" + } question = data["results"][0]["question"] for key, value in replacements.items(): - question = question.replace(key,value) + question = question.replace(key, value) for answer in answers: for key, value in replacements.items(): - answer = answer.replace(key,value) + answer = answer.replace(key, value) return question, answers, correctAnswer else: - self.bot.log("There was already a trivia question for that channel (error code 1106)") - return "There's already a trivia question going on. Try again in like, a minute (error code 1106)", "", "" + logMessage = "There was already a trivia question for that channel" + self.bot.log(logMessage) + return self.bot.longStrings["Trivia going on"], "", "" - # Lets players answer a trivia question - def triviaAnswer(self, user : str, channel : str, command : str): - question = self.bot.database["trivia questions"].find_one({"_id":channel}) + def triviaAnswer(self, user: str, channel: str, command: str): + """ + Answer the current trivia question. - if command in ["a","b","c","d"]: - if question != None: - if user not in question["players"]: - self.bot.log(user+" answered the question in "+channel) + *Parameters* + ------------ + user: str + The id of the user who answered. + channel: str + The id of the channel the game is in. + command: str + The user's answer. - self.bot.database["trivia questions"].update_one({"_id":channel},{"$set":{"players."+user : command}}) + *Returns* + --------- + sendMessage: str + The message to send if the function failed. + """ + triviaQuestions = self.bot.database["trivia questions"] + question = triviaQuestions.find_one({"_id": channel}) - return "Locked in "+user+"'s answer" - else: - self.bot.log(user+" has already answered this question (error code 1105)") - return user+" has already answered this question (error code 1105)" - else: - self.bot.log("There's no question right now (error code 1104)") - return "There's no question right now (error code 1104)" + if command not in ["a", "b", "c", "d"]: + self.bot.log("I didn't quite understand that") + return "I didn't quite understand that" + elif question is None: + self.bot.log("There's no question right now") + return "There's no question right now" + elif user in question["players"]: + self.bot.log(f"{user} has already answered this question") + return f"{user} has already answered this question" else: - self.bot.log("I didn't quite understand that (error code 1103)") - return "I didn't quite understand that (error code 1103)" + self.bot.log(f"{user} answered the question in {channel}") + updater = {"$set": {f"players.{user}": command}} + triviaQuestions.update_one({"_id": channel}, updater) + return None - # Adds 1 GwendoBuck to each player that got the question right and deletes question from games.json. - def triviaCountPoints(self, channel : str): - question = self.bot.database["trivia questions"].find_one({"_id":channel}) + def triviaCountPoints(self, channel: str): + """ + Add money to every winner's account. + + *Parameters* + ------------ + channel: str + The id of the channel the game is in. + """ + triviaQuestions = self.bot.database["trivia questions"] + question = triviaQuestions.find_one({"_id": channel}) self.bot.log("Counting points for question in "+channel) - if question != None: + if question is not None: for player, answer in question["players"].items(): if answer == question["answer"]: - self.bot.money.addMoney(player,1) - - + self.bot.money.addMoney(player, 1) else: - self.bot.log("Couldn't find the question (error code 1102)") + self.bot.log("Couldn't find the questio") return None - async def triviaParse(self, ctx, answer): + async def triviaParse(self, ctx: SlashContext, answer: str): + """ + Parse a trivia command. + + *Parameters* + ------------ + ctx: SlashContext + The context of the command. + answer: str + The answer, if any. + """ await self.bot.defer(ctx) + channelId = str(ctx.channel_id) if answer == "": - question, options, correctAnswer = self.triviaStart(str(ctx.channel_id)) + question, options, correctAnswer = self.triviaStart(channelId) if options != "": results = "**"+question+"**\n" for x, option in enumerate(options): @@ -98,21 +179,27 @@ class Trivia(): await asyncio.sleep(60) - self.triviaCountPoints(str(ctx.channel_id)) + self.triviaCountPoints(channelId) - self.bot.databaseFuncs.deleteGame("trivia questions",str(ctx.channel_id)) + deleteGameParams = ["trivia questions", channelId] + self.bot.databaseFuncs.deleteGame(*deleteGameParams) - 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") + self.bot.log("Time's up for the trivia question", channelId) + sendMessage = self.bot.longStrings["Trivia time up"] + formatParams = [chr(correctAnswer), options[correctAnswer-97]] + sendMessage = sendMessage.format(*formatParams) + await ctx.send(sendMessage) 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}**") + elif answer in ["a", "b", "c", "d"]: + userId = f"#{ctx.author.id}" + response = self.triviaAnswer(userId, channelId, answer) + if response is None: + userName = ctx.author.display_name + await ctx.send(f"{userName} 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)") + self.bot.log("I didn't understand that", channelId) + await ctx.send("I didn't understand that") diff --git a/resources/longStrings.json b/resources/longStrings.json index d1e1a0e..188fc7f 100644 --- a/resources/longStrings.json +++ b/resources/longStrings.json @@ -10,5 +10,7 @@ "Blackjack started": "Blackjack game started. Use \"/blackjack bet [amount]\" to enter the game within the next 30 seconds.", "Blackjack going on": "There's already a blackjack game going on. Try again in a few minutes.", "Stock value": "The current {} stock is valued at **{}** GwendoBucks", - "Stock parameters": "You must give both a stock name and an amount of GwendoBucks you wish to spend." + "Stock parameters": "You must give both a stock name and an amount of GwendoBucks you wish to spend.", + "Trivia going on": "There's already a trivia question going on. Try again in like, a minute", + "Trivia time up": "Time's up! The answer was \"*{}) {}*\". Anyone who answered that has gotten 1 GwendoBuck" } \ No newline at end of file From 45a2013c7fc1da495053d8287a9c5f5a3c7f3597 Mon Sep 17 00:00:00 2001 From: NikolajDanger Date: Sun, 18 Apr 2021 20:09:13 +0200 Subject: [PATCH 17/23] :red_circle: Connect four --- funcs/games/connectFour.py | 1111 ++++++++++++++++++++++++++---------- resources/longStrings.json | 4 +- 2 files changed, 801 insertions(+), 314 deletions(-) diff --git a/funcs/games/connectFour.py b/funcs/games/connectFour.py index 88a8024..c0d511d 100644 --- a/funcs/games/connectFour.py +++ b/funcs/games/connectFour.py @@ -1,14 +1,42 @@ -import random -import copy -import math -import discord +""" +Contains the classes that deal with playing and drawing connect four. + +*Classes* +--------- + ConnectFour + DrawConnectFour +""" +import random # Used to shuffle players and by the ai to pick from +# similar options +import copy # Used to deepcopy boards +import math # Used for math.inf +import discord # Used for typehints, discord.file and to check whether +# the opponent in ConnectFour.start is a discord.User + +from PIL import Image, ImageDraw, ImageFont # Used by DrawConnectFour() +from discord_slash.context import SlashContext # Used for typehints +from typing import Union # Used for typehints + +ROWCOUNT = 6 +COLUMNCOUNT = 7 -from PIL import Image, ImageDraw, ImageFont class ConnectFour(): - def __init__(self,bot): + """ + Deals with connect four commands and logic. + + *Methods* + --------- + start(ctx: SlashContext, opponent: Union[int, Discord.User]) + placePiece(ctx: Union[SlashContext, discord.Message], + user: int, column: int) + surrender(ctx: SlashContext) + """ + + def __init__(self, bot): + """Initialize the class.""" self.bot = bot - self.draw = drawConnectFour(bot) + self.draw = DrawConnectFour(bot) self.AISCORES = { "middle": 3, "two in a row": 10, @@ -19,67 +47,84 @@ class ConnectFour(): "win": 10000, "avoid losing": 100 } + self.REACTIONS = ["1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣"] - self.ROWCOUNT = 6 - self.COLUMNCOUNT = 7 + async def start(self, ctx: SlashContext, + opponent: Union[int, discord.User]): + """ + Start a game of connect four. - # Starts the game - async def start(self, ctx, opponent): + *Parameters* + ------------ + ctx: SlashContext + The context of the slash command. + opponent: Union[int, discord.User] + The requested opponent. If it's an int, the opponent is + Gwendolyn with a difficulty equal to the value of the + int. The difficulty is equal to how many levels the AI + searches when minimaxing. + """ await self.bot.defer(ctx) user = f"#{ctx.author.id}" channel = str(ctx.channel_id) - game = self.bot.database["connect 4 games"].find_one({"_id":channel}) + game = self.bot.database["connect 4 games"].find_one({"_id": channel}) startedGame = False canStart = True - if game != None: - sendMessage = "There's already a connect 4 game going on in this channel" + if game is not None: + sendMessage = self.bot.longStrings["Connect 4 going on"] 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) + elif 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 challenged a difficulty that doesn't exist" + canStart = False + elif type(opponent) == discord.User: + 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 = "Difficulty doesn't exist" - logMessage = "They tried to play against a difficulty that doesn't exist" + 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: + opponent = f"#{opponent.id}" + difficulty = 5 + diffText = "" + else: + sendMessage = "You can't play against yourself" + logMessage = "They tried to play against themself" 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 - if ctx.author != opponent: - opponent = f"#{opponent.id}" - difficulty = 5 - diffText = "" - else: - sendMessage = "You can't play against yourself" - logMessage = "They tried to play against themself" - canStart = False - if canStart: - board = [[0 for _ in range(self.COLUMNCOUNT)] for _ in range(self.ROWCOUNT)] + x, y = COLUMNCOUNT, ROWCOUNT + board = [[0 for _ in range(x)] for _ in range(y)] 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) @@ -101,23 +146,42 @@ class ConnectFour(): # 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)) + boardsPath = "resources/games/connect4Boards/" + filePath = f"{boardsPath}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: + oldImagesPath = "resources/games/oldImages/" + oldImagePath = f"{oldImagesPath}connectFour{ctx.channel_id}" + with open(oldImagePath, "w") as f: f.write(str(oldImage.id)) if gwendoTurn: - await self.connectFourAI(ctx) + await self._connectFourAI(ctx) else: - reactions = ["1️⃣","2️⃣","3️⃣","4️⃣","5️⃣","6️⃣","7️⃣"] - for reaction in reactions: + for reaction in self.REACTIONS: await oldImage.add_reaction(reaction) - # Places a piece at the lowest available point in a specific column - async def placePiece(self, ctx, user, column): + async def placePiece(self, ctx: Union[SlashContext, discord.Message], + user: int, column: int): + """ + Place a piece on the board. + + *Parameters* + ------------ + ctx: Union[SlashContext, discord.Message] + The context of the command/reaction. ctx can only be + SlashContext if the piece is placed by the bot + immediately after the player starts the game with a + command. Otherwise, ctx will be the message the player + reacted to when placing a piece. + user: int + The player-number of the player placing the piece. + column: int + The column the player is placing the piece in. + """ channel = str(ctx.channel.id) - game = self.bot.database["connect 4 games"].find_one({"_id":channel}) + connect4Games = self.bot.database["connect 4 games"] + game = connect4Games.find_one({"_id": channel}) playerNumber = game["players"].index(user)+1 userName = self.bot.databaseFuncs.getName(user) placedPiece = False @@ -127,39 +191,48 @@ class ConnectFour(): logMessage = "There was no game in the channel" else: board = game["board"] - board = self.placeOnBoard(board, playerNumber, column) + board = self._placeOnBoard(board, playerNumber, column) 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}}) + updater = {"$set": {"board": board}} + connect4Games.update_one({"_id": channel}, updater) + turn = (game["turn"]+1) % 2 + updater = {"$set": {"turn": turn}} + connect4Games.update_one({"_id": channel}, updater) self.bot.log("Checking for win") - won, winDirection, winCoordinates = self.isWon(board) + won, winDirection, winCoordinates = self._isWon(board) if won != 0: gameWon = True - self.bot.database["connect 4 games"].update_one({"_id":channel},{"$set":{"winner":won}}) - self.bot.database["connect 4 games"].update_one({"_id":channel},{"$set":{"win direction":winDirection}}) - self.bot.database["connect 4 games"].update_one({"_id":channel}, - {"$set":{"win coordinates":winCoordinates}}) + updater = {"$set": {"winner": won}} + connect4Games.update_one({"_id": channel}, updater) + updater = {"$set": {"win direction": winDirection}} + connect4Games.update_one({"_id": channel}, updater) + updater = {"$set": {"win coordinates": winCoordinates}} + connect4Games.update_one({"_id": channel}, updater) - sendMessage = f"{userName} placed a piece in column {column+1} and won." + sendMessage = "{} placed a piece in column {} and won. " + sendMessage = sendMessage.format(userName, column+1) logMessage = f"{userName} won" winAmount = int(game["difficulty"])**2+5 if game["players"][won-1] != f"#{self.bot.user.id}": - sendMessage += " Adding "+str(winAmount)+" GwendoBucks to their account" + sendMessage += "Adding {} GwendoBucks to their account" + sendMessage = sendMessage.format(winAmount) elif 0 not in board[0]: gameWon = True sendMessage = "It's a draw!" logMessage = "The game ended in a draw" else: gameWon = False - otherUserName = self.bot.databaseFuncs.getName(game["players"][turn]) - sendMessage = f"{userName} placed a piece in column {column+1}. It's now {otherUserName}'s turn" + otherUserId = game["players"][turn] + otherUserName = self.bot.databaseFuncs.getName(otherUserId) + sendMessage = self.bot.longStrings["Connect 4 placed"] + formatParams = [userName, column+1, otherUserName] + sendMessage = sendMessage.format(*formatParams) logMessage = "They placed the piece" gwendoTurn = (game["players"][turn] == f"#{self.bot.user.id}") @@ -172,7 +245,8 @@ class ConnectFour(): if placedPiece: self.draw.drawImage(channel) - with open(f"resources/games/oldImages/connectFour{channel}", "r") as f: + oldImagePath = f"resources/games/oldImages/connectFour{channel}" + with open(oldImagePath, "r") as f: oldImage = await ctx.channel.fetch_message(int(f.read())) if oldImage is not None: @@ -181,56 +255,35 @@ class ConnectFour(): 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)) + oldImage = await ctx.channel.send(file=discord.File(filePath)) if gameWon: - self.endGame(channel) + self._endGame(channel) else: - with open(f"resources/games/oldImages/connectFour{channel}", "w") as f: + with open(oldImagePath, "w") as f: f.write(str(oldImage.id)) if gwendoTurn: - await self.connectFourAI(ctx) + await self._connectFourAI(ctx) else: - reactions = ["1️⃣","2️⃣","3️⃣","4️⃣","5️⃣","6️⃣","7️⃣"] - for reaction in reactions: + for reaction in self.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 + async def surrender(self, ctx: SlashContext): + """ + Surrender a connect four game. - for x, line in enumerate(board): - if line[column] == 0: - placementX = x - - board[placementX][placementY] = player - - 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 - async def surrender(self, ctx): + *Parameters* + ------------ + ctx: SlashContext + The context of the command. + """ await self.bot.defer(ctx) channel = str(ctx.channel_id) - game = self.bot.database["connect 4 games"].find_one({"_id":channel}) + 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 + winnerIndex = (loserIndex+1) % 2 winnerID = game["players"][winnerIndex] winnerName = self.bot.databaseFuncs.getName(winnerID) @@ -242,7 +295,8 @@ class ConnectFour(): sendMessage += f" Adding {reward} to their account" await ctx.send(sendMessage) - with open(f"resources/games/oldImages/connectFour{channel}", "r") as f: + oldImagePath = f"resources/games/oldImages/connectFour{channel}" + with open(oldImagePath, "r") as f: oldImage = await ctx.channel.fetch_message(int(f.read())) if oldImage is not None: @@ -250,142 +304,219 @@ class ConnectFour(): else: self.bot.log("The old image was already deleted") - self.endGame(channel) + self._endGame(channel) else: 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): + def _placeOnBoard(self, board: dict, player: int, column: int): + """ + Place a piece in a given board. + + This differs from placePiece() by not having anything to do + with any actual game. It just places the piece in a game dict. + + *Parameters* + ------------ + board: dict + The board to place the piece in. + player: int + The player who places the piece. + column: int + The column the piece is placed in. + + *Returns* + --------- + board: dict + The board with the placed piece. + """ + placementX, placementY = -1, column + + for x, line in enumerate(board): + if line[column] == 0: + placementX = x + break + + board[placementX][placementY] = player + + if placementX == -1: + return None + else: + return board + + def _endGame(self, channel: str): + 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) + + def _isWon(self, board: dict): won = 0 winDirection = "" - winCoordinates = [0,0] + winCoordinates = [0, 0] - for row in range(self.ROWCOUNT): - for place in range(self.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 <= self.COLUMNCOUNT-4: - pieces = [board[row][place+1],board[row][place+2],board[row][place+3]] + if place <= COLUMNCOUNT-4: + pieceOne = board[row][place+1] + pieceTwo = board[row][place+2] + pieceThree = board[row][place+3] + + pieces = [pieceOne, pieceTwo, pieceThree] else: pieces = [0] if all(x == piecePlayer for x in pieces): won = piecePlayer winDirection = "h" - winCoordinates = [row,place] + winCoordinates = [row, place] # Checks vertical - if row <= self.ROWCOUNT-4: - pieces = [board[row+1][place],board[row+2][place],board[row+3][place]] + if row <= ROWCOUNT-4: + pieceOne = board[row+1][place] + pieceTwo = board[row+2][place] + pieceThree = board[row+3][place] + + pieces = [pieceOne, pieceTwo, pieceThree] else: pieces = [0] if all(x == piecePlayer for x in pieces): won = piecePlayer winDirection = "v" - winCoordinates = [row,place] + winCoordinates = [row, place] # Checks right diagonal - if row <= self.ROWCOUNT-4 and place <= self.COLUMNCOUNT-4: - pieces = [board[row+1][place+1],board[row+2][place+2],board[row+3][place+3]] + goodRow = (row <= ROWCOUNT-4) + goodColumn = (place <= COLUMNCOUNT-4) + if goodRow and goodColumn: + pieceOne = board[row+1][place+1] + pieceTwo = board[row+2][place+2] + pieceThree = board[row+3][place+3] + + pieces = [pieceOne, pieceTwo, pieceThree] else: pieces = [0] if all(x == piecePlayer for x in pieces): won = piecePlayer winDirection = "r" - winCoordinates = [row,place] + winCoordinates = [row, place] # Checks left diagonal - if row <= self.ROWCOUNT-4 and place >= 3: - pieces = [board[row+1][place-1],board[row+2][place-2],board[row+3][place-3]] + if row <= ROWCOUNT-4 and place >= 3: + pieceOne = board[row+1][place-1] + pieceTwo = board[row+2][place-2] + pieceThree = board[row+3][place-3] + + pieces = [pieceOne, pieceTwo, pieceThree] else: pieces = [0] if all(x == piecePlayer for x in pieces): won = piecePlayer winDirection = "l" - winCoordinates = [row,place] - + winCoordinates = [row, place] return won, winDirection, winCoordinates - # Plays as the AI - async def connectFourAI(self, ctx): + async def _connectFourAI(self, ctx: SlashContext): + def outOfRange(possibleScores: list): + allowedRange = max(possibleScores)*(1-0.1) + moreThanOne = len(possibleScores) != 1 + return (min(possibleScores) <= allowedRange) and moreThanOne + channel = str(ctx.channel.id) self.bot.log("Figuring out best move") - game = self.bot.database["connect 4 games"].find_one({"_id":channel}) + game = self.bot.database["connect 4 games"].find_one({"_id": channel}) board = game["board"] player = game["players"].index(f"#{self.bot.user.id}")+1 difficulty = game["difficulty"] - scores = [-math.inf for _ in range(self.COLUMNCOUNT)] - for column in range(self.COLUMNCOUNT): + scores = [int(-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) + testBoard = self._placeOnBoard(testBoard, player, column) + if testBoard is not None: + _minimaxParams = [ + testBoard, + difficulty, + player % 2+1, + player, + int(-math.inf), + int(math.inf), + False + ] + scores[column] = await self._minimax(*_minimaxParams) self.bot.log(f"Best score for column {column} is {scores[column]}") possibleScores = scores.copy() - while (min(possibleScores) <= (max(possibleScores) - max(possibleScores)/10)) and len(possibleScores) != 1: + while outOfRange(possibleScores): possibleScores.remove(min(possibleScores)) - highest_score = random.choice(possibleScores) + highestScore = random.choice(possibleScores) - bestColumns = [i for i, x in enumerate(scores) if x == highest_score] + bestColumns = [i for i, x in enumerate(scores) if x == highestScore] placement = random.choice(bestColumns) await self.placePiece(ctx, f"#{self.bot.user.id}", placement) - # Calculates points for a board - def AICalcPoints(self,board,player): + def _AICalcPoints(self, board: dict, player: int): score = 0 - otherPlayer = player%2+1 + otherPlayer = player % 2+1 # Adds points for middle placement # Checks horizontal - for row in range(self.ROWCOUNT): + for row in range(ROWCOUNT): if board[row][3] == player: score += self.AISCORES["middle"] rowArray = [int(i) for i in list(board[row])] - for place in range(self.COLUMNCOUNT-3): + for place in range(COLUMNCOUNT-3): window = rowArray[place:place+4] - score += self.evaluateWindow(window,player,otherPlayer) + score += self._evaluateWindow(window, player, otherPlayer) # Checks Vertical - for column in range(self.COLUMNCOUNT): + for column in range(COLUMNCOUNT): columnArray = [int(i[column]) for i in list(board)] - for place in range(self.ROWCOUNT-3): + for place in range(ROWCOUNT-3): window = columnArray[place:place+4] - score += self.evaluateWindow(window,player,otherPlayer) + score += self._evaluateWindow(window, player, otherPlayer) # Checks right diagonal - for row in range(self.ROWCOUNT-3): - for place in range(self.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 row in range(ROWCOUNT-3): + for place in range(COLUMNCOUNT-3): + pieceOne = board[row][place] + pieceTwo = board[row+1][place+1] + pieceThree = board[row+2][place+2] + pieceFour = board[row+3][place+3] - for place in range(3,self.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) + window = [pieceOne, pieceTwo, pieceThree, pieceFour] + score += self._evaluateWindow(window, player, otherPlayer) + for place in range(3, COLUMNCOUNT): + pieceOne = board[row][place] + pieceTwo = board[row+1][place-1] + pieceThree = board[row+2][place-2] + pieceFour = board[row+3][place-3] - ## Checks if anyone has won - #won = isWon(board)[0] - - ## Add points if AI wins - #if won == player: - # score += self.AISCORES["win"] + window = [pieceOne, pieceTwo, pieceThree, pieceFour] + score += self._evaluateWindow(window, player, otherPlayer) return score - def evaluateWindow(self, window,player,otherPlayer): + def _evaluateWindow(self, window: list, player: int, otherPlayer: int): if window.count(player) == 4: return self.AISCORES["win"] elif window.count(player) == 3 and window.count(0) == 1: @@ -397,76 +528,498 @@ class ConnectFour(): else: return 0 - async def minimax(self, board, depth, player , originalPlayer, alpha, beta, maximizingPlayer): - #terminal = ((0 not in board[0]) or (isWon(board)[0] != 0)) + async def _minimax(self, board: dict, depth: int, player: int, + originalPlayer: int, alpha: int, beta: int, + maximizingPlayer: bool): + """ + Evaluate the minimax value of a board. + + *Parameters* + ------------ + board: dict + The board to evaluate. + depth: int + How many more levels to check. If 0, the current value + is returned. + player: int + The player who can place a piece. + originalPlayer: int + The AI player. + alpha, beta: int + Used with alpha/beta pruning, in order to prune options + that will never be picked. + maximizingPlayer: bool + Whether the current player is the one trying to + maximize their score. + + *Returns* + --------- + value: int + The minimax value of the board. + """ terminal = 0 not in board[0] - points = self.AICalcPoints(board,originalPlayer) - # The depth is how many moves ahead the computer checks. This value is the difficulty. + points = self._AICalcPoints(board, originalPlayer) + # The depth is how many moves ahead the computer checks. This + # value is the difficulty. if depth == 0 or terminal or (points > 5000 or points < -6000): return points if maximizingPlayer: - value = -math.inf - for column in range(0,self.COLUMNCOUNT): + value = int(-math.inf) + 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 += self.AISCORES["avoid losing"] - value = max(value,evaluation) - alpha = max(alpha,evaluation) - if beta <= alpha: break + testBoard = self._placeOnBoard(testBoard, player, column) + if testBoard is not None: + _minimaxParams = [ + testBoard, + depth-1, + (player % 2)+1, + originalPlayer, + alpha, + beta, + False + ] + evaluation = await self._minimax(*_minimaxParams) + if evaluation < -9000: + evaluation += self.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,self.COLUMNCOUNT): + value = int(math.inf) + 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 += self.AISCORES["avoid losing"] - value = min(value,evaluation) - beta = min(beta,evaluation) - if beta <= alpha: break + testBoard = self._placeOnBoard(testBoard, player, column) + if testBoard is not None: + _minimaxParams = [ + testBoard, + depth-1, + (player % 2)+1, + originalPlayer, + alpha, + beta, + True + ] + evaluation = await self._minimax(*_minimaxParams) + if evaluation < -9000: + evaluation += self.AISCORES["avoid losing"] + value = min(value, evaluation) + beta = min(beta, evaluation) + if beta <= alpha: + break return value -class drawConnectFour(): + +class DrawConnectFour(): + """ + Functions for drawing the connect four board. + + *Methods* + --------- + drawImage(channel: str) + + *Attributes* + ------------ + BORDER: int + The border around the game board. + GRIDBORDER: int + The border between the edge of the board and the piece + furthest towards the edge. + CORNERSIZE: int + The size of the corner circles. + BOARDOUTLINESIZE: int + How big the outline of the board is. + BOARDOUTLINECOLOR: RGBA tuple + The color of the outline of the board. + PIECEOUTLINESIZE: int + How big the outline of each piece is. + PIECEOUTLINECOLOR: RGB tuple + The color of the outline of each piece. + EMPTYOUTLINESIZE: int + How big the outline of an empty place on the board is. + EMPTYOUTLINECOLOR: RGB tuple + The color of the outline of an empty place on the board. + BOTTOMBORDER: int + The border at the bottom where the names are. + TEXTSIZE: int + The size of the text. + TEXTPADDING: int + How much padding to add to the the text. + WIDTH, HEIGHT: int + The size of the image, minus the bottom border + BACKGROUNDCOLOR: RGBA tuple + The color of the background. + PLAYER1COLOR, PLAYER2COLOR: RGB tuple + The color of each player. + BOARDCOLOR: RGB tuple + The color of the board + WINBARCOLOR: RGBA tuple + The color of the bar placed over the winning pieces. + PIECESIZE: int + The size of each piece. + FONT: FreeTypeFont + The font to use to write text. + PIECEGRIDSIZE: list + The size of each grid cell on the board. + PIECESTARTX, PIECESTARTY: int + Where the top left piece should be placed. + WINBARWHITE: RGBA tuple + White, but with the alpha set to winbarAlpha. + """ + def __init__(self, bot): + """Initialize the class.""" self.bot = bot + self.BORDER = 40 + self.GRIDBORDER = 40 + self.CORNERSIZE = 300 + self.BOARDOUTLINESIZE = 10 + self.BOARDOUTLINECOLOR = (0, 0, 0) + + self.PIECEOUTLINESIZE = 10 + self.PIECEOUTLINECOLOR = (244, 244, 248) + self.EMPTYOUTLINESIZE = 0 + self.EMPTYOUTLINECOLOR = (0, 0, 0) + + self.BOTTOMBORDER = 110 + self.TEXTSIZE = 100 + self.TEXTPADDING = 20 + self.WIDTH, self.HEIGHT = 2800, 2400 + self.BACKGROUNDCOLOR = (230, 230, 234, 255) + self.PLAYER1COLOR = (254, 74, 73) + self.PLAYER2COLOR = (254, 215, 102) + self.BOARDCOLOR = (42, 183, 202) + winbarAlpha = 160 + self.WINBARCOLOR = (250, 250, 250, 255) + self.PIECESIZE = 300 + fontPath = "resources/fonts/futura-bold.ttf" + + self.FONT = ImageFont.truetype(fontPath, self.TEXTSIZE) + + boardWidth = self.WIDTH-(2*(self.BORDER+self.GRIDBORDER)) + boardHeight = self.HEIGHT-(2*(self.BORDER+self.GRIDBORDER)) + boardSize = [boardWidth, boardHeight] + + pieceWidth = boardSize[0]//COLUMNCOUNT + pieceHeight = boardSize[1]//ROWCOUNT + self.PIECEGRIDSIZE = [pieceWidth, pieceHeight] + + pieceStart = (self.BORDER+self.GRIDBORDER)-(self.PIECESIZE//2) + self.PIECESTARTX = pieceStart+(self.PIECEGRIDSIZE[0]//2) + self.PIECESTARTY = pieceStart+(self.PIECEGRIDSIZE[1]//2) + + self.WINBARWHITE = (255, 255, 255, winbarAlpha) + # Draws the whole thing - def drawImage(self, channel): + def drawImage(self, channel: str): + """ + Draw an image of the connect four board. + + *Parameters* + ------------ + channel: str + The id of the channel the game is in. + """ self.bot.log("Drawing connect four board") - game = self.bot.database["connect 4 games"].find_one({"_id":channel}) + game = self.bot.database["connect 4 games"].find_one({"_id": channel}) board = game["board"] - border = 40 - gridBorder = 40 - cornerSize = 300 - boardOutlineSize = 10 - pieceOutlineSize = 10 - emptyOutlineSize = 0 - bottomBorder = 110 - exampleCircles = 100 - w, h = 2800,2400 - backgroundColor = (230,230,234,255) - boardOutlineColor = (0,0,0) - pieceOutlineColor = (244,244,248) - emptyOutlineColor = (0,0,0) - player1Color = (254,74,73) - player2Color = (254,215,102) - boardColor = (42,183,202) - placeSize = 300 - white = (255,255,255,160) - winBarColor = (250,250,250,255) + imageSize = (self.WIDTH, self.HEIGHT+self.BOTTOMBORDER) + background = Image.new("RGB", imageSize, self.BACKGROUNDCOLOR) + d = ImageDraw.Draw(background, "RGBA") - fnt = ImageFont.truetype('resources/fonts/futura-bold.ttf', exampleCircles) + self._drawBoard(d) - boardSize = [w-(2*(border+gridBorder)),h-(2*(border+gridBorder))] - placeGridSize = [math.floor(boardSize[0]/7),math.floor(boardSize[1]/6)] - pieceStartx = (border+gridBorder)+math.floor(placeGridSize[0]/2)-math.floor(placeSize/2) - pieceStarty = (border+gridBorder)+math.floor(placeGridSize[1]/2)-math.floor(placeSize/2) + self._drawPieces(d, board) + if game["winner"] != 0: + self._drawWin(background, game) + + self._drawFooter(d, game) + + background.save(f"resources/games/connect4Boards/board{channel}.png") + + def _drawBoard(self, d: ImageDraw): + # This whole part was the easiest way to make a rectangle with + # rounded corners and an outline + drawParams = { + "fill": self.BOARDCOLOR, + "outline": self.BOARDOUTLINECOLOR, + "width": self.BOARDOUTLINESIZE + } + + # - Corners -: + cornerPositions = [ + [ + ( + self.BORDER, + self.BORDER + ), ( + self.BORDER+self.CORNERSIZE, + self.BORDER+self.CORNERSIZE + ) + ], [ + ( + self.WIDTH-(self.BORDER+self.CORNERSIZE), + self.HEIGHT-(self.BORDER+self.CORNERSIZE) + ), ( + self.WIDTH-self.BORDER, + self.HEIGHT-self.BORDER + ) + ], [ + ( + self.BORDER, + self.HEIGHT-(self.BORDER+self.CORNERSIZE) + ), ( + self.BORDER+self.CORNERSIZE, + self.HEIGHT-self.BORDER + ) + ], [ + ( + self.WIDTH-(self.BORDER+self.CORNERSIZE), + self.BORDER + ), ( + self.WIDTH-self.BORDER, + self.BORDER+self.CORNERSIZE + ) + ] + ] + + d.ellipse(cornerPositions[0], **drawParams) + d.ellipse(cornerPositions[1], **drawParams) + d.ellipse(cornerPositions[2], **drawParams) + d.ellipse(cornerPositions[3], **drawParams) + + # - Rectangle -: + rectanglePositions = [ + [ + ( + self.BORDER+(self.CORNERSIZE//2), + self.BORDER + ), ( + self.WIDTH-(self.BORDER+(self.CORNERSIZE//2)), + self.HEIGHT-self.BORDER + ) + ], [ + ( + self.BORDER, + self.BORDER+(self.CORNERSIZE//2) + ), ( + self.WIDTH-self.BORDER, + self.HEIGHT-(self.BORDER+(self.CORNERSIZE//2)) + ) + ] + ] + d.rectangle(rectanglePositions[0], **drawParams) + d.rectangle(rectanglePositions[1], **drawParams) + + # - Removing outline on the inside -: + cleanUpPositions = [ + [ + ( + self.BORDER+(self.CORNERSIZE//2), + self.BORDER+(self.CORNERSIZE//2) + ), ( + self.WIDTH-(self.BORDER+(self.CORNERSIZE//2)), + self.HEIGHT-(self.BORDER+(self.CORNERSIZE//2)) + ) + ], [ + ( + self.BORDER+(self.CORNERSIZE/2), + self.BORDER+self.BOARDOUTLINESIZE + ), ( + self.WIDTH-(self.BORDER+(self.CORNERSIZE//2)), + self.HEIGHT-(self.BORDER+self.BOARDOUTLINESIZE) + ) + ], [ + ( + self.BORDER+self.BOARDOUTLINESIZE, + self.BORDER+(self.CORNERSIZE//2) + ), ( + self.WIDTH-(self.BORDER+self.BOARDOUTLINESIZE), + self.HEIGHT-(self.BORDER+(self.CORNERSIZE//2)) + ) + ] + ] + d.rectangle(cleanUpPositions[0], fill=self.BOARDCOLOR) + d.rectangle(cleanUpPositions[1], fill=self.BOARDCOLOR) + d.rectangle(cleanUpPositions[2], fill=self.BOARDCOLOR) + + def _drawPieces(self, d: ImageDraw, board: list): + for x, line in enumerate(board): + for y, piece in enumerate(line): + + if piece == 1: + pieceColor = self.PLAYER1COLOR + outlineWidth = self.PIECEOUTLINESIZE + outlineColor = self.PIECEOUTLINECOLOR + elif piece == 2: + pieceColor = self.PLAYER2COLOR + outlineWidth = self.PIECEOUTLINESIZE + outlineColor = self.PIECEOUTLINECOLOR + else: + pieceColor = self.BACKGROUNDCOLOR + outlineWidth = self.EMPTYOUTLINESIZE + outlineColor = self.EMPTYOUTLINECOLOR + + startX = self.PIECESTARTX + self.PIECEGRIDSIZE[0]*y + startY = self.PIECESTARTY + self.PIECEGRIDSIZE[1]*x + + position = [ + (startX, startY), + (startX+self.PIECESIZE, startY+self.PIECESIZE) + ] + + pieceParams = { + "fill": pieceColor, + "outline": outlineColor, + "width": outlineWidth + } + d.ellipse(position, **pieceParams) + + def _drawWin(self, background: Image, game: dict): + """ + Draw the bar that shows the winning pieces. + + *Parameters* + ------------ + background: Image + The image of the board. + game: dict + The game data. + """ + coordinates = game["win coordinates"] + start = self.BORDER + self.GRIDBORDER + startX = start + self.PIECEGRIDSIZE[0]*coordinates[1] + startY = start + self.PIECEGRIDSIZE[1]*coordinates[0] + a = (self.PIECEGRIDSIZE[0]*4-self.GRIDBORDER-self.BORDER)**2 + b = (self.PIECEGRIDSIZE[1]*4-self.GRIDBORDER-self.BORDER)**2 + diagonalLength = (math.sqrt(a+b))/self.PIECEGRIDSIZE[0] + sizeRatio = self.PIECEGRIDSIZE[1]/self.PIECEGRIDSIZE[0] + diagonalAngle = math.degrees(math.atan(sizeRatio)) + + if game["win direction"] == "h": + imageSize = (self.PIECEGRIDSIZE[0]*4, self.PIECEGRIDSIZE[1]) + winBar = Image.new("RGBA", imageSize, (0, 0, 0, 0)) + winD = ImageDraw.Draw(winBar) + drawPositions = [ + [ + (0, 0), + (self.PIECEGRIDSIZE[0], self.PIECEGRIDSIZE[1]) + ], [ + ((self.PIECEGRIDSIZE[0]*3), 0), + (self.PIECEGRIDSIZE[0]*4, self.PIECEGRIDSIZE[1]) + ], [ + (int(self.PIECEGRIDSIZE[0]*0.5), 0), + (int(self.PIECEGRIDSIZE[0]*3.5), self.PIECEGRIDSIZE[1]) + ] + ] + winD.ellipse(drawPositions[0], fill=self.WINBARWHITE) + winD.ellipse(drawPositions[1], fill=self.WINBARWHITE) + winD.rectangle(drawPositions[2], fill=self.WINBARWHITE) + + elif game["win direction"] == "v": + imageSize = (self.PIECEGRIDSIZE[0], self.PIECEGRIDSIZE[1]*4) + winBar = Image.new("RGBA", imageSize, (0, 0, 0, 0)) + winD = ImageDraw.Draw(winBar) + drawPositions = [ + [ + (0, 0), + (self.PIECEGRIDSIZE[0], self.PIECEGRIDSIZE[1]) + ], [ + (0, (self.PIECEGRIDSIZE[1]*3)), + (self.PIECEGRIDSIZE[0], self.PIECEGRIDSIZE[1]*4) + ], [ + (0, (int(self.PIECEGRIDSIZE[1]*0.5))), + (self.PIECEGRIDSIZE[0], int(self.PIECEGRIDSIZE[1]*3.5)) + ] + ] + winD.ellipse(drawPositions[0], fill=self.WINBARWHITE) + winD.ellipse(drawPositions[1], fill=self.WINBARWHITE) + winD.rectangle(drawPositions[2], fill=self.WINBARWHITE) + + elif game["win direction"] == "r": + imageWidth = int(self.PIECEGRIDSIZE[0]*diagonalLength) + imageSize = (imageWidth, self.PIECEGRIDSIZE[1]) + winBar = Image.new("RGBA", imageSize, (0, 0, 0, 0)) + winD = ImageDraw.Draw(winBar) + drawPositions = [ + [ + ( + 0, + 0 + ), ( + self.PIECEGRIDSIZE[0], + self.PIECEGRIDSIZE[1] + ) + ], [ + ( + (self.PIECEGRIDSIZE[0]*(diagonalLength-1)), + 0 + ), ( + self.PIECEGRIDSIZE[0]*diagonalLength, + self.PIECEGRIDSIZE[1] + ) + ], [ + ( + int(self.PIECEGRIDSIZE[0]*0.5), + 0 + ), ( + int(self.PIECEGRIDSIZE[0]*(diagonalLength-0.5)), + self.PIECEGRIDSIZE[1] + ) + ] + ] + winD.ellipse(drawPositions[0], fill=self.WINBARWHITE) + winD.ellipse(drawPositions[1], fill=self.WINBARWHITE) + winD.rectangle(drawPositions[2], fill=self.WINBARWHITE) + winBar = winBar.rotate(-diagonalAngle, expand=1) + startX -= 90 + startY -= 100 + + elif game["win direction"] == "l": + imageWidth = int(self.PIECEGRIDSIZE[0]*diagonalLength) + imageSize = (imageWidth, self.PIECEGRIDSIZE[1]) + winBar = Image.new("RGBA", imageSize, (0, 0, 0, 0)) + winD = ImageDraw.Draw(winBar) + drawPositions = [ + [ + (0, 0), + (self.PIECEGRIDSIZE[0], self.PIECEGRIDSIZE[1]) + ], [ + ( + (self.PIECEGRIDSIZE[0]*(diagonalLength-1)), + 0 + ), ( + self.PIECEGRIDSIZE[0]*diagonalLength, + self.PIECEGRIDSIZE[1] + ) + ], [ + ( + int(self.PIECEGRIDSIZE[0]*0.5), + 0 + ), ( + int(self.PIECEGRIDSIZE[0]*(diagonalLength-0.5)), + self.PIECEGRIDSIZE[1] + ) + ] + ] + winD.ellipse(drawPositions[0], fill=self.WINBARWHITE) + winD.ellipse(drawPositions[1], fill=self.WINBARWHITE) + winD.rectangle(drawPositions[2], fill=self.WINBARWHITE) + winBar = winBar.rotate(diagonalAngle, expand=1) + startX -= self.PIECEGRIDSIZE[0]*3 + 90 + startY -= self.GRIDBORDER + 60 + + mask = winBar.copy() + + winBarImage = Image.new("RGBA", mask.size, color=self.WINBARCOLOR) + background.paste(winBarImage, (startX, startY), mask) + + def _drawFooter(self, d: ImageDraw, game: dict): if game["players"][0] == "Gwendolyn": player1 = "Gwendolyn" else: @@ -477,107 +1030,39 @@ class drawConnectFour(): else: player2 = self.bot.databaseFuncs.getName(game["players"][1]) + exampleHeight = self.HEIGHT - self.BORDER + exampleHeight += (self.BOTTOMBORDER+self.BORDER)//2 - self.TEXTSIZE//2 + textWidth = self.FONT.getsize(player2)[0] + exampleRPadding = self.BORDER+self.TEXTSIZE+textWidth+self.TEXTPADDING + circlePositions = [ + [ + ( + self.BORDER, + exampleHeight + ), ( + self.BORDER+self.TEXTSIZE, + exampleHeight+self.TEXTSIZE + ) + ], [ + ( + self.WIDTH-exampleRPadding, + exampleHeight + ), ( + self.WIDTH-self.BORDER-textWidth-self.TEXTPADDING, + exampleHeight+self.TEXTSIZE + ) + ] + ] + circleParams = { + "outline": self.BOARDOUTLINECOLOR, + "width": 3 + } + d.ellipse(circlePositions[0], fill=self.PLAYER1COLOR, **circleParams) + d.ellipse(circlePositions[1], fill=self.PLAYER2COLOR, **circleParams) - background = Image.new("RGB", (w,h+bottomBorder),backgroundColor) - d = ImageDraw.Draw(background,"RGBA") - - # This whole part was the easiest way to make a rectangle with rounded corners and an outline - # - Corners: - d.ellipse([(border,border),(border+cornerSize,border+cornerSize)],fill=boardColor,outline=boardOutlineColor,width=boardOutlineSize) - d.ellipse([(w-(border+cornerSize),h-(border+cornerSize)),(w-border,h-border)],fill=boardColor,outline=boardOutlineColor,width=boardOutlineSize) - d.ellipse([(border,h-(border+cornerSize)),(border+cornerSize,h-border)],fill=boardColor,outline=boardOutlineColor,width=boardOutlineSize) - d.ellipse([(w-(border+cornerSize),border),(w-border,border+cornerSize)],fill=boardColor,outline=boardOutlineColor,width=boardOutlineSize) - # - Rectangle: - d.rectangle([(border+math.floor(cornerSize/2),border),(w-(border+math.floor(cornerSize/2)),h-border)],fill=boardColor,outline=boardOutlineColor,width=boardOutlineSize) - d.rectangle([(border,border+math.floor(cornerSize/2)),(w-border,h-(border+math.floor(cornerSize/2)))],fill=boardColor,outline=boardOutlineColor,width=boardOutlineSize) - # - Removing outline on the inside: - d.rectangle([(border+math.floor(cornerSize/2),border+math.floor(cornerSize/2)),(w-(border+math.floor(cornerSize/2)),h-(border+math.floor(cornerSize/2)))],fill=boardColor) - d.rectangle([(border+math.floor(cornerSize/2),border+boardOutlineSize),(w-(border+math.floor(cornerSize/2)),h-(border+boardOutlineSize))],fill=boardColor) - d.rectangle([(border+boardOutlineSize,border+math.floor(cornerSize/2)),(w-(border+boardOutlineSize),h-(border+math.floor(cornerSize/2)))],fill=boardColor) - - for x, line in enumerate(board): - for y, piece in enumerate(line): - - if piece == 1: - pieceColor = player1Color - outlineWidth = pieceOutlineSize - outlineColor = pieceOutlineColor - elif piece == 2: - pieceColor = player2Color - outlineWidth = pieceOutlineSize - outlineColor = pieceOutlineColor - else: - pieceColor = backgroundColor - outlineWidth = emptyOutlineSize - outlineColor = emptyOutlineColor - - startx = pieceStartx + placeGridSize[0]*y - starty = pieceStarty + placeGridSize[1]*x - - d.ellipse([(startx,starty),(startx+placeSize,starty+placeSize)],fill=pieceColor,outline=outlineColor,width=outlineWidth) - - if game["winner"] != 0: - coordinates = game["win coordinates"] - startx = border + placeGridSize[0]*coordinates[1] + gridBorder - starty = border + placeGridSize[1]*coordinates[0] + gridBorder - a = (placeGridSize[0]*4-gridBorder-border)**2 - b = (placeGridSize[1]*4-gridBorder-border)**2 - diagonalLength = (math.sqrt(a+b))/placeGridSize[0] - diagonalAngle = math.degrees(math.atan(placeGridSize[1]/placeGridSize[0])) - - if game["win direction"] == "h": - winBar = Image.new("RGBA",(placeGridSize[0]*4,placeGridSize[1]),(0,0,0,0)) - winD = ImageDraw.Draw(winBar) - winD.ellipse([(0,0),(placeGridSize[0],placeGridSize[1])],fill=white) - winD.ellipse([((placeGridSize[0]*3),0),(placeGridSize[0]*4,placeGridSize[1])],fill=white) - winD.rectangle([(int(placeGridSize[0]*0.5),0),(int(placeGridSize[0]*3.5),placeGridSize[1])],fill=white) - - elif game["win direction"] == "v": - winBar = Image.new("RGBA",(placeGridSize[0],placeGridSize[1]*4),(0,0,0,0)) - winD = ImageDraw.Draw(winBar) - winD.ellipse([(0,0),(placeGridSize[0],placeGridSize[1])],fill=white) - winD.ellipse([(0,(placeGridSize[1]*3)),(placeGridSize[0],placeGridSize[1]*4)],fill=white) - winD.rectangle([0,(int(placeGridSize[1]*0.5)),(placeGridSize[0],int(placeGridSize[1]*3.5))],fill=white) - - elif game["win direction"] == "r": - winBar = Image.new("RGBA",(int(placeGridSize[0]*diagonalLength),placeGridSize[1]),(0,0,0,0)) - winD = ImageDraw.Draw(winBar) - winD.ellipse([(0,0),(placeGridSize[0],placeGridSize[1])],fill=white) - winD.ellipse([((placeGridSize[0]*(diagonalLength-1)),0),(placeGridSize[0]*diagonalLength,placeGridSize[1])],fill=white) - winD.rectangle([(int(placeGridSize[0]*0.5),0),(int(placeGridSize[0]*(diagonalLength-0.5)),placeGridSize[1])],fill=white) - winBar = winBar.rotate(-diagonalAngle,expand=1) - startx -= 90 - starty -= 100 - - elif game["win direction"] == "l": - winBar = Image.new("RGBA",(int(placeGridSize[0]*diagonalLength),placeGridSize[1]),(0,0,0,0)) - winD = ImageDraw.Draw(winBar) - winD.ellipse([(0,0),(placeGridSize[0],placeGridSize[1])],fill=white) - winD.ellipse([((placeGridSize[0]*(diagonalLength-1)),0),(placeGridSize[0]*diagonalLength,placeGridSize[1])],fill=white) - winD.rectangle([(int(placeGridSize[0]*0.5),0),(int(placeGridSize[0]*(diagonalLength-0.5)),placeGridSize[1])],fill=white) - winBar = winBar.rotate(diagonalAngle,expand=1) - startx -= placeGridSize[0]*3 + 90 - starty -= gridBorder + 60 - - - mask = winBar.copy()#.convert("L") - #mask.putalpha(128) - #mask.save("test.png") - - winBarImage = Image.new("RGBA",mask.size,color=winBarColor) - background.paste(winBarImage,(startx,starty),mask) - - # Bottom - textPadding = 20 - - exampleHeight = h - border + int((bottomBorder+border)/2) - int(exampleCircles/2) - d.ellipse([(border,exampleHeight),(border+exampleCircles),(exampleHeight+exampleCircles)],fill=player1Color,outline=boardOutlineColor,width=3) - d.text((border+exampleCircles+textPadding,exampleHeight),player1,font=fnt,fill=(0,0,0)) - - textWidth = fnt.getsize(player2)[0] - d.ellipse([(w-border-exampleCircles-textWidth-textPadding,exampleHeight),(w-border-textWidth-textPadding),(exampleHeight+exampleCircles)],fill=player2Color,outline=boardOutlineColor,width=3) - d.text((w-border-textWidth,exampleHeight),player2,font=fnt,fill=(0,0,0)) - - - background.save("resources/games/connect4Boards/board"+channel+".png") - + textPositions = [ + (self.BORDER+self.TEXTSIZE+self.TEXTPADDING, exampleHeight), + (self.WIDTH-self.BORDER-textWidth, exampleHeight) + ] + d.text(textPositions[0], player1, font=self.FONT, fill=(0, 0, 0)) + d.text(textPositions[1], player2, font=self.FONT, fill=(0, 0, 0)) diff --git a/resources/longStrings.json b/resources/longStrings.json index 188fc7f..c0f1de8 100644 --- a/resources/longStrings.json +++ b/resources/longStrings.json @@ -12,5 +12,7 @@ "Stock value": "The current {} stock is valued at **{}** GwendoBucks", "Stock parameters": "You must give both a stock name and an amount of GwendoBucks you wish to spend.", "Trivia going on": "There's already a trivia question going on. Try again in like, a minute", - "Trivia time up": "Time's up! The answer was \"*{}) {}*\". Anyone who answered that has gotten 1 GwendoBuck" + "Trivia time up": "Time's up! The answer was \"*{}) {}*\". Anyone who answered that has gotten 1 GwendoBuck", + "Connect 4 going on": "There's already a connect 4 game going on in this channel", + "Connect 4 placed": "{} placed a piece in column {}. It's now {}'s turn" } \ No newline at end of file From e0c38f0a0ad7b1d4d4aa85c178c0d5b5df3fe766 Mon Sep 17 00:00:00 2001 From: NikolajDanger Date: Sun, 18 Apr 2021 21:15:41 +0200 Subject: [PATCH 18/23] :sparkles: Hangman --- funcs/games/hangman.py | 261 +++++++++++++++++++++++-------------- resources/longStrings.json | 5 +- 2 files changed, 169 insertions(+), 97 deletions(-) diff --git a/funcs/games/hangman.py b/funcs/games/hangman.py index f330000..8da9592 100644 --- a/funcs/games/hangman.py +++ b/funcs/games/hangman.py @@ -1,32 +1,59 @@ -import json, urllib, datetime, string, discord -import math, random +import json +import requests +import datetime +import string +import discord +import math +import random +from discord_slash.context import SlashContext from PIL import ImageDraw, Image, ImageFont + class Hangman(): - def __init__(self,bot): + def __init__(self, bot): self.bot = bot self.draw = DrawHangman(bot) - self.APIURL = "https://api.wordnik.com/v4/words.json/randomWords?hasDictionaryDef=true&minCorpusCount=5000&maxCorpusCount=-1&minDictionaryCount=1&maxDictionaryCount=-1&minLength=3&maxLength=11&limit=1&api_key=" + self.APIURL = "https://api.wordnik.com/v4/words.json/randomWords?" + apiKey = self.bot.credentials.wordnikKey + self.APIPARAMS = { + "hasDictionaryDef": True, + "minCorpusCount": 5000, + "maxCorpusCount": -1, + "minDictionaryCount": 1, + "maxDictionaryCount": -1, + "minLength": 3, + "maxLength": 11, + "limit": 1, + "api_key": apiKey + } - async def start(self, ctx): + async def start(self, ctx: SlashContext): await self.bot.defer(ctx) channel = str(ctx.channel_id) user = f"#{ctx.author.id}" - game = self.bot.database["hangman games"].find_one({"_id":channel}) + 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 + if game is None: word = "-" while "-" in word or "." in word: - with urllib.request.urlopen(self.APIURL+apiKey) as p: - word = list(json.load(p)[0]["word"].upper()) + response = requests.get(self.APIURL, params=self.APIPARAMS) + word = list(response.json()[0]["word"].upper()) + self.bot.log("Found the word \""+"".join(word)+"\"") guessed = [False] * len(word) - gameID = datetime.datetime.now().strftime('%Y%m%d%H%M%S') - newGame = {"_id":channel,"player" : user,"guessed letters" : [],"word" : word,"game ID" : gameID,"misses" : 0,"guessed" : guessed} + gameID = datetime.datetime.now().strftime("%Y%m%d%H%M%S") + newGame = { + "_id": channel, + "player": user, + "guessed letters": [], + "word": word, + "game ID": gameID, + "misses": 0, + "guessed": guessed + } self.bot.database["hangman games"].insert_one(newGame) remainingLetters = list(string.ascii_uppercase) @@ -38,17 +65,21 @@ class Hangman(): startedGame = True else: logMessage = "There was already a game going on" - sendMessage = "There's already a Hangman game going on in the channel" + sendMessage = self.bot.longStrings["Hangman going on"] self.bot.log(logMessage) await ctx.send(sendMessage) if startedGame: - filePath = f"resources/games/hangmanBoards/hangmanBoard{channel}.png" - newImage = await ctx.channel.send(file = discord.File(filePath)) + boardsPath = "resources/games/hangmanBoards/" + filePath = f"{boardsPath}hangmanBoard{channel}.png" + newImage = await ctx.channel.send(file=discord.File(filePath)) - blankMessage = await ctx.channel.send("_ _") - reactionMessages = {newImage : remainingLetters[:15], blankMessage : remainingLetters[15:]} + blankMessage = await ctx.channel.send("_ _") + reactionMessages = { + newImage: remainingLetters[:15], + blankMessage: remainingLetters[15:] + } oldMessages = f"{newImage.id}\n{blankMessage.id}" @@ -60,7 +91,7 @@ class Hangman(): emoji = chr(ord(letter)+127397) await message.add_reaction(emoji) - async def stop(self, ctx): + async def stop(self, ctx: SlashContext): channel = str(ctx.channel.id) game = self.bot.database["hangman games"].find_one({"_id": channel}) @@ -69,7 +100,7 @@ class Hangman(): 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}) + self.bot.database["hangman games"].delete_one({"_id": channel}) with open(f"resources/games/oldImages/hangman{channel}", "r") as f: messages = f.read().splitlines() @@ -81,11 +112,12 @@ class Hangman(): await ctx.send("Game stopped") - async def guess(self, message, user, guess): + async def guess(self, message: discord.Message, user: str, guess: str): channel = str(message.channel.id) - game = self.bot.database["hangman games"].find_one({"_id":channel}) + hangmanGames = self.bot.database["hangman games"] + game = hangmanGames.find_one({"_id": channel}) - gameExists = (game != None) + gameExists = (game is not None) singleLetter = (len(guess) == 1 and guess.isalpha()) newGuess = (guess not in game["guessed letters"]) validGuess = (gameExists and singleLetter and newGuess) @@ -97,35 +129,40 @@ class Hangman(): 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}}) + updater = {"$set": {f"guessed.{x}": True}} + hangmanGames.update_one({"_id": channel}, updater) if correctGuess == 0: - self.bot.database["hangman games"].update_one({"_id":channel},{"$inc":{"misses":1}}) + updater = {"$inc": {"misses": 1}} + hangmanGames.update_one({"_id": channel}, updater) - self.bot.database["hangman games"].update_one({"_id":channel},{"$push":{"guessed letters":guess}}) + updater = {"$push": {"guessed letters": guess}} + hangmanGames.update_one({"_id": channel}, updater) remainingLetters = list(string.ascii_uppercase) - game = self.bot.database["hangman games"].find_one({"_id":channel}) + game = hangmanGames.find_one({"_id": channel}) for letter in game["guessed letters"]: remainingLetters.remove(letter) if correctGuess == 1: - sendMessage = f"Guessed {guess}. There was 1 {guess} in the word." + sendMessage = "Guessed {}. There was 1 {} in the word." + sendMessage = sendMessage.format(guess, guess) else: - sendMessage = f"Guessed {guess}. There were {correctGuess} {guess}s in the word." + sendMessage = "Guessed {}. There were {} {}s in the word." + sendMessage = sendMessage.format(guess, correctGuess, guess) 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." + hangmanGames.delete_one({"_id": channel}) + sendMessage += self.bot.longStrings["Hangman lost 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" + elif all(game["guessed"]): + hangmanGames.delete_one({"_id": channel}) + self.bot.money.addMoney(user, 15) + sendMessage += self.bot.longStrings["Hangman guessed word"] remainingLetters = [] await message.channel.send(sendMessage) @@ -138,23 +175,28 @@ class Hangman(): 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)) + boardsPath = "resources/games/hangmanBoards/" + filePath = f"{boardsPath}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:]} + blankMessage = await message.channel.send("_ _") + reactionMessages = { + newImage: remainingLetters[:15], + blankMessage: remainingLetters[15:] + } else: blankMessage = "" - reactionMessages = {newImage : remainingLetters} + 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: + oldImagePath = f"resources/games/oldImages/hangman{channel}" + with open(oldImagePath, "w") as f: f.write(oldMessages) for message, letters in reactionMessages.items(): @@ -162,58 +204,85 @@ class Hangman(): emoji = chr(ord(letter)+127397) await message.add_reaction(emoji) + class DrawHangman(): - def __init__(self,bot): + def __init__(self, bot): self.bot = bot self.CIRCLESIZE = 120 self.LINEWIDTH = 12 - self.BODYSIZE =210 + self.BODYSIZE = 210 self.LIMBSIZE = 60 self.ARMPOSITION = 60 - self.MANX = (self.LIMBSIZE*2)+self.LINEWIDTH*4 - self.MANY = (self.CIRCLESIZE+self.BODYSIZE+self.LIMBSIZE)+self.LINEWIDTH*4 + MANPADDING = self.LINEWIDTH*4 + self.MANX = (self.LIMBSIZE*2)+MANPADDING + self.MANY = (self.CIRCLESIZE+self.BODYSIZE+self.LIMBSIZE)+MANPADDING self.LETTERLINELENGTH = 90 self.LETTERLINEDISTANCE = 30 - self.GALLOWX, self.GALLOWY = 360,600 + self.GALLOWX, self.GALLOWY = 360, 600 self.GOLDENRATIO = 1-(1 / ((1 + 5 ** 0.5) / 2)) LETTERSIZE = 75 TEXTSIZE = 70 - self.FONT = ImageFont.truetype('resources/fonts/comic-sans-bold.ttf', LETTERSIZE) - self.SMALLFONT = ImageFont.truetype('resources/fonts/comic-sans-bold.ttf', TEXTSIZE) - def calcDeviance(self,preDev,preDevAcc,posChange,maxmin,maxAcceleration): - devAcc = preDevAcc + random.uniform(-posChange,posChange) - if devAcc > maxmin * maxAcceleration: devAcc = maxmin * maxAcceleration - elif devAcc < -maxmin * maxAcceleration: devAcc = -maxmin * maxAcceleration + FONTPATH = "resources/fonts/comic-sans-bold.ttf" + self.FONT = ImageFont.truetype(FONTPATH, LETTERSIZE) + self.SMALLFONT = ImageFont.truetype(FONTPATH, TEXTSIZE) - dev = preDev + devAcc - if dev > maxmin: dev = maxmin - elif dev < -maxmin: dev = -maxmin - return dev, devAcc + def calcDeviance(self, preDeviance, preDevianceAccuracy, positionChange, + maxmin, maxAcceleration): + randomDeviance = random.uniform(-positionChange, positionChange) + devianceAccuracy = preDevianceAccuracy + randomDeviance + if devianceAccuracy > maxmin * maxAcceleration: + devianceAccuracy = maxmin * maxAcceleration + elif devianceAccuracy < -maxmin * maxAcceleration: + devianceAccuracy = -maxmin * maxAcceleration + + deviance = preDeviance + devianceAccuracy + if deviance > maxmin: + deviance = maxmin + elif deviance < -maxmin: + deviance = -maxmin + return deviance, devianceAccuracy def badCircle(self): - background = Image.new("RGBA",(self.CIRCLESIZE+(self.LINEWIDTH*3),self.CIRCLESIZE+(self.LINEWIDTH*3)),color=(0,0,0,0)) + circlePadding = (self.LINEWIDTH*3) + imageWidth = self.CIRCLESIZE+circlePadding + imageSize = (imageWidth, imageWidth) + background = Image.new("RGBA", imageSize, color=(0, 0, 0, 0)) - d = ImageDraw.Draw(background,"RGBA") + d = ImageDraw.Draw(background, "RGBA") middle = (self.CIRCLESIZE+(self.LINEWIDTH*3))/2 - devx = 0 - devy = 0 - devAccx = 0 - devAccy = 0 - start = random.randint(-100,-80) - degreesAmount = 360 + random.randint(-10,30) + devianceX = 0 + devianceY = 0 + devianceAccuracyX = 0 + devianceAccuracyY = 0 + start = random.randint(-100, -80) + degreesAmount = 360 + random.randint(-10, 30) for degree in range(degreesAmount): - devx, devAccx = self.calcDeviance(devx,devAccx,self.LINEWIDTH/100,self.LINEWIDTH,0.03) - devy, devAccy = self.calcDeviance(devy,devAccy,self.LINEWIDTH/100,self.LINEWIDTH,0.03) + devianceXParams = [ + devianceX, + devianceAccuracyX, + self.LINEWIDTH/100, + self.LINEWIDTH, + 0.03 + ] + devianceYParams = [ + devianceY, + devianceAccuracyY, + self.LINEWIDTH/100, + self.LINEWIDTH, + 0.03 + ] + devianceX, devianceAccuracyX = self.calcDeviance(*devianceXParams) + devianceY, devianceAccuracyY = self.calcDeviance(*devianceYParams) - x = middle + (math.cos(math.radians(degree+start)) * (self.CIRCLESIZE/2)) - (self.LINEWIDTH/2) + devx - y = middle + (math.sin(math.radians(degree+start)) * (self.CIRCLESIZE/2)) - (self.LINEWIDTH/2) + devy + x = middle + (math.cos(math.radians(degree+start)) * (self.CIRCLESIZE/2)) - (self.LINEWIDTH/2) + devianceX + y = middle + (math.sin(math.radians(degree+start)) * (self.CIRCLESIZE/2)) - (self.LINEWIDTH/2) + devianceY d.ellipse([(x,y),(x+self.LINEWIDTH,y+self.LINEWIDTH)],fill=(0,0,0,255)) @@ -227,21 +296,21 @@ class DrawHangman(): background = Image.new("RGBA",(w,h),color=(0,0,0,0)) d = ImageDraw.Draw(background,"RGBA") - devx = random.randint(-int(self.LINEWIDTH/3),int(self.LINEWIDTH/3)) - devy = 0 - devAccx = 0 - devAccy = 0 + devianceX = random.randint(-int(self.LINEWIDTH/3),int(self.LINEWIDTH/3)) + devianceY = 0 + devianceAccuracyX = 0 + devianceAccuracyY = 0 for pixel in range(length): - devx, devAccx = self.calcDeviance(devx,devAccx,self.LINEWIDTH/1000,self.LINEWIDTH,0.004) - devy, devAccy = self.calcDeviance(devy,devAccy,self.LINEWIDTH/1000,self.LINEWIDTH,0.004) + devianceX, devianceAccuracyX = self.calcDeviance(devianceX,devianceAccuracyX,self.LINEWIDTH/1000,self.LINEWIDTH,0.004) + devianceY, devianceAccuracyY = self.calcDeviance(devianceY,devianceAccuracyY,self.LINEWIDTH/1000,self.LINEWIDTH,0.004) if rotated: - x = self.LINEWIDTH + pixel + devx - y = self.LINEWIDTH + devy + x = self.LINEWIDTH + pixel + devianceX + y = self.LINEWIDTH + devianceY else: - x = self.LINEWIDTH + devx - y = self.LINEWIDTH + pixel + devy + x = self.LINEWIDTH + devianceX + y = self.LINEWIDTH + pixel + devianceY d.ellipse([(x,y),(x+self.LINEWIDTH,y+self.LINEWIDTH)],fill=(0,0,0,255)) @@ -265,33 +334,33 @@ class DrawHangman(): if limb == "ra": limbDrawing = self.badLine(self.LIMBSIZE,True) rotation = random.randint(-45,45) - xpos = int((self.MANX-(self.LINEWIDTH*3))/2) + xPosition = int((self.MANX-(self.LINEWIDTH*3))/2) rotationCompensation = min(-int(math.sin(math.radians(rotation))*(self.LIMBSIZE+(self.LINEWIDTH*3))),0) - ypos = self.CIRCLESIZE+self.ARMPOSITION + rotationCompensation + yPosition = self.CIRCLESIZE+self.ARMPOSITION + rotationCompensation limbDrawing = limbDrawing.rotate(rotation,expand=1) - background.paste(limbDrawing,(xpos,ypos),limbDrawing) + background.paste(limbDrawing,(xPosition,yPosition),limbDrawing) elif limb == "la": limbDrawing = self.badLine(self.LIMBSIZE,True) rotation = random.randint(-45,45) - xpos = int((self.MANX-(self.LINEWIDTH*3))/2)-self.LIMBSIZE + xPosition = int((self.MANX-(self.LINEWIDTH*3))/2)-self.LIMBSIZE rotationCompensation = min(int(math.sin(math.radians(rotation))*(self.LIMBSIZE+(self.LINEWIDTH*3))),0) - ypos = self.CIRCLESIZE+self.ARMPOSITION + rotationCompensation + yPosition = self.CIRCLESIZE+self.ARMPOSITION + rotationCompensation limbDrawing = limbDrawing.rotate(rotation,expand=1) - background.paste(limbDrawing,(xpos,ypos),limbDrawing) + background.paste(limbDrawing,(xPosition,yPosition),limbDrawing) elif limb == "rl": limbDrawing = self.badLine(self.LIMBSIZE,True) rotation = random.randint(-15,15) - xpos = int((self.MANX-(self.LINEWIDTH*3))/2)-self.LINEWIDTH - ypos = self.CIRCLESIZE+self.BODYSIZE-self.LINEWIDTH + xPosition = int((self.MANX-(self.LINEWIDTH*3))/2)-self.LINEWIDTH + yPosition = self.CIRCLESIZE+self.BODYSIZE-self.LINEWIDTH limbDrawing = limbDrawing.rotate(rotation-45,expand=1) - background.paste(limbDrawing,(xpos,ypos),limbDrawing) + background.paste(limbDrawing,(xPosition,yPosition),limbDrawing) elif limb == "ll": limbDrawing = self.badLine(self.LIMBSIZE,True) rotation = random.randint(-15,15) limbDrawing = limbDrawing.rotate(rotation+45,expand=1) - xpos = int((self.MANX-(self.LINEWIDTH*3))/2)-limbDrawing.size[0]+self.LINEWIDTH*3 - ypos = self.CIRCLESIZE+self.BODYSIZE - background.paste(limbDrawing,(xpos,ypos),limbDrawing) + xPosition = int((self.MANX-(self.LINEWIDTH*3))/2)-limbDrawing.size[0]+self.LINEWIDTH*3 + yPosition = self.CIRCLESIZE+self.BODYSIZE + background.paste(limbDrawing,(xPosition,yPosition),limbDrawing) return background @@ -329,13 +398,13 @@ class DrawHangman(): if guessed[x]: letterDrawing = self.badText(letter,True) letterWidth = self.FONT.getsize(letter)[0] - letterx = int(x*(self.LETTERLINELENGTH+self.LETTERLINEDISTANCE)-(letterWidth/2)+(self.LETTERLINELENGTH*0.5)+(self.LINEWIDTH*2)) - letterLines.paste(letterDrawing,(letterx,0),letterDrawing) + letterX = int(x*(self.LETTERLINELENGTH+self.LETTERLINEDISTANCE)-(letterWidth/2)+(self.LETTERLINELENGTH*0.5)+(self.LINEWIDTH*2)) + letterLines.paste(letterDrawing,(letterX,0),letterDrawing) elif misses == 6: letterDrawing = self.badText(letter,True,(242,66,54)) letterWidth = self.FONT.getsize(letter)[0] - letterx = int(x*(self.LETTERLINELENGTH+self.LETTERLINEDISTANCE)-(letterWidth/2)+(self.LETTERLINELENGTH*0.5)+(self.LINEWIDTH*2)) - letterLines.paste(letterDrawing,(letterx,0),letterDrawing) + letterX = int(x*(self.LETTERLINELENGTH+self.LETTERLINEDISTANCE)-(letterWidth/2)+(self.LETTERLINELENGTH*0.5)+(self.LINEWIDTH*2)) + letterLines.paste(letterDrawing,(letterX,0),letterDrawing) return letterLines @@ -343,9 +412,9 @@ class DrawHangman(): shortestDist = math.inf x, y = newPosition for i, j in positions: - xdist = abs(i-x) - ydist = abs(j-y) - dist = math.sqrt(xdist**2+ydist**2) + xDistance = abs(i-x) + yDistance = abs(j-y) + dist = math.sqrt(xDistance**2+yDistance**2) if shortestDist > dist: shortestDist = dist return shortestDist diff --git a/resources/longStrings.json b/resources/longStrings.json index c0f1de8..b5d9c11 100644 --- a/resources/longStrings.json +++ b/resources/longStrings.json @@ -14,5 +14,8 @@ "Trivia going on": "There's already a trivia question going on. Try again in like, a minute", "Trivia time up": "Time's up! The answer was \"*{}) {}*\". Anyone who answered that has gotten 1 GwendoBuck", "Connect 4 going on": "There's already a connect 4 game going on in this channel", - "Connect 4 placed": "{} placed a piece in column {}. It's now {}'s turn" + "Connect 4 placed": "{} placed a piece in column {}. It's now {}'s turn", + "Hangman going on": "There's already a Hangman game going on in the channel", + "Hangman lost game": " You've guessed wrong six times and have lost the game.", + "Hangman guessed word": " You've guessed the word! Congratulations! Adding 15 GwendoBucks to your account" } \ No newline at end of file From 8236c38a3f18c09a3e90e1ae937be9529ae1a118 Mon Sep 17 00:00:00 2001 From: Nikolaj Danger Date: Wed, 21 Apr 2021 20:25:14 +0200 Subject: [PATCH 19/23] :Sparkles: Hangman --- funcs/games/hangman.py | 247 +++++++++++++++++++++++------------------ 1 file changed, 141 insertions(+), 106 deletions(-) diff --git a/funcs/games/hangman.py b/funcs/games/hangman.py index 8da9592..b00c727 100644 --- a/funcs/games/hangman.py +++ b/funcs/games/hangman.py @@ -281,29 +281,50 @@ class DrawHangman(): devianceX, devianceAccuracyX = self.calcDeviance(*devianceXParams) devianceY, devianceAccuracyY = self.calcDeviance(*devianceYParams) - x = middle + (math.cos(math.radians(degree+start)) * (self.CIRCLESIZE/2)) - (self.LINEWIDTH/2) + devianceX - y = middle + (math.sin(math.radians(degree+start)) * (self.CIRCLESIZE/2)) - (self.LINEWIDTH/2) + devianceY + radians = math.radians(degree+start) + circleX = (math.cos(radians) * (self.CIRCLESIZE/2)) + circleY = (math.sin(radians) * (self.CIRCLESIZE/2)) - d.ellipse([(x,y),(x+self.LINEWIDTH,y+self.LINEWIDTH)],fill=(0,0,0,255)) + x = middle + circleX - (self.LINEWIDTH/2) + devianceX + y = middle + circleY - (self.LINEWIDTH/2) + devianceY + + circlePosition = [(x, y), (x+self.LINEWIDTH, y+self.LINEWIDTH)] + d.ellipse(circlePosition, fill=(0, 0, 0, 255)) return background - def badLine(self, length, rotated = False): + def badLine(self, length: int, rotated: bool = False): if rotated: w, h = length+self.LINEWIDTH*3, self.LINEWIDTH*3 else: - w, h = self.LINEWIDTH*3,length+self.LINEWIDTH*3 - background = Image.new("RGBA",(w,h),color=(0,0,0,0)) + w, h = self.LINEWIDTH*3, length+self.LINEWIDTH*3 + background = Image.new("RGBA", (w, h), color=(0, 0, 0, 0)) - d = ImageDraw.Draw(background,"RGBA") - devianceX = random.randint(-int(self.LINEWIDTH/3),int(self.LINEWIDTH/3)) + d = ImageDraw.Draw(background, "RGBA") + + possibleDeviance = int(self.LINEWIDTH/3) + devianceX = random.randint(-possibleDeviance, possibleDeviance) devianceY = 0 devianceAccuracyX = 0 devianceAccuracyY = 0 for pixel in range(length): - devianceX, devianceAccuracyX = self.calcDeviance(devianceX,devianceAccuracyX,self.LINEWIDTH/1000,self.LINEWIDTH,0.004) - devianceY, devianceAccuracyY = self.calcDeviance(devianceY,devianceAccuracyY,self.LINEWIDTH/1000,self.LINEWIDTH,0.004) + devianceParamsX = [ + devianceX, + devianceAccuracyX, + self.LINEWIDTH/1000, + self.LINEWIDTH, + 0.004 + ] + devianceParamsY = [ + devianceY, + devianceAccuracyY, + self.LINEWIDTH/1000, + self.LINEWIDTH, + 0.004 + ] + devianceX, devianceAccuracyX = self.calcDeviance(*devianceParamsX) + devianceY, devianceAccuracyY = self.calcDeviance(*devianceParamsY) if rotated: x = self.LINEWIDTH + pixel + devianceX @@ -312,165 +333,179 @@ class DrawHangman(): x = self.LINEWIDTH + devianceX y = self.LINEWIDTH + pixel + devianceY - d.ellipse([(x,y),(x+self.LINEWIDTH,y+self.LINEWIDTH)],fill=(0,0,0,255)) + circlePosition = [(x, y), (x+self.LINEWIDTH, y+self.LINEWIDTH)] + d.ellipse(circlePosition, fill=(0, 0, 0, 255)) return background - def drawMan(self, misses): - background = Image.new("RGBA",(self.MANX,self.MANY),color=(0,0,0,0)) + def drawMan(self, misses: int): + manSize = (self.MANX, self.MANY) + background = Image.new("RGBA", manSize, color=(0, 0, 0, 0)) if misses >= 1: head = self.badCircle() - background.paste(head,(int((self.MANX-(self.CIRCLESIZE+(self.LINEWIDTH*3)))/2),0),head) + pasteX = (self.MANX-(self.CIRCLESIZE+(self.LINEWIDTH*3)))//2 + pastePosition = (pasteX, 0) + background.paste(head, pastePosition, head) if misses >= 2: body = self.badLine(self.BODYSIZE) - background.paste(body,(int((self.MANX-(self.LINEWIDTH*3))/2),self.CIRCLESIZE),body) + pasteX = (self.MANX-(self.LINEWIDTH*3))//2 + pastePosition = (pasteX, self.CIRCLESIZE) + background.paste(body, pastePosition, body) if misses >= 3: - limbs = random.sample(["rl","ll","ra","la"],min(misses-2,4)) - else: limbs = [] + limbs = random.sample(["rl", "ll", "ra", "la"], min(misses-2, 4)) + else: + limbs = [] for limb in limbs: - if limb == "ra": - limbDrawing = self.badLine(self.LIMBSIZE,True) - rotation = random.randint(-45,45) - xPosition = int((self.MANX-(self.LINEWIDTH*3))/2) - rotationCompensation = min(-int(math.sin(math.radians(rotation))*(self.LIMBSIZE+(self.LINEWIDTH*3))),0) - yPosition = self.CIRCLESIZE+self.ARMPOSITION + rotationCompensation - limbDrawing = limbDrawing.rotate(rotation,expand=1) - background.paste(limbDrawing,(xPosition,yPosition),limbDrawing) - elif limb == "la": - limbDrawing = self.badLine(self.LIMBSIZE,True) - rotation = random.randint(-45,45) - xPosition = int((self.MANX-(self.LINEWIDTH*3))/2)-self.LIMBSIZE - rotationCompensation = min(int(math.sin(math.radians(rotation))*(self.LIMBSIZE+(self.LINEWIDTH*3))),0) - yPosition = self.CIRCLESIZE+self.ARMPOSITION + rotationCompensation - limbDrawing = limbDrawing.rotate(rotation,expand=1) - background.paste(limbDrawing,(xPosition,yPosition),limbDrawing) - elif limb == "rl": - limbDrawing = self.badLine(self.LIMBSIZE,True) - rotation = random.randint(-15,15) - xPosition = int((self.MANX-(self.LINEWIDTH*3))/2)-self.LINEWIDTH - yPosition = self.CIRCLESIZE+self.BODYSIZE-self.LINEWIDTH - limbDrawing = limbDrawing.rotate(rotation-45,expand=1) - background.paste(limbDrawing,(xPosition,yPosition),limbDrawing) - elif limb == "ll": - limbDrawing = self.badLine(self.LIMBSIZE,True) - rotation = random.randint(-15,15) - limbDrawing = limbDrawing.rotate(rotation+45,expand=1) - xPosition = int((self.MANX-(self.LINEWIDTH*3))/2)-limbDrawing.size[0]+self.LINEWIDTH*3 + limbDrawing = self.badLine(self.LIMBSIZE, True) + xPosition = (self.MANX-(self.LINEWIDTH*3))//2 + + if limb[1] == "a": + rotation = random.randint(-45, 45) + shift = math.sin(math.radians(rotation)) + compensation = int(shift*(self.LIMBSIZE+(self.LINEWIDTH*3))) + limbDrawing = limbDrawing.rotate(rotation, expand=1) + yPosition = self.CIRCLESIZE + self.ARMPOSITION + compensation + if limb == "ra": + compensation = min(-compensation, 0) + else: + xPosition -= self.LIMBSIZE + compensation = min(compensation, 0) + else: + rotation = random.randint(-15, 15) yPosition = self.CIRCLESIZE+self.BODYSIZE - background.paste(limbDrawing,(xPosition,yPosition),limbDrawing) + if limb == "rl": + xPosition -= self.LINEWIDTH + limbDrawing = limbDrawing.rotate(rotation-45, expand=1) + else: + yPosition -= self.LINEWIDTH + xPosition += -limbDrawing.size[0]+self.LINEWIDTH*3 + limbDrawing = limbDrawing.rotate(rotation+45, expand=1) + + pastePosition = (xPosition, yPosition) + background.paste(limbDrawing, pastePosition, limbDrawing) return background - def badText(self, text, big, color=(0,0,0,255)): - if big: font = self.FONT - else: font = self.SMALLFONT + def badText(self, text: str, big: bool, color: tuple = (0, 0, 0, 255)): + if big: + font = self.FONT + else: + font = self.SMALLFONT w, h = font.getsize(text) - img = Image.new("RGBA",(w,h),color=(0,0,0,0)) - d = ImageDraw.Draw(img,"RGBA") + img = Image.new("RGBA", (w, h), color=(0, 0, 0, 0)) + d = ImageDraw.Draw(img, "RGBA") - d.text((0,0),text,font=font,fill=color) + d.text((0, 0), text, font=font, fill=color) return img def drawGallows(self): - background = Image.new("RGBA",(self.GALLOWX,self.GALLOWY),color=(0,0,0,0)) + gallowSize = (self.GALLOWX, self.GALLOWY) + background = Image.new("RGBA", gallowSize, color=(0, 0, 0, 0)) - bottomLine = self.badLine(int(self.GALLOWX*0.75),True) - background.paste(bottomLine,(int(self.GALLOWX*0.125),self.GALLOWY-(self.LINEWIDTH*4)),bottomLine) + bottomLine = self.badLine(int(self.GALLOWX * 0.75), True) + bottomLineX = int(self.GALLOWX * 0.125) + bottomLineY = self.GALLOWY-(self.LINEWIDTH*4) + pastePosition = (bottomLineX, bottomLineY) + background.paste(bottomLine, pastePosition, bottomLine) lineTwo = self.badLine(self.GALLOWY-self.LINEWIDTH*6) - background.paste(lineTwo,(int(self.GALLOWX*(0.75*self.GOLDENRATIO)),self.LINEWIDTH*2),lineTwo) + lineTwoX = int(self.GALLOWX*(0.75*self.GOLDENRATIO)) + lineTwoY = self.LINEWIDTH*2 + pastePosition = (lineTwoX, lineTwoY) + background.paste(lineTwo, pastePosition, lineTwo) - topLine = self.badLine(int(self.GALLOWY*0.30),True) - background.paste(topLine,(int(self.GALLOWX*(0.75*self.GOLDENRATIO))-self.LINEWIDTH,self.LINEWIDTH*3),topLine) + topLine = self.badLine(int(self.GALLOWY*0.30), True) + pasteX = int(self.GALLOWX*(0.75*self.GOLDENRATIO))-self.LINEWIDTH + pastePosition = (pasteX, self.LINEWIDTH*3) + background.paste(topLine, pastePosition, topLine) lastLine = self.badLine(int(self.GALLOWY*0.125)) - background.paste(lastLine,((int(self.GALLOWX*(0.75*self.GOLDENRATIO))+int(self.GALLOWY*0.30)-self.LINEWIDTH),self.LINEWIDTH*3),lastLine) + pasteX += int(self.GALLOWY*0.30) + background.paste(lastLine, (pasteX, self.LINEWIDTH*3), lastLine) return background - def drawLetterLines(self, word,guessed,misses): - letterLines = Image.new("RGBA",((self.LETTERLINELENGTH+self.LETTERLINEDISTANCE)*len(word),self.LETTERLINELENGTH+self.LINEWIDTH*3),color=(0,0,0,0)) + def drawLetterLines(self, word: str, guessed: list, misses: int): + imageWidth = (self.LETTERLINELENGTH+self.LETTERLINEDISTANCE)*len(word) + imageSize = (imageWidth, self.LETTERLINELENGTH+self.LINEWIDTH*3) + letterLines = Image.new("RGBA", imageSize, color=(0, 0, 0, 0)) for x, letter in enumerate(word): - line = self.badLine(self.LETTERLINELENGTH,True) - letterLines.paste(line,(x*(self.LETTERLINELENGTH+self.LETTERLINEDISTANCE),self.LETTERLINELENGTH),line) + line = self.badLine(self.LETTERLINELENGTH, True) + pasteX = x*(self.LETTERLINELENGTH+self.LETTERLINEDISTANCE) + pastePosition = (pasteX, self.LETTERLINELENGTH) + letterLines.paste(line, pastePosition, line) if guessed[x]: - letterDrawing = self.badText(letter,True) + letterDrawing = self.badText(letter, True) letterWidth = self.FONT.getsize(letter)[0] - letterX = int(x*(self.LETTERLINELENGTH+self.LETTERLINEDISTANCE)-(letterWidth/2)+(self.LETTERLINELENGTH*0.5)+(self.LINEWIDTH*2)) - letterLines.paste(letterDrawing,(letterX,0),letterDrawing) + letterX = x*(self.LETTERLINELENGTH+self.LETTERLINEDISTANCE) + letterX -= (letterWidth//2) + letterX += (self.LETTERLINELENGTH//2)+(self.LINEWIDTH*2) + letterLines.paste(letterDrawing, (letterX, 0), letterDrawing) elif misses == 6: - letterDrawing = self.badText(letter,True,(242,66,54)) + letterDrawing = self.badText(letter, True, (242, 66, 54)) letterWidth = self.FONT.getsize(letter)[0] - letterX = int(x*(self.LETTERLINELENGTH+self.LETTERLINEDISTANCE)-(letterWidth/2)+(self.LETTERLINELENGTH*0.5)+(self.LINEWIDTH*2)) - letterLines.paste(letterDrawing,(letterX,0),letterDrawing) + letterX = x*(self.LETTERLINELENGTH+self.LETTERLINEDISTANCE) + letterX -= (letterWidth//2) + letterX += (self.LETTERLINELENGTH//2)+(self.LINEWIDTH*2) + letterLines.paste(letterDrawing, (letterX, 0), letterDrawing) return letterLines - def shortestDist(self,positions,newPosition): + def shortestDist(self, positions: list, newPosition: tuple): shortestDist = math.inf x, y = newPosition for i, j in positions: xDistance = abs(i-x) yDistance = abs(j-y) dist = math.sqrt(xDistance**2+yDistance**2) - if shortestDist > dist: shortestDist = dist + if shortestDist > dist: + shortestDist = dist return shortestDist - def drawMisses(self,guesses,word): - background = Image.new("RGBA",(600,400),color=(0,0,0,0)) + def drawMisses(self, guesses: list, word: str): + background = Image.new("RGBA", (600, 400), color=(0, 0, 0, 0)) pos = [] for guess in guesses: if guess not in word: placed = False - while placed == False: - letter = self.badText(guess,True) + while not placed: + letter = self.badText(guess, True) w, h = self.FONT.getsize(guess) - x = random.randint(0,600-w) - y = random.randint(0,400-h) - if self.shortestDist(pos,(x,y)) > 70: - pos.append((x,y)) - background.paste(letter,(x,y),letter) + x = random.randint(0, 600-w) + y = random.randint(0, 400-h) + if self.shortestDist(pos, (x, y)) > 70: + pos.append((x, y)) + background.paste(letter, (x, y), letter) placed = True return background - def drawImage(self,channel): + def drawImage(self, channel: str): self.bot.log("Drawing hangman image", channel) - game = self.bot.database["hangman games"].find_one({"_id":channel}) + game = self.bot.database["hangman games"].find_one({"_id": channel}) random.seed(game["game ID"]) background = Image.open("resources/paper.jpg") - try: - gallow = self.drawGallows() - except: - self.bot.log("Error drawing gallows (error code 1711)") - - try: - man = self.drawMan(game["misses"]) - except: - self.bot.log("Error drawing stick figure (error code 1712)") + gallow = self.drawGallows() + man = self.drawMan(game["misses"]) random.seed(game["game ID"]) - try: - letterLines = self.drawLetterLines(game["word"],game["guessed"],game["misses"]) - except: - self.bot.log("error drawing letter lines (error code 1713)") + letterLineParams = [game["word"], game["guessed"], game["misses"]] + letterLines = self.drawLetterLines(*letterLineParams) random.seed(game["game ID"]) - try: - misses = self.drawMisses(game["guessed letters"],game["word"]) - except: - self.bot.log("Error drawing misses (error code 1714)") + misses = self.drawMisses(game["guessed letters"], game["word"]) - background.paste(gallow,(100,100),gallow) - background.paste(man,(300,210),man) - background.paste(letterLines,(120,840),letterLines) - background.paste(misses,(600,150),misses) + background.paste(gallow, (100, 100), gallow) + background.paste(man, (300, 210), man) + background.paste(letterLines, (120, 840), letterLines) + background.paste(misses, (600, 150), misses) - missesText = self.badText("MISSES",False) + missesText = self.badText("MISSES", False) missesTextWidth = missesText.size[0] - background.paste(missesText,(850-int(missesTextWidth/2),50),missesText) + background.paste(missesText, (850-missesTextWidth//2, 50), missesText) - background.save("resources/games/hangmanBoards/hangmanBoard"+channel+".png") + boardPath = f"resources/games/hangmanBoards/hangmanBoard{channel}.png" + background.save(boardPath) From 8f6c8b06be3915ab750a2634fe7e06facee4c4a2 Mon Sep 17 00:00:00 2001 From: NikolajDanger Date: Thu, 22 Apr 2021 20:36:06 +0200 Subject: [PATCH 20/23] :sparkles: hangman --- funcs/games/hangman.py | 391 ++++++++++++++++++++++++++--------------- 1 file changed, 246 insertions(+), 145 deletions(-) diff --git a/funcs/games/hangman.py b/funcs/games/hangman.py index b00c727..d702688 100644 --- a/funcs/games/hangman.py +++ b/funcs/games/hangman.py @@ -1,22 +1,53 @@ -import json -import requests -import datetime -import string -import discord -import math -import random +""" +Deals with commands and logic for hangman games. -from discord_slash.context import SlashContext -from PIL import ImageDraw, Image, ImageFont +*Classes* +--------- + Hangman() + Deals with the game logic of hangman. + DrawHangman() + Draws the image shown to the player. +""" +import requests # Used for getting the word in Hangman.start() +import datetime # Used for generating the game id +import string # string.ascii_uppercase used +import discord # Used for discord.file and type hints +import math # Used by DrawHangman(), mainly for drawing circles +import random # Used to draw poorly + +from discord_slash.context import SlashContext # Used for typehints +from PIL import ImageDraw, Image, ImageFont # Used to draw the image class Hangman(): + """ + Controls hangman commands and game logic. + + *Methods* + --------- + start(ctx: SlashContext) + stop(ctx: SlashContext) + guess(message: discord.message, user: str, guess: str) + """ + def __init__(self, bot): - self.bot = bot - self.draw = DrawHangman(bot) - self.APIURL = "https://api.wordnik.com/v4/words.json/randomWords?" - apiKey = self.bot.credentials.wordnikKey - self.APIPARAMS = { + """ + Initialize the class. + + *Attributes* + ------------ + draw: DrawHangman + The DrawHangman used to draw the hangman image. + APIURL: str + The url to get the words from. + APIPARAMS: dict + The parameters to pass to every api call. + """ + self.__bot = bot + self.__draw = DrawHangman(bot) + self.__APIURL = "https://api.wordnik.com/v4/words.json/randomWords?" + apiKey = self.__bot.credentials.wordnikKey + self.__APIPARAMS = { "hasDictionaryDef": True, "minCorpusCount": 5000, "maxCorpusCount": -1, @@ -29,20 +60,28 @@ class Hangman(): } async def start(self, ctx: SlashContext): - await self.bot.defer(ctx) + """ + Start a game of hangman. + + *Parameters* + ------------ + ctx: SlashContext + The context of the command. + """ + await self.__bot.defer(ctx) 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) + game = self.__bot.database["hangman games"].find_one({"_id": channel}) + userName = self.__bot.databaseFuncs.getName(user) startedGame = False if game is None: word = "-" while "-" in word or "." in word: - response = requests.get(self.APIURL, params=self.APIPARAMS) + response = requests.get(self.__APIURL, params=self.__APIPARAMS) word = list(response.json()[0]["word"].upper()) - self.bot.log("Found the word \""+"".join(word)+"\"") + self.__bot.log("Found the word \""+"".join(word)+"\"") guessed = [False] * len(word) gameID = datetime.datetime.now().strftime("%Y%m%d%H%M%S") newGame = { @@ -54,20 +93,20 @@ class Hangman(): "misses": 0, "guessed": guessed } - self.bot.database["hangman games"].insert_one(newGame) + self.__bot.database["hangman games"].insert_one(newGame) remainingLetters = list(string.ascii_uppercase) - self.draw.drawImage(channel) + self.__draw.drawImage(channel) logMessage = "Game started" sendMessage = f"{userName} started game of hangman." startedGame = True else: logMessage = "There was already a game going on" - sendMessage = self.bot.longStrings["Hangman going on"] + sendMessage = self.__bot.longStrings["Hangman going on"] - self.bot.log(logMessage) + self.__bot.log(logMessage) await ctx.send(sendMessage) if startedGame: @@ -92,29 +131,49 @@ class Hangman(): await message.add_reaction(emoji) async def stop(self, ctx: SlashContext): + """ + Stop the game of hangman. + + *Parameters* + ------------ + ctx: SlashContext + The context of the command. + """ channel = str(ctx.channel.id) - game = self.bot.database["hangman games"].find_one({"_id": channel}) + 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}) + 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") + self.__bot.log("Deleting old message") await oldMessage.delete() await ctx.send("Game stopped") async def guess(self, message: discord.Message, user: str, guess: str): + """ + Guess a letter. + + *Parameters* + ------------ + message: discord.Message + The message that the reaction was placed on. + user: str + The id of the user. + guess: str + The guess. + """ channel = str(message.channel.id) - hangmanGames = self.bot.database["hangman games"] + hangmanGames = self.__bot.database["hangman games"] game = hangmanGames.find_one({"_id": channel}) gameExists = (game is not None) @@ -123,7 +182,7 @@ class Hangman(): validGuess = (gameExists and singleLetter and newGuess) if validGuess: - self.bot.log("Guessed the letter") + self.__bot.log("Guessed the letter") correctGuess = 0 for x, letter in enumerate(game["word"]): @@ -153,16 +212,16 @@ class Hangman(): sendMessage = "Guessed {}. There were {} {}s in the word." sendMessage = sendMessage.format(guess, correctGuess, guess) - self.draw.drawImage(channel) + self.__draw.drawImage(channel) if game["misses"] == 6: hangmanGames.delete_one({"_id": channel}) - sendMessage += self.bot.longStrings["Hangman lost game"] + sendMessage += self.__bot.longStrings["Hangman lost game"] remainingLetters = [] elif all(game["guessed"]): hangmanGames.delete_one({"_id": channel}) - self.bot.money.addMoney(user, 15) - sendMessage += self.bot.longStrings["Hangman guessed word"] + self.__bot.money.addMoney(user, 15) + sendMessage += self.__bot.longStrings["Hangman guessed word"] remainingLetters = [] await message.channel.send(sendMessage) @@ -172,7 +231,7 @@ class Hangman(): for oldID in oldMessageIDs: oldMessage = await message.channel.fetch_message(int(oldID)) - self.bot.log("Deleting old message") + self.__bot.log("Deleting old message") await oldMessage.delete() boardsPath = "resources/games/hangmanBoards/" @@ -206,34 +265,63 @@ class Hangman(): class DrawHangman(): + """ + Draws the image of the hangman game. + + *Methods* + --------- + drawImage(channel: str) + """ + def __init__(self, bot): - self.bot = bot - self.CIRCLESIZE = 120 - self.LINEWIDTH = 12 + """ + Initialize the class. - self.BODYSIZE = 210 - self.LIMBSIZE = 60 - self.ARMPOSITION = 60 + *Attributes* + ------------ + CIRCLESIZE + LINEWIDTH + BODYSIZE + LIMBSIZE + ARMPOSITION + MANX, MANY + LETTERLINELENGTH + LETTERLINEDISTANCE + GALLOWX, GALLOWY + PHI + FONT + SMALLFONT + """ + self.__bot = bot + self.__CIRCLESIZE = 120 + self.__LINEWIDTH = 12 - MANPADDING = self.LINEWIDTH*4 - self.MANX = (self.LIMBSIZE*2)+MANPADDING - self.MANY = (self.CIRCLESIZE+self.BODYSIZE+self.LIMBSIZE)+MANPADDING + self.__BODYSIZE = 210 + self.__LIMBSIZE = 60 + self.__ARMPOSITION = 60 - self.LETTERLINELENGTH = 90 - self.LETTERLINEDISTANCE = 30 + self.__MANX = (self.__LIMBSIZE*2) + self.__MANY = (self.__CIRCLESIZE+self.__BODYSIZE+self.__LIMBSIZE) + MANPADDING = self.__LINEWIDTH*4 + self.__MANX += MANPADDING + self.__MANY += MANPADDING - self.GALLOWX, self.GALLOWY = 360, 600 - self.GOLDENRATIO = 1-(1 / ((1 + 5 ** 0.5) / 2)) + self.__LETTERLINELENGTH = 90 + self.__LETTERLINEDISTANCE = 30 - LETTERSIZE = 75 - TEXTSIZE = 70 + self.__GALLOWX, self.__GALLOWY = 360, 600 + self.__PHI = 1-(1 / ((1 + 5 ** 0.5) / 2)) + + LETTERSIZE = 75 # Wrong guesses letter size + WORDSIZE = 70 # Correct guesses letter size FONTPATH = "resources/fonts/comic-sans-bold.ttf" - self.FONT = ImageFont.truetype(FONTPATH, LETTERSIZE) - self.SMALLFONT = ImageFont.truetype(FONTPATH, TEXTSIZE) + self.__FONT = ImageFont.truetype(FONTPATH, LETTERSIZE) + self.__SMALLFONT = ImageFont.truetype(FONTPATH, WORDSIZE) - def calcDeviance(self, preDeviance, preDevianceAccuracy, positionChange, - maxmin, maxAcceleration): + def __deviate(self, preDeviance: int, preDevianceAccuracy: int, + positionChange: float, maxmin: int, + maxAcceleration: float): randomDeviance = random.uniform(-positionChange, positionChange) devianceAccuracy = preDevianceAccuracy + randomDeviance if devianceAccuracy > maxmin * maxAcceleration: @@ -248,14 +336,14 @@ class DrawHangman(): deviance = -maxmin return deviance, devianceAccuracy - def badCircle(self): - circlePadding = (self.LINEWIDTH*3) - imageWidth = self.CIRCLESIZE+circlePadding + def __badCircle(self): + circlePadding = (self.__LINEWIDTH*3) + imageWidth = self.__CIRCLESIZE+circlePadding imageSize = (imageWidth, imageWidth) background = Image.new("RGBA", imageSize, color=(0, 0, 0, 0)) d = ImageDraw.Draw(background, "RGBA") - middle = (self.CIRCLESIZE+(self.LINEWIDTH*3))/2 + middle = (self.__CIRCLESIZE+(self.__LINEWIDTH*3))/2 devianceX = 0 devianceY = 0 devianceAccuracyX = 0 @@ -267,42 +355,42 @@ class DrawHangman(): devianceXParams = [ devianceX, devianceAccuracyX, - self.LINEWIDTH/100, - self.LINEWIDTH, + self.__LINEWIDTH/100, + self.__LINEWIDTH, 0.03 ] devianceYParams = [ devianceY, devianceAccuracyY, - self.LINEWIDTH/100, - self.LINEWIDTH, + self.__LINEWIDTH/100, + self.__LINEWIDTH, 0.03 ] - devianceX, devianceAccuracyX = self.calcDeviance(*devianceXParams) - devianceY, devianceAccuracyY = self.calcDeviance(*devianceYParams) + devianceX, devianceAccuracyX = self.__deviate(*devianceXParams) + devianceY, devianceAccuracyY = self.__deviate(*devianceYParams) radians = math.radians(degree+start) - circleX = (math.cos(radians) * (self.CIRCLESIZE/2)) - circleY = (math.sin(radians) * (self.CIRCLESIZE/2)) + circleX = (math.cos(radians) * (self.__CIRCLESIZE/2)) + circleY = (math.sin(radians) * (self.__CIRCLESIZE/2)) - x = middle + circleX - (self.LINEWIDTH/2) + devianceX - y = middle + circleY - (self.LINEWIDTH/2) + devianceY + x = middle + circleX - (self.__LINEWIDTH/2) + devianceX + y = middle + circleY - (self.__LINEWIDTH/2) + devianceY - circlePosition = [(x, y), (x+self.LINEWIDTH, y+self.LINEWIDTH)] + circlePosition = [(x, y), (x+self.__LINEWIDTH, y+self.__LINEWIDTH)] d.ellipse(circlePosition, fill=(0, 0, 0, 255)) return background - def badLine(self, length: int, rotated: bool = False): + def __badLine(self, length: int, rotated: bool = False): if rotated: - w, h = length+self.LINEWIDTH*3, self.LINEWIDTH*3 + w, h = length+self.__LINEWIDTH*3, self.__LINEWIDTH*3 else: - w, h = self.LINEWIDTH*3, length+self.LINEWIDTH*3 + w, h = self.__LINEWIDTH*3, length+self.__LINEWIDTH*3 background = Image.new("RGBA", (w, h), color=(0, 0, 0, 0)) d = ImageDraw.Draw(background, "RGBA") - possibleDeviance = int(self.LINEWIDTH/3) + possibleDeviance = int(self.__LINEWIDTH/3) devianceX = random.randint(-possibleDeviance, possibleDeviance) devianceY = 0 devianceAccuracyX = 0 @@ -312,45 +400,46 @@ class DrawHangman(): devianceParamsX = [ devianceX, devianceAccuracyX, - self.LINEWIDTH/1000, - self.LINEWIDTH, + self.__LINEWIDTH/1000, + self.__LINEWIDTH, 0.004 ] devianceParamsY = [ devianceY, devianceAccuracyY, - self.LINEWIDTH/1000, - self.LINEWIDTH, + self.__LINEWIDTH/1000, + self.__LINEWIDTH, 0.004 ] - devianceX, devianceAccuracyX = self.calcDeviance(*devianceParamsX) - devianceY, devianceAccuracyY = self.calcDeviance(*devianceParamsY) + devianceX, devianceAccuracyX = self.__deviate(*devianceParamsX) + devianceY, devianceAccuracyY = self.__deviate(*devianceParamsY) if rotated: - x = self.LINEWIDTH + pixel + devianceX - y = self.LINEWIDTH + devianceY + x = self.__LINEWIDTH + pixel + devianceX + y = self.__LINEWIDTH + devianceY else: - x = self.LINEWIDTH + devianceX - y = self.LINEWIDTH + pixel + devianceY + x = self.__LINEWIDTH + devianceX + y = self.__LINEWIDTH + pixel + devianceY - circlePosition = [(x, y), (x+self.LINEWIDTH, y+self.LINEWIDTH)] + circlePosition = [(x, y), (x+self.__LINEWIDTH, y+self.__LINEWIDTH)] d.ellipse(circlePosition, fill=(0, 0, 0, 255)) return background - def drawMan(self, misses: int): - manSize = (self.MANX, self.MANY) + def __drawMan(self, misses: int, seed: str): + random.seed(seed) + manSize = (self.__MANX, self.__MANY) background = Image.new("RGBA", manSize, color=(0, 0, 0, 0)) if misses >= 1: - head = self.badCircle() - pasteX = (self.MANX-(self.CIRCLESIZE+(self.LINEWIDTH*3)))//2 + head = self.__badCircle() + pasteX = (self.__MANX-(self.__CIRCLESIZE+(self.__LINEWIDTH*3)))//2 pastePosition = (pasteX, 0) background.paste(head, pastePosition, head) if misses >= 2: - body = self.badLine(self.BODYSIZE) - pasteX = (self.MANX-(self.LINEWIDTH*3))//2 - pastePosition = (pasteX, self.CIRCLESIZE) + body = self.__badLine(self.__BODYSIZE) + pasteX = (self.__MANX-(self.__LINEWIDTH*3))//2 + pastePosition = (pasteX, self.__CIRCLESIZE) background.paste(body, pastePosition, body) if misses >= 3: @@ -358,30 +447,33 @@ class DrawHangman(): else: limbs = [] + random.seed(seed) + for limb in limbs: - limbDrawing = self.badLine(self.LIMBSIZE, True) - xPosition = (self.MANX-(self.LINEWIDTH*3))//2 + limbDrawing = self.__badLine(self.__LIMBSIZE, True) + xPosition = (self.__MANX-(self.__LINEWIDTH*3))//2 if limb[1] == "a": rotation = random.randint(-45, 45) shift = math.sin(math.radians(rotation)) - compensation = int(shift*(self.LIMBSIZE+(self.LINEWIDTH*3))) + lineLength = self.__LIMBSIZE+(self.__LINEWIDTH*3) + compensation = int(shift*lineLength) limbDrawing = limbDrawing.rotate(rotation, expand=1) - yPosition = self.CIRCLESIZE + self.ARMPOSITION + compensation + yPosition = self.__CIRCLESIZE + self.__ARMPOSITION if limb == "ra": compensation = min(-compensation, 0) else: - xPosition -= self.LIMBSIZE + xPosition -= self.__LIMBSIZE compensation = min(compensation, 0) + + yPosition += compensation else: rotation = random.randint(-15, 15) - yPosition = self.CIRCLESIZE+self.BODYSIZE + yPosition = self.__CIRCLESIZE+self.__BODYSIZE-self.__LINEWIDTH if limb == "rl": - xPosition -= self.LINEWIDTH limbDrawing = limbDrawing.rotate(rotation-45, expand=1) else: - yPosition -= self.LINEWIDTH - xPosition += -limbDrawing.size[0]+self.LINEWIDTH*3 + xPosition += -limbDrawing.size[0]+self.__LINEWIDTH*3 limbDrawing = limbDrawing.rotate(rotation+45, expand=1) pastePosition = (xPosition, yPosition) @@ -389,11 +481,11 @@ class DrawHangman(): return background - def badText(self, text: str, big: bool, color: tuple = (0, 0, 0, 255)): + def __badText(self, text: str, big: bool, color: tuple = (0, 0, 0, 255)): if big: - font = self.FONT + font = self.__FONT else: - font = self.SMALLFONT + font = self.__SMALLFONT w, h = font.getsize(text) img = Image.new("RGBA", (w, h), color=(0, 0, 0, 0)) d = ImageDraw.Draw(img, "RGBA") @@ -401,109 +493,118 @@ class DrawHangman(): d.text((0, 0), text, font=font, fill=color) return img - def drawGallows(self): - gallowSize = (self.GALLOWX, self.GALLOWY) + def __drawGallows(self): + gallowSize = (self.__GALLOWX, self.__GALLOWY) background = Image.new("RGBA", gallowSize, color=(0, 0, 0, 0)) - bottomLine = self.badLine(int(self.GALLOWX * 0.75), True) - bottomLineX = int(self.GALLOWX * 0.125) - bottomLineY = self.GALLOWY-(self.LINEWIDTH*4) + bottomLine = self.__badLine(int(self.__GALLOWX * 0.75), True) + bottomLineX = int(self.__GALLOWX * 0.125) + bottomLineY = self.__GALLOWY-(self.__LINEWIDTH*4) pastePosition = (bottomLineX, bottomLineY) background.paste(bottomLine, pastePosition, bottomLine) - lineTwo = self.badLine(self.GALLOWY-self.LINEWIDTH*6) - lineTwoX = int(self.GALLOWX*(0.75*self.GOLDENRATIO)) - lineTwoY = self.LINEWIDTH*2 + lineTwo = self.__badLine(self.__GALLOWY-self.__LINEWIDTH*6) + lineTwoX = int(self.__GALLOWX*(0.75*self.__PHI)) + lineTwoY = self.__LINEWIDTH*2 pastePosition = (lineTwoX, lineTwoY) background.paste(lineTwo, pastePosition, lineTwo) - topLine = self.badLine(int(self.GALLOWY*0.30), True) - pasteX = int(self.GALLOWX*(0.75*self.GOLDENRATIO))-self.LINEWIDTH - pastePosition = (pasteX, self.LINEWIDTH*3) + topLine = self.__badLine(int(self.__GALLOWY*0.30), True) + pasteX = int(self.__GALLOWX*(0.75*self.__PHI))-self.__LINEWIDTH + pastePosition = (pasteX, self.__LINEWIDTH*3) background.paste(topLine, pastePosition, topLine) - lastLine = self.badLine(int(self.GALLOWY*0.125)) - pasteX += int(self.GALLOWY*0.30) - background.paste(lastLine, (pasteX, self.LINEWIDTH*3), lastLine) + lastLine = self.__badLine(int(self.__GALLOWY*0.125)) + pasteX += int(self.__GALLOWY*0.30) + background.paste(lastLine, (pasteX, self.__LINEWIDTH*3), lastLine) return background - def drawLetterLines(self, word: str, guessed: list, misses: int): - imageWidth = (self.LETTERLINELENGTH+self.LETTERLINEDISTANCE)*len(word) - imageSize = (imageWidth, self.LETTERLINELENGTH+self.LINEWIDTH*3) + def __drawLetterLines(self, word: str, guessed: list, misses: int): + letterWidth = self.__LETTERLINELENGTH+self.__LETTERLINEDISTANCE + imageWidth = letterWidth*len(word) + imageSize = (imageWidth, self.__LETTERLINELENGTH+self.__LINEWIDTH*3) letterLines = Image.new("RGBA", imageSize, color=(0, 0, 0, 0)) for x, letter in enumerate(word): - line = self.badLine(self.LETTERLINELENGTH, True) - pasteX = x*(self.LETTERLINELENGTH+self.LETTERLINEDISTANCE) - pastePosition = (pasteX, self.LETTERLINELENGTH) + line = self.__badLine(self.__LETTERLINELENGTH, True) + pasteX = x*(self.__LETTERLINELENGTH+self.__LETTERLINEDISTANCE) + pastePosition = (pasteX, self.__LETTERLINELENGTH) letterLines.paste(line, pastePosition, line) if guessed[x]: - letterDrawing = self.badText(letter, True) - letterWidth = self.FONT.getsize(letter)[0] - letterX = x*(self.LETTERLINELENGTH+self.LETTERLINEDISTANCE) + letterDrawing = self.__badText(letter, True) + letterWidth = self.__FONT.getsize(letter)[0] + letterX = x*(self.__LETTERLINELENGTH+self.__LETTERLINEDISTANCE) letterX -= (letterWidth//2) - letterX += (self.LETTERLINELENGTH//2)+(self.LINEWIDTH*2) + letterX += (self.__LETTERLINELENGTH//2)+(self.__LINEWIDTH*2) letterLines.paste(letterDrawing, (letterX, 0), letterDrawing) elif misses == 6: - letterDrawing = self.badText(letter, True, (242, 66, 54)) - letterWidth = self.FONT.getsize(letter)[0] - letterX = x*(self.LETTERLINELENGTH+self.LETTERLINEDISTANCE) + letterDrawing = self.__badText(letter, True, (242, 66, 54)) + letterWidth = self.__FONT.getsize(letter)[0] + letterX = x*(self.__LETTERLINELENGTH+self.__LETTERLINEDISTANCE) letterX -= (letterWidth//2) - letterX += (self.LETTERLINELENGTH//2)+(self.LINEWIDTH*2) + letterX += (self.__LETTERLINELENGTH//2)+(self.__LINEWIDTH*2) letterLines.paste(letterDrawing, (letterX, 0), letterDrawing) return letterLines - def shortestDist(self, positions: list, newPosition: tuple): - shortestDist = math.inf + def __shortestDist(self, positions: list, newPosition: tuple): + __shortestDist = math.inf x, y = newPosition for i, j in positions: xDistance = abs(i-x) yDistance = abs(j-y) dist = math.sqrt(xDistance**2+yDistance**2) - if shortestDist > dist: - shortestDist = dist - return shortestDist + if __shortestDist > dist: + __shortestDist = dist + return __shortestDist - def drawMisses(self, guesses: list, word: str): + def __drawMisses(self, guesses: list, word: str): background = Image.new("RGBA", (600, 400), color=(0, 0, 0, 0)) pos = [] for guess in guesses: if guess not in word: placed = False while not placed: - letter = self.badText(guess, True) - w, h = self.FONT.getsize(guess) + letter = self.__badText(guess, True) + w, h = self.__FONT.getsize(guess) x = random.randint(0, 600-w) y = random.randint(0, 400-h) - if self.shortestDist(pos, (x, y)) > 70: + if self.__shortestDist(pos, (x, y)) > 70: pos.append((x, y)) background.paste(letter, (x, y), letter) placed = True return background def drawImage(self, channel: str): - self.bot.log("Drawing hangman image", channel) - game = self.bot.database["hangman games"].find_one({"_id": channel}) + """ + Draw a hangman Image. + + *Parameters* + ------------ + channel: str + The id of the channel the game is in. + """ + self.__bot.log("Drawing hangman image", channel) + game = self.__bot.database["hangman games"].find_one({"_id": channel}) random.seed(game["game ID"]) background = Image.open("resources/paper.jpg") - gallow = self.drawGallows() - man = self.drawMan(game["misses"]) + gallow = self.__drawGallows() + man = self.__drawMan(game["misses"], game["game ID"]) random.seed(game["game ID"]) letterLineParams = [game["word"], game["guessed"], game["misses"]] - letterLines = self.drawLetterLines(*letterLineParams) + letterLines = self.__drawLetterLines(*letterLineParams) random.seed(game["game ID"]) - misses = self.drawMisses(game["guessed letters"], game["word"]) + misses = self.__drawMisses(game["guessed letters"], game["word"]) background.paste(gallow, (100, 100), gallow) background.paste(man, (300, 210), man) background.paste(letterLines, (120, 840), letterLines) background.paste(misses, (600, 150), misses) - missesText = self.badText("MISSES", False) + missesText = self.__badText("MISSES", False) missesTextWidth = missesText.size[0] background.paste(missesText, (850-missesTextWidth//2, 50), missesText) From 8c253aca3de56f7622e19325e9a2a132a82dc30c Mon Sep 17 00:00:00 2001 From: Nikolaj Date: Mon, 14 Jun 2021 21:00:10 +0200 Subject: [PATCH 21/23] :broom: PEP updating --- cogs/StarWarsCog.py | 40 ------ cogs/{EventCog.py => event_cog.py} | 12 +- cogs/{GameCogs.py => game_cog.py} | 18 +-- cogs/{LookupCog.py => lookup_cog.py} | 4 +- cogs/{MiscCog.py => misc_cog.py} | 0 cogs/star_wars_cog.py | 40 ++++++ funcs/__init__.py | 2 +- funcs/games/__init__.py | 2 +- funcs/games/blackjack.py | 120 +++++++++--------- .../games/{connectFour.py => connect_four.py} | 58 ++++----- .../{gamesContainer.py => games_container.py} | 6 +- funcs/games/hangman.py | 22 ++-- funcs/games/hex.py | 50 ++++---- funcs/games/invest.py | 16 +-- funcs/games/money.py | 6 +- funcs/games/trivia.py | 10 +- funcs/lookup/__init__.py | 2 +- .../{lookupFuncs.py => lookup_funcs.py} | 0 funcs/other/generators.py | 2 +- .../__init__.py | 2 +- .../star_wars.py} | 6 +- .../star_wars_char.py} | 20 +-- .../star_wars_destiny.py} | 20 +-- .../star_wars_roll.py} | 14 +- Gwendolyn.py => gwendolyn.py | 79 ++++++------ project-guidelines.md | 4 +- .../oldImages/blackjack740652054388932682 | 1 + .../games/oldImages/hangman740652054388932682 | 2 + .../games/oldImages/hex740652054388932682 | 1 + resources/help/help-connectfour.txt | 2 +- resources/help/help-star_wars_character.txt | 1 + ...arwarsroll.txt => help-star_wars_roll.txt} | 2 +- resources/help/help-starwarscharacter.txt | 1 - resources/help/help.txt | 6 +- .../{longStrings.json => long_strings.json} | 0 resources/slashParameters.json | 28 ++-- .../starwarsskills.json | 0 .../starwarstemplates.json | 0 resources/startingFiles.json | 6 +- utils/__init__.py | 10 +- utils/{eventHandlers.py => event_handlers.py} | 26 ++-- utils/{helperClasses.py => helper_classes.py} | 21 +-- utils/{utilFunctions.py => util_functions.py} | 14 +- 43 files changed, 343 insertions(+), 333 deletions(-) delete mode 100644 cogs/StarWarsCog.py rename cogs/{EventCog.py => event_cog.py} (72%) rename cogs/{GameCogs.py => game_cog.py} (91%) rename cogs/{LookupCog.py => lookup_cog.py} (87%) rename cogs/{MiscCog.py => misc_cog.py} (100%) create mode 100644 cogs/star_wars_cog.py rename funcs/games/{connectFour.py => connect_four.py} (95%) rename funcs/games/{gamesContainer.py => games_container.py} (90%) rename funcs/lookup/{lookupFuncs.py => lookup_funcs.py} (100%) rename funcs/{starWarsFuncs => star_wars_funcs}/__init__.py (70%) rename funcs/{starWarsFuncs/starWars.py => star_wars_funcs/star_wars.py} (62%) rename funcs/{starWarsFuncs/starWarsChar.py => star_wars_funcs/star_wars_char.py} (97%) rename funcs/{starWarsFuncs/starWarsDestiny.py => star_wars_funcs/star_wars_destiny.py} (76%) rename funcs/{starWarsFuncs/starWarsRoll.py => star_wars_funcs/star_wars_roll.py} (96%) rename Gwendolyn.py => gwendolyn.py (65%) create mode 100644 resources/games/oldImages/blackjack740652054388932682 create mode 100644 resources/games/oldImages/hangman740652054388932682 create mode 100644 resources/games/oldImages/hex740652054388932682 create mode 100644 resources/help/help-star_wars_character.txt rename resources/help/{help-starwarsroll.txt => help-star_wars_roll.txt} (86%) delete mode 100644 resources/help/help-starwarscharacter.txt rename resources/{longStrings.json => long_strings.json} (100%) rename resources/{starWars => star_wars}/starwarsskills.json (100%) rename resources/{starWars => star_wars}/starwarstemplates.json (100%) rename utils/{eventHandlers.py => event_handlers.py} (87%) rename utils/{helperClasses.py => helper_classes.py} (94%) rename utils/{utilFunctions.py => util_functions.py} (95%) diff --git a/cogs/StarWarsCog.py b/cogs/StarWarsCog.py deleted file mode 100644 index 72516d4..0000000 --- a/cogs/StarWarsCog.py +++ /dev/null @@ -1,40 +0,0 @@ -"""Contains the StarWarsCog, which deals with Star Wars commands.""" -from discord.ext import commands -from discord_slash import cog_ext - -from utils import getParams # pylint: disable=import-error - -params = getParams() - - -class StarWarsCog(commands.Cog): - """Contains the Star Wars commands.""" - - def __init__(self, bot): - """Initialize the cog.""" - self.bot = bot - - @cog_ext.cog_slash(**params["starWarsRoll"]) - async def starWarsRoll(self, ctx, dice=""): - """Roll Star Wars dice.""" - await self.bot.starWars.roll.parseRoll(ctx, dice) - - @cog_ext.cog_slash(**params["starWarsDestiny"]) - async def starWarsDestiny(self, ctx, parameters=""): - """Control Star Wars destiny points.""" - await self.bot.starWars.destiny.parseDestiny(ctx, parameters) - - @cog_ext.cog_slash(**params["starWarsCrit"]) - async def starWarsCrit(self, ctx, severity: int = 0): - """Roll for critical injuries.""" - await self.bot.starWars.roll.critRoll(ctx, severity) - - @cog_ext.cog_slash(**params["starWarsCharacter"]) - async def starWarsCharacter(self, ctx, parameters=""): - """Access and change Star Wars character sheet data.""" - await self.bot.starWars.character.parseChar(ctx, parameters) - - -def setup(bot): - """Add the cog to the bot.""" - bot.add_cog(StarWarsCog(bot)) diff --git a/cogs/EventCog.py b/cogs/event_cog.py similarity index 72% rename from cogs/EventCog.py rename to cogs/event_cog.py index 50fc3b9..6216ae7 100644 --- a/cogs/EventCog.py +++ b/cogs/event_cog.py @@ -13,26 +13,26 @@ class EventCog(commands.Cog): @commands.Cog.listener() async def on_ready(self): """Log and set bot status when bot logs in.""" - await self.bot.eventHandler.on_ready() + await self.bot.event_handler.on_ready() @commands.Cog.listener() async def on_slash_command(self, ctx): """Log when a slash command is run.""" - await self.bot.eventHandler.on_slash_command(ctx) + await self.bot.event_handler.on_slash_command(ctx) @commands.Cog.listener() async def on_slash_command_error(self, ctx, error): """Log when a slash error occurs.""" - await self.bot.errorHandler.on_slash_command_error(ctx, error) + await self.bot.error_handler.on_slash_command_error(ctx, error) - async def on_error(self, method, *args, **kwargs): + async def on_error(self, method): """Log when an error occurs.""" - await self.bot.errorHandler.on_error(method) + await self.bot.error_handler.on_error(method) @commands.Cog.listener() async def on_reaction_add(self, reaction, user): """Handle when someone reacts to a message.""" - await self.bot.eventHandler.on_reaction_add(reaction, user) + await self.bot.event_handler.on_reaction_add(reaction, user) def setup(bot): diff --git a/cogs/GameCogs.py b/cogs/game_cog.py similarity index 91% rename from cogs/GameCogs.py rename to cogs/game_cog.py index ebd1f54..d6bb92f 100644 --- a/cogs/GameCogs.py +++ b/cogs/game_cog.py @@ -95,20 +95,20 @@ class ConnectFourCog(commands.Cog): """Initialize the cog.""" self.bot = bot - @cog_ext.cog_subcommand(**params["connectFourStartUser"]) - async def connectFourStartUser(self, ctx, user): + @cog_ext.cog_subcommand(**params["connect_fourStartUser"]) + async def connect_fourStartUser(self, ctx, user): """Start a game of connect four against another user.""" - await self.bot.games.connectFour.start(ctx, user) + await self.bot.games.connect_four.start(ctx, user) - @cog_ext.cog_subcommand(**params["connectFourStartGwendolyn"]) - async def connectFourStartGwendolyn(self, ctx, difficulty=3): + @cog_ext.cog_subcommand(**params["connect_fourStartGwendolyn"]) + async def connect_fourStartGwendolyn(self, ctx, difficulty=3): """Start a game of connect four against Gwendolyn.""" - await self.bot.games.connectFour.start(ctx, difficulty) + await self.bot.games.connect_four.start(ctx, difficulty) - @cog_ext.cog_subcommand(**params["connectFourSurrender"]) - async def connectFourSurrender(self, ctx): + @cog_ext.cog_subcommand(**params["connect_fourSurrender"]) + async def connect_fourSurrender(self, ctx): """Surrender the game of connect four.""" - await self.bot.games.connectFour.surrender(ctx) + await self.bot.games.connect_four.surrender(ctx) class HangmanCog(commands.Cog): diff --git a/cogs/LookupCog.py b/cogs/lookup_cog.py similarity index 87% rename from cogs/LookupCog.py rename to cogs/lookup_cog.py index f41d38b..91167da 100644 --- a/cogs/LookupCog.py +++ b/cogs/lookup_cog.py @@ -18,13 +18,13 @@ class LookupCog(commands.Cog): @cog_ext.cog_slash(**params["spell"]) async def spell(self, ctx, query): """Look up a spell.""" - await self.bot.lookupFuncs.spellFunc(ctx, query) + await self.bot.lookup_funcs.spellFunc(ctx, query) # Looks up a monster @cog_ext.cog_slash(**params["monster"]) async def monster(self, ctx, query): """Look up a monster.""" - await self.bot.lookupFuncs.monsterFunc(ctx, query) + await self.bot.lookup_funcs.monsterFunc(ctx, query) def setup(bot): diff --git a/cogs/MiscCog.py b/cogs/misc_cog.py similarity index 100% rename from cogs/MiscCog.py rename to cogs/misc_cog.py diff --git a/cogs/star_wars_cog.py b/cogs/star_wars_cog.py new file mode 100644 index 0000000..b029f9f --- /dev/null +++ b/cogs/star_wars_cog.py @@ -0,0 +1,40 @@ +"""Contains the StarWarsCog, which deals with Star Wars commands.""" +from discord.ext import commands +from discord_slash import cog_ext + +from utils import getParams # pylint: disable=import-error + +params = getParams() + + +class StarWarsCog(commands.Cog): + """Contains the Star Wars commands.""" + + def __init__(self, bot): + """Initialize the cog.""" + self.bot = bot + + @cog_ext.cog_slash(**params["star_wars_roll"]) + async def star_wars_roll(self, ctx, dice=""): + """Roll Star Wars dice.""" + await self.bot.star_wars.roll.parseRoll(ctx, dice) + + @cog_ext.cog_slash(**params["star_wars_destiny"]) + async def star_wars_destiny(self, ctx, parameters=""): + """Control Star Wars destiny points.""" + await self.bot.star_wars.destiny.parseDestiny(ctx, parameters) + + @cog_ext.cog_slash(**params["star_wars_crit"]) + async def star_wars_crit(self, ctx, severity: int = 0): + """Roll for critical injuries.""" + await self.bot.star_wars.roll.critRoll(ctx, severity) + + @cog_ext.cog_slash(**params["star_wars_character"]) + async def star_wars_character(self, ctx, parameters=""): + """Access and change Star Wars character sheet data.""" + await self.bot.star_wars.character.parseChar(ctx, parameters) + + +def setup(bot): + """Add the cog to the bot.""" + bot.add_cog(StarWarsCog(bot)) diff --git a/funcs/__init__.py b/funcs/__init__.py index 597efd8..a8c7fc8 100644 --- a/funcs/__init__.py +++ b/funcs/__init__.py @@ -8,4 +8,4 @@ from .lookup import LookupFuncs from .other import Other -from .starWarsFuncs import StarWars +from .star_wars_funcs import StarWars diff --git a/funcs/games/__init__.py b/funcs/games/__init__.py index 8d19ead..9eae213 100644 --- a/funcs/games/__init__.py +++ b/funcs/games/__init__.py @@ -3,4 +3,4 @@ __all__ = ["Money", "Games"] from .money import Money -from .gamesContainer import Games +from .games_container import Games diff --git a/funcs/games/blackjack.py b/funcs/games/blackjack.py index 075e33d..d02e087 100644 --- a/funcs/games/blackjack.py +++ b/funcs/games/blackjack.py @@ -211,7 +211,7 @@ class Blackjack(): allStanding = True preAllStanding = True - message = self.bot.longStrings["Blackjack all players standing"] + message = self.bot.long_strings["Blackjack all players standing"] if game["all standing"]: self.bot.log("All are standing") @@ -245,11 +245,11 @@ class Blackjack(): return "", True, done else: if game["round"] == 0: - firstRoundMsg = self.bot.longStrings["Blackjack first round"] + firstRoundMsg = self.bot.long_strings["Blackjack first round"] else: firstRoundMsg = "" - sendMessage = self.bot.longStrings["Blackjack commands"] + sendMessage = self.bot.long_strings["Blackjack commands"] print(firstRoundMsg) sendMessage = sendMessage.format(firstRoundMsg) return sendMessage, False, done @@ -352,7 +352,7 @@ class Blackjack(): winningCalc = (self._calcWinnings(*_calcWinningsParams)) winnings, netWinnings, reason = winningCalc - userName = self.bot.databaseFuncs.getName(user) + userName = self.bot.database_funcs.getName(user) if winnings < 0: if winnings == -1: @@ -569,7 +569,7 @@ class Blackjack(): """ self.bot.log("Loop "+str(gameRound), str(channel.id)) - oldImagePath = f"resources/games/oldImages/blackjack{channel.id}" + oldImagePath = f"resources/games/old_images/blackjack{channel.id}" with open(oldImagePath, "r") as f: oldImage = await channel.fetch_message(int(f.read())) @@ -604,15 +604,15 @@ class Blackjack(): if rightRound: if not gamedone: - logMessage = f"Loop {gameRound} calling self._blackjackLoop()" - self.bot.log(logMessage, str(channel.id)) + log_message = f"Loop {gameRound} calling self._blackjackLoop()" + self.bot.log(log_message, str(channel.id)) await self._blackjackLoop(channel, gameRound+1, gameID) else: new_message = self._blackjackFinish(str(channel.id)) await channel.send(new_message) else: - logMessage = f"Ending loop on round {gameRound}" - self.bot.log(logMessage, str(channel.id)) + log_message = f"Ending loop on round {gameRound}" + self.bot.log(log_message, str(channel.id)) async def hit(self, ctx: discord_slash.context.SlashContext, handNumber: int = 0): @@ -640,16 +640,16 @@ class Blackjack(): hand, handNumber = self._getHandNumber(userHands, handNumber) if hand is None: - logMessage = "They didn't specify a hand" + log_message = "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" + log_message = "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" + log_message = "They've already hit this round" sendMessage = "You've already hit this round" elif hand["standing"]: - logMessage = "They're already standing" + log_message = "They're already standing" sendMessage = "You can't hit when you're standing" else: hand["hand"].append(self._drawCard(channel)) @@ -675,13 +675,13 @@ class Blackjack(): roundDone = self._isRoundDone(game) sendMessage = f"{ctx.author.display_name} hit" - logMessage = "They succeeded" + log_message = "They succeeded" else: - logMessage = "They tried to hit without being in the game" + log_message = "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) + self.bot.log(log_message) if roundDone: gameID = game["gameID"] @@ -713,22 +713,22 @@ class Blackjack(): hand, handNumber = self._getHandNumber(*handParams) if hand is None: - logMessage = "They didn't specify a hand" + log_message = "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" + log_message = "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" + log_message = "They've already hit this round" sendMessage = "You've already hit this round" elif hand["standing"]: - logMessage = "They're already standing" + log_message = "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" + log_message = "They tried to double after round 1" sendMessage = "You can only double on the first round" elif self.bot.money.checkBalance(user) < hand["bet"]: - logMessage = "They tried to double without being in the game" + log_message = "They tried to double without being in the game" sendMessage = "You can't double when you're not in the game" else: bet = hand["bet"] @@ -759,16 +759,16 @@ class Blackjack(): game = blackjackGames.find_one({"_id": channel}) roundDone = self._isRoundDone(game) - sendMessage = self.bot.longStrings["Blackjack double"] - userName = self.bot.databaseFuncs.getName(user) + sendMessage = self.bot.long_strings["Blackjack double"] + userName = self.bot.database_funcs.getName(user) sendMessage = sendMessage.format(bet, userName) - logMessage = "They succeeded" + log_message = "They succeeded" else: - logMessage = "They tried to double without being in the game" + log_message = "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) + self.bot.log(log_message) if roundDone: gameID = game["gameID"] @@ -801,16 +801,16 @@ class Blackjack(): if hand is None: sendMessage = "You need to specify which hand" - logMessage = "They didn't specify a hand" + log_message = "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" + log_message = "They tried to stand on round 0" elif hand["hit"]: sendMessage = "You've already hit this round" - logMessage = "They'd already hit this round" + log_message = "They'd already hit this round" elif hand["standing"]: sendMessage = "You're already standing" - logMessage = "They're already standing" + log_message = "They're already standing" else: hand["standing"] = True @@ -829,14 +829,14 @@ class Blackjack(): roundDone = self._isRoundDone(game) sendMessage = f"{ctx.author.display_name} is standing" - logMessage = "They succeeded" + log_message = "They succeeded" else: - logMessage = "They tried to stand without being in the game" + log_message = "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) + self.bot.log(log_message) if roundDone: gameID = game["gameID"] @@ -887,33 +887,33 @@ class Blackjack(): otherHand = 4 if handNumberError: - logMessage = "They didn't specify a hand" + log_message = "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" + log_message = "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" + log_message = "They tried to split more than three times" sendMessage = "You can only split 3 times" elif hand["hit"]: - logMessage = "They've already hit" + log_message = "They've already hit" sendMessage = "You've already hit or split this hand." elif hand["standing"]: - logMessage = "They're already standing" + log_message = "They're already standing" sendMessage = "You're already standing" elif len(hand["hand"]) != 2: - logMessage = "They tried to split after the first round" + log_message = "They tried to split after the first round" sendMessage = "You can only split on the first round" else: firstCard = self._calcHandValue([hand["hand"][0]]) secondCard = self._calcHandValue([hand["hand"][1]]) if firstCard != secondCard: - logMessage = "They tried to split two different cards" - sendMessage = self.bot.longStrings["Blackjack different cards"] + log_message = "They tried to split two different cards" + sendMessage = self.bot.long_strings["Blackjack different cards"] else: bet = hand["bet"] if self.bot.money.checkBalance(user) < bet: - logMessage = "They didn't have enough GwendoBucks" + log_message = "They didn't have enough GwendoBucks" sendMessage = "You don't have enough GwendoBucks" else: self.bot.money.addMoney(user, -1 * bet) @@ -972,13 +972,13 @@ class Blackjack(): game = blackjackGames.find_one({"_id": channel}) roundDone = self._isRoundDone(game) - sendMessage = self.bot.longStrings["Blackjack split"] - userName = self.bot.databaseFuncs.getName(user) + sendMessage = self.bot.long_strings["Blackjack split"] + userName = self.bot.database_funcs.getName(user) sendMessage = sendMessage.format(userName) - logMessage = "They succeeded" + log_message = "They succeeded" await ctx.send(sendMessage) - self.bot.log(logMessage) + self.bot.log(log_message) if roundDone: gameID = game["gameID"] @@ -1002,28 +1002,28 @@ class Blackjack(): user = f"#{ctx.author.id}" collection = self.bot.database["blackjack games"] game = collection.find_one({"_id": channel}) - userName = self.bot.databaseFuncs.getName(user) + userName = self.bot.database_funcs.getName(user) self.bot.log(f"{userName} is trying to join the Blackjack game") if game is None: sendMessage = "There is no game going on in this channel" - logMessage = sendMessage + log_message = sendMessage elif user in game["user hands"]: sendMessage = "You're already in the game!" - logMessage = "They're already in the game" + log_message = "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" + log_message = "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" + log_message = "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" + log_message = "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" + log_message = "They didn't have enough GwendoBucks" else: self.bot.money.addMoney(user, -1 * bet) playerHand = [self._drawCard(channel) for _ in range(2)] @@ -1048,9 +1048,9 @@ class Blackjack(): enterGameText = "entered the game with a bet of" betText = f"{bet} GwendoBucks" sendMessage = f"{userName} {enterGameText} {betText}" - logMessage = sendMessage + log_message = sendMessage - self.bot.log(logMessage) + self.bot.log(log_message) await ctx.send(sendMessage) async def start(self, ctx: discord_slash.context.SlashContext): @@ -1109,7 +1109,7 @@ class Blackjack(): gameStarted = True if gameStarted: - sendMessage = self.bot.longStrings["Blackjack started"] + sendMessage = self.bot.long_strings["Blackjack started"] await ctx.channel.send(sendMessage) tableImagesPath = "resources/games/blackjackTables/" @@ -1117,7 +1117,7 @@ class Blackjack(): oldImage = await ctx.channel.send(file=discord.File(filePath)) - with open("resources/games/oldImages/blackjack"+channel, "w") as f: + with open("resources/games/old_images/blackjack"+channel, "w") as f: f.write(str(oldImage.id)) await asyncio.sleep(30) @@ -1142,7 +1142,7 @@ class Blackjack(): new_message = self._blackjackFinish(channel) await ctx.channel.send(new_message) else: - sendMessage = self.bot.longStrings["Blackjack going on"] + sendMessage = self.bot.long_strings["Blackjack going on"] await ctx.channel.send(sendMessage) self.bot.log("There was already a game going on") @@ -1267,7 +1267,7 @@ class DrawBlackjack(): for x in range(len(hands)): key, value = list(hands.items())[x] - key = self.bot.databaseFuncs.getName(key) + key = self.bot.database_funcs.getName(key) handParams = [ value["hand"], False, diff --git a/funcs/games/connectFour.py b/funcs/games/connect_four.py similarity index 95% rename from funcs/games/connectFour.py rename to funcs/games/connect_four.py index c0d511d..4667882 100644 --- a/funcs/games/connectFour.py +++ b/funcs/games/connect_four.py @@ -73,8 +73,8 @@ class ConnectFour(): canStart = True if game is not None: - sendMessage = self.bot.longStrings["Connect 4 going on"] - logMessage = "There was already a game going on" + sendMessage = self.bot.long_strings["Connect 4 going on"] + log_message = "There was already a game going on" canStart = False elif type(opponent) == int: # Opponent is Gwendolyn @@ -84,7 +84,7 @@ class ConnectFour(): opponent = f"#{self.bot.user.id}" else: sendMessage = "Difficulty doesn't exist" - logMessage = "They challenged a difficulty that doesn't exist" + log_message = "They challenged a difficulty that doesn't exist" canStart = False elif type(opponent) == discord.User: if opponent.bot: @@ -96,7 +96,7 @@ class ConnectFour(): opponent = f"#{self.bot.user.id}" else: sendMessage = "You can't challenge a bot!" - logMessage = "They tried to challenge a bot" + log_message = "They tried to challenge a bot" canStart = False else: # Opponent is another player @@ -106,7 +106,7 @@ class ConnectFour(): diffText = "" else: sendMessage = "You can't play against yourself" - logMessage = "They tried to play against themself" + log_message = "They tried to play against themself" canStart = False if canStart: @@ -133,15 +133,15 @@ class ConnectFour(): gwendoTurn = (players[0] == f"#{self.bot.user.id}") startedGame = True - opponentName = self.bot.databaseFuncs.getName(opponent) - turnName = self.bot.databaseFuncs.getName(players[0]) + opponentName = self.bot.database_funcs.getName(opponent) + turnName = self.bot.database_funcs.getName(players[0]) startedText = f"Started game against {opponentName}{diffText}." turnText = f"It's {turnName}'s turn" sendMessage = f"{startedText} {turnText}" - logMessage = "They started a game" + log_message = "They started a game" - self.bot.log(logMessage) + self.bot.log(log_message) await ctx.send(sendMessage) # Sets the whole game in motion @@ -150,13 +150,13 @@ class ConnectFour(): filePath = f"{boardsPath}board{ctx.channel_id}.png" oldImage = await ctx.channel.send(file=discord.File(filePath)) - oldImagesPath = "resources/games/oldImages/" - oldImagePath = f"{oldImagesPath}connectFour{ctx.channel_id}" + old_imagesPath = "resources/games/old_images/" + oldImagePath = f"{old_imagesPath}connect_four{ctx.channel_id}" with open(oldImagePath, "w") as f: f.write(str(oldImage.id)) if gwendoTurn: - await self._connectFourAI(ctx) + await self._connect_fourAI(ctx) else: for reaction in self.REACTIONS: await oldImage.add_reaction(reaction) @@ -183,19 +183,19 @@ class ConnectFour(): connect4Games = self.bot.database["connect 4 games"] game = connect4Games.find_one({"_id": channel}) playerNumber = game["players"].index(user)+1 - userName = self.bot.databaseFuncs.getName(user) + userName = self.bot.database_funcs.getName(user) placedPiece = False if game is None: sendMessage = "There's no game in this channel" - logMessage = "There was no game in the channel" + log_message = "There was no game in the channel" else: board = game["board"] board = self._placeOnBoard(board, playerNumber, column) if board is None: sendMessage = "There isn't any room in that column" - logMessage = "There wasn't any room in the column" + log_message = "There wasn't any room in the column" else: updater = {"$set": {"board": board}} connect4Games.update_one({"_id": channel}, updater) @@ -217,7 +217,7 @@ class ConnectFour(): sendMessage = "{} placed a piece in column {} and won. " sendMessage = sendMessage.format(userName, column+1) - logMessage = f"{userName} won" + log_message = f"{userName} won" winAmount = int(game["difficulty"])**2+5 if game["players"][won-1] != f"#{self.bot.user.id}": sendMessage += "Adding {} GwendoBucks to their account" @@ -225,27 +225,27 @@ class ConnectFour(): elif 0 not in board[0]: gameWon = True sendMessage = "It's a draw!" - logMessage = "The game ended in a draw" + log_message = "The game ended in a draw" else: gameWon = False otherUserId = game["players"][turn] - otherUserName = self.bot.databaseFuncs.getName(otherUserId) - sendMessage = self.bot.longStrings["Connect 4 placed"] + otherUserName = self.bot.database_funcs.getName(otherUserId) + sendMessage = self.bot.long_strings["Connect 4 placed"] formatParams = [userName, column+1, otherUserName] sendMessage = sendMessage.format(*formatParams) - logMessage = "They placed the piece" + log_message = "They placed the piece" gwendoTurn = (game["players"][turn] == f"#{self.bot.user.id}") placedPiece = True await ctx.channel.send(sendMessage) - self.bot.log(logMessage) + self.bot.log(log_message) if placedPiece: self.draw.drawImage(channel) - oldImagePath = f"resources/games/oldImages/connectFour{channel}" + oldImagePath = f"resources/games/old_images/connect_four{channel}" with open(oldImagePath, "r") as f: oldImage = await ctx.channel.fetch_message(int(f.read())) @@ -263,7 +263,7 @@ class ConnectFour(): with open(oldImagePath, "w") as f: f.write(str(oldImage.id)) if gwendoTurn: - await self._connectFourAI(ctx) + await self._connect_fourAI(ctx) else: for reaction in self.REACTIONS: await oldImage.add_reaction(reaction) @@ -285,7 +285,7 @@ class ConnectFour(): loserIndex = game["players"].index(f"#{ctx.author.id}") winnerIndex = (loserIndex+1) % 2 winnerID = game["players"][winnerIndex] - winnerName = self.bot.databaseFuncs.getName(winnerID) + winnerName = self.bot.database_funcs.getName(winnerID) sendMessage = f"{ctx.author.display_name} surrenders." sendMessage += f" This means {winnerName} is the winner." @@ -295,7 +295,7 @@ class ConnectFour(): sendMessage += f" Adding {reward} to their account" await ctx.send(sendMessage) - oldImagePath = f"resources/games/oldImages/connectFour{channel}" + oldImagePath = f"resources/games/old_images/connect_four{channel}" with open(oldImagePath, "r") as f: oldImage = await ctx.channel.fetch_message(int(f.read())) @@ -353,7 +353,7 @@ class ConnectFour(): reward = difficulty**2 + 5 self.bot.money.addMoney(game["players"][winner-1], reward) - self.bot.databaseFuncs.deleteGame("connect 4 games", channel) + self.bot.database_funcs.deleteGame("connect 4 games", channel) def _isWon(self, board: dict): won = 0 @@ -429,7 +429,7 @@ class ConnectFour(): return won, winDirection, winCoordinates - async def _connectFourAI(self, ctx: SlashContext): + async def _connect_fourAI(self, ctx: SlashContext): def outOfRange(possibleScores: list): allowedRange = max(possibleScores)*(1-0.1) moreThanOne = len(possibleScores) != 1 @@ -1023,12 +1023,12 @@ class DrawConnectFour(): if game["players"][0] == "Gwendolyn": player1 = "Gwendolyn" else: - player1 = self.bot.databaseFuncs.getName(game["players"][0]) + player1 = self.bot.database_funcs.getName(game["players"][0]) if game["players"][1] == "Gwendolyn": player2 = "Gwendolyn" else: - player2 = self.bot.databaseFuncs.getName(game["players"][1]) + player2 = self.bot.database_funcs.getName(game["players"][1]) exampleHeight = self.HEIGHT - self.BORDER exampleHeight += (self.BOTTOMBORDER+self.BORDER)//2 - self.TEXTSIZE//2 diff --git a/funcs/games/gamesContainer.py b/funcs/games/games_container.py similarity index 90% rename from funcs/games/gamesContainer.py rename to funcs/games/games_container.py index 0169cc8..7c8a5b2 100644 --- a/funcs/games/gamesContainer.py +++ b/funcs/games/games_container.py @@ -11,7 +11,7 @@ Has a container for game functions. from .invest import Invest from .trivia import Trivia from .blackjack import Blackjack -from .connectFour import ConnectFour +from .connect_four import ConnectFour from .hangman import Hangman from .hex import HexGame @@ -28,7 +28,7 @@ class Games(): Contains investment functions. blackjack Contains blackjack functions. - connectFour + connect_four Contains connect four functions. hangman Contains hangman functions. @@ -43,6 +43,6 @@ class Games(): self.invest = Invest(bot) self.trivia = Trivia(bot) self.blackjack = Blackjack(bot) - self.connectFour = ConnectFour(bot) + self.connect_four = ConnectFour(bot) self.hangman = Hangman(bot) self.hex = HexGame(bot) diff --git a/funcs/games/hangman.py b/funcs/games/hangman.py index d702688..4b65d70 100644 --- a/funcs/games/hangman.py +++ b/funcs/games/hangman.py @@ -72,7 +72,7 @@ class Hangman(): 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) + userName = self.__bot.database_funcs.getName(user) startedGame = False if game is None: @@ -99,14 +99,14 @@ class Hangman(): self.__draw.drawImage(channel) - logMessage = "Game started" + log_message = "Game started" sendMessage = f"{userName} started game of hangman." startedGame = True else: - logMessage = "There was already a game going on" - sendMessage = self.__bot.longStrings["Hangman going on"] + log_message = "There was already a game going on" + sendMessage = self.__bot.long_strings["Hangman going on"] - self.__bot.log(logMessage) + self.__bot.log(log_message) await ctx.send(sendMessage) if startedGame: @@ -122,7 +122,7 @@ class Hangman(): oldMessages = f"{newImage.id}\n{blankMessage.id}" - with open(f"resources/games/oldImages/hangman{channel}", "w") as f: + with open(f"resources/games/old_images/hangman{channel}", "w") as f: f.write(oldMessages) for message, letters in reactionMessages.items(): @@ -149,7 +149,7 @@ class Hangman(): else: self.__bot.database["hangman games"].delete_one({"_id": channel}) - with open(f"resources/games/oldImages/hangman{channel}", "r") as f: + with open(f"resources/games/old_images/hangman{channel}", "r") as f: messages = f.read().splitlines() for message in messages: @@ -216,17 +216,17 @@ class Hangman(): if game["misses"] == 6: hangmanGames.delete_one({"_id": channel}) - sendMessage += self.__bot.longStrings["Hangman lost game"] + sendMessage += self.__bot.long_strings["Hangman lost game"] remainingLetters = [] elif all(game["guessed"]): hangmanGames.delete_one({"_id": channel}) self.__bot.money.addMoney(user, 15) - sendMessage += self.__bot.longStrings["Hangman guessed word"] + sendMessage += self.__bot.long_strings["Hangman guessed word"] remainingLetters = [] await message.channel.send(sendMessage) - with open(f"resources/games/oldImages/hangman{channel}", "r") as f: + with open(f"resources/games/old_images/hangman{channel}", "r") as f: oldMessageIDs = f.read().splitlines() for oldID in oldMessageIDs: @@ -254,7 +254,7 @@ class Hangman(): else: oldMessages = str(newImage.id) - oldImagePath = f"resources/games/oldImages/hangman{channel}" + oldImagePath = f"resources/games/old_images/hangman{channel}" with open(oldImagePath, "w") as f: f.write(oldMessages) diff --git a/funcs/games/hex.py b/funcs/games/hex.py index 43f74d1..4f5279b 100644 --- a/funcs/games/hex.py +++ b/funcs/games/hex.py @@ -28,11 +28,11 @@ class HexGame(): await ctx.send("You can't surrender when you're not a player.") else: opponent = (players.index(user) + 1) % 2 - opponentName = self.bot.databaseFuncs.getName(players[opponent]) + opponentName = self.bot.database_funcs.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") - with open(f"resources/games/oldImages/hex{channel}", "r") as f: + with open(f"resources/games/old_images/hex{channel}", "r") as f: oldImage = await ctx.channel.fetch_message(int(f.read())) if oldImage is not None: @@ -44,7 +44,7 @@ class HexGame(): 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: + with open(f"resources/games/old_images/hex{channel}", "w") as f: f.write(str(oldImage.id)) self.bot.database["hex games"].delete_one({"_id":channel}) @@ -72,10 +72,10 @@ class HexGame(): opponent = game["players"][::-1][game["turn"]-1] gwendoTurn = (opponent == f"#{self.bot.user.id}") - opponentName = self.bot.databaseFuncs.getName(opponent) + opponentName = self.bot.database_funcs.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: + with open(f"resources/games/old_images/hex{channel}", "r") as f: oldImage = await ctx.channel.fetch_message(int(f.read())) if oldImage is not None: @@ -87,7 +87,7 @@ class HexGame(): 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: + with open(f"resources/games/old_images/hex{channel}", "w") as f: f.write(str(oldImage.id)) if gwendoTurn: @@ -105,7 +105,7 @@ class HexGame(): if game != None: sendMessage = "There's already a hex game going on in this channel" - logMessage = "There was already a game going on" + log_message = "There was already a game going on" canStart = False else: if type(opponent) == int: @@ -117,7 +117,7 @@ class HexGame(): opponent = f"#{self.bot.user.id}" else: sendMessage = "Difficulty doesn't exist" - logMessage = "They tried to play against a difficulty that doesn't exist" + log_message = "They tried to play against a difficulty that doesn't exist" canStart = False elif type(opponent) == discord.member.Member: @@ -131,7 +131,7 @@ class HexGame(): opponent = f"#{self.bot.user.id}" else: sendMessage = "You can't challenge a bot!" - logMessage = "They tried to challenge a bot" + log_message = "They tried to challenge a bot" canStart = False else: # Opponent is another player @@ -142,11 +142,11 @@ class HexGame(): diffText = "" else: sendMessage = "You can't play against yourself" - logMessage = "They tried to play against themself" + log_message = "They tried to play against themself" canStart = False else: canStart = False - logMessage = f"Opponent was neither int or member. It was {type(opponent)}" + log_message = f"Opponent was neither int or member. It was {type(opponent)}" sendMessage = "Something went wrong" if canStart: @@ -167,18 +167,18 @@ class HexGame(): gwendoTurn = (players[0] == f"#{self.bot.user.id}") startedGame = True - turnName = self.bot.databaseFuncs.getName(players[0]) + turnName = self.bot.database_funcs.getName(players[0]) sendMessage = f"Started Hex game against {opponentName}{diffText}. It's {turnName}'s turn" - logMessage = "Game started" + log_message = "Game started" await ctx.send(sendMessage) - self.bot.log(logMessage) + self.bot.log(log_message) 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: + with open(f"resources/games/old_images/hex{ctx.channel_id}", "w") as f: f.write(str(newImage.id)) if gwendoTurn: @@ -199,7 +199,7 @@ class HexGame(): else: 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])}." + sendMessage = f"You can't place when you're not in the game. The game's players are: {self.bot.database_funcs.getName(game['players'][0])} and {self.bot.database_funcs.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" @@ -228,12 +228,12 @@ class HexGame(): 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) + sendMessage = self.bot.database_funcs.getName(game["players"][player-1])+" placed at "+position.upper()+". It's now "+self.bot.database_funcs.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!" + sendMessage = self.bot.database_funcs.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." @@ -258,7 +258,7 @@ class HexGame(): # Update the board self.draw.drawHexPlacement(channel,player, position) - with open(f"resources/games/oldImages/hex{channel}", "r") as f: + with open(f"resources/games/old_images/hex{channel}", "r") as f: oldImage = await ctx.channel.fetch_message(int(f.read())) if oldImage is not None: @@ -281,7 +281,7 @@ class HexGame(): self.bot.database["hex games"].delete_one({"_id":channel}) else: - with open(f"resources/games/oldImages/hex{channel}", "w") as f: + with open(f"resources/games/old_images/hex{channel}", "w") as f: f.write(str(oldImage.id)) if gwendoTurn: @@ -321,7 +321,7 @@ class HexGame(): sendMessage = "It's not your turn" else: turn = game["turn"] - self.bot.log("Undoing {}'s last move".format(self.bot.databaseFuncs.getName(user))) + self.bot.log("Undoing {}'s last move".format(self.bot.database_funcs.getName(user))) lastMove = game["gameHistory"].pop() game["board"][lastMove[0]][lastMove[1]] = 0 @@ -337,7 +337,7 @@ class HexGame(): await ctx.send(sendMessage) if undid: - with open(f"resources/games/oldImages/hex{channel}", "r") as f: + with open(f"resources/games/old_images/hex{channel}", "r") as f: oldImage = await ctx.channel.fetch_message(int(f.read())) if oldImage is not None: @@ -349,7 +349,7 @@ class HexGame(): 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: + with open(f"resources/games/old_images/hex{channel}", "w") as f: f.write(str(oldImage.id)) @@ -556,7 +556,7 @@ class DrawHex(): game = self.bot.database["hex games"].find_one({"_id":channel}) for p in [1,2]: - playername = self.bot.databaseFuncs.getName(game["players"][p-1]) + playername = self.bot.database_funcs.getName(game["players"][p-1]) # Draw name x = self.XNAME[p] x -= self.NAMEFONT.getsize(playername)[0] if p==2 else 0 # player2's name is right-aligned @@ -620,7 +620,7 @@ class DrawHex(): # Write player names and color for p in [1,2]: - playername = self.bot.databaseFuncs.getName(game["players"][p%2]) + playername = self.bot.database_funcs.getName(game["players"][p%2]) x = self.XNAME[p] x -= self.NAMEFONT.getsize(playername)[0] if p==2 else 0 # player2's name is right-aligned diff --git a/funcs/games/invest.py b/funcs/games/invest.py index 2501353..5e65c90 100644 --- a/funcs/games/invest.py +++ b/funcs/games/invest.py @@ -42,7 +42,7 @@ class Invest(): price: int The price of the stock. """ - res = self.bot.finnhubClient.quote(symbol.upper()) + res = self.bot.finnhub_client.quote(symbol.upper()) if res == {}: return 0 else: @@ -65,7 +65,7 @@ class Invest(): investmentsDatabase = self.bot.database["investments"] userInvestments = investmentsDatabase.find_one({"_id": user}) - userName = self.bot.databaseFuncs.getName(user) + userName = self.bot.database_funcs.getName(user) if userInvestments in [None, {}]: return f"{userName} does not have a stock portfolio." @@ -162,7 +162,7 @@ class Invest(): } investmentsDatabase.insert_one(newUser) - userName = self.bot.databaseFuncs.getName(user) + userName = self.bot.database_funcs.getName(user) sendMessage = "{} bought {} GwendoBucks worth of {} stock" sendMessage = sendMessage.format(userName, buyAmount, stock) return sendMessage @@ -219,7 +219,7 @@ class Invest(): updater = {"$unset": {f"investments.{stock}": ""}} investmentsDatabase.update_one({"_id": user}, updater) - userName = self.bot.databaseFuncs.getName(user) + userName = self.bot.database_funcs.getName(user) sendMessage = "{} sold {} GwendoBucks worth of {} stock" return sendMessage.format(userName, sellAmount, stock) else: @@ -252,7 +252,7 @@ class Invest(): response = response.format(commands[0].upper()) else: price = f"{price:,}".replace(",", ".") - response = self.bot.longStrings["Stock value"] + response = self.bot.long_strings["Stock value"] response = response.format(commands[1].upper(), price) elif parameters.startswith("buy"): @@ -260,14 +260,14 @@ class Invest(): if len(commands) == 3: response = self.buyStock(user, commands[1], int(commands[2])) else: - response = self.bot.longStrings["Stock parameters"] + response = self.bot.long_strings["Stock parameters"] elif parameters.startswith("sell"): commands = parameters.split(" ") if len(commands) == 3: response = self.sellStock(user, commands[1], int(commands[2])) else: - response = self.bot.longStrings["Stock parameters"] + response = self.bot.long_strings["Stock parameters"] else: response = "Incorrect parameters" @@ -280,7 +280,7 @@ class Invest(): "description": text, "colour": 0x00FF00 } - em = discord.Embed(*embedParams) + em = discord.Embed(**embedParams) await ctx.send(embed=em) else: await ctx.send(response) diff --git a/funcs/games/money.py b/funcs/games/money.py index c626a91..1113fbe 100644 --- a/funcs/games/money.py +++ b/funcs/games/money.py @@ -98,7 +98,7 @@ class Money(): else: newUser = { "_id": user, - "user name": self.bot.databaseFuncs.getName(user), + "user name": self.bot.database_funcs.getName(user), "money": amount } self.database["users"].insert_one(newUser) @@ -120,7 +120,7 @@ class Money(): """ await self.bot.defer(ctx) username = user.display_name - if self.bot.databaseFuncs.getID(username) is None: + if self.bot.database_funcs.getID(username) is None: async for member in ctx.guild.fetch_members(limit=None): if member.display_name.lower() == username.lower(): username = member.display_name @@ -134,7 +134,7 @@ class Money(): userid = f"#{ctx.author.id}" userData = self.database["users"].find_one({"_id": userid}) - targetUser = self.bot.databaseFuncs.getID(username) + targetUser = self.bot.database_funcs.getID(username) if amount <= 0: self.bot.log("They tried to steal") diff --git a/funcs/games/trivia.py b/funcs/games/trivia.py index e01fc1d..8068e65 100644 --- a/funcs/games/trivia.py +++ b/funcs/games/trivia.py @@ -91,9 +91,9 @@ class Trivia(): return question, answers, correctAnswer else: - logMessage = "There was already a trivia question for that channel" - self.bot.log(logMessage) - return self.bot.longStrings["Trivia going on"], "", "" + log_message = "There was already a trivia question for that channel" + self.bot.log(log_message) + return self.bot.long_strings["Trivia going on"], "", "" def triviaAnswer(self, user: str, channel: str, command: str): """ @@ -182,10 +182,10 @@ class Trivia(): self.triviaCountPoints(channelId) deleteGameParams = ["trivia questions", channelId] - self.bot.databaseFuncs.deleteGame(*deleteGameParams) + self.bot.database_funcs.deleteGame(*deleteGameParams) self.bot.log("Time's up for the trivia question", channelId) - sendMessage = self.bot.longStrings["Trivia time up"] + sendMessage = self.bot.long_strings["Trivia time up"] formatParams = [chr(correctAnswer), options[correctAnswer-97]] sendMessage = sendMessage.format(*formatParams) await ctx.send(sendMessage) diff --git a/funcs/lookup/__init__.py b/funcs/lookup/__init__.py index 2216bf2..2329a0a 100644 --- a/funcs/lookup/__init__.py +++ b/funcs/lookup/__init__.py @@ -2,4 +2,4 @@ __all__ = ["LookupFuncs"] -from .lookupFuncs import LookupFuncs \ No newline at end of file +from .lookup_funcs import LookupFuncs \ No newline at end of file diff --git a/funcs/lookup/lookupFuncs.py b/funcs/lookup/lookup_funcs.py similarity index 100% rename from funcs/lookup/lookupFuncs.py rename to funcs/lookup/lookup_funcs.py diff --git a/funcs/other/generators.py b/funcs/other/generators.py index 37f66f9..a1621b3 100644 --- a/funcs/other/generators.py +++ b/funcs/other/generators.py @@ -64,7 +64,7 @@ class Generators(): if random.randint(1,10) > 1: try: new_letter = random.choice(letter_dict[chain[-2]+chain[-1]]) - except: + except KeyError(): new_letter = random.choice(letter_dict[chain[-1]]) else: new_letter = random.choice(letter_dict[chain[-1]]) diff --git a/funcs/starWarsFuncs/__init__.py b/funcs/star_wars_funcs/__init__.py similarity index 70% rename from funcs/starWarsFuncs/__init__.py rename to funcs/star_wars_funcs/__init__.py index 041ebad..8089a50 100644 --- a/funcs/starWarsFuncs/__init__.py +++ b/funcs/star_wars_funcs/__init__.py @@ -2,4 +2,4 @@ __all__ = ["StarWars"] -from .starWars import StarWars \ No newline at end of file +from .star_wars import StarWars \ No newline at end of file diff --git a/funcs/starWarsFuncs/starWars.py b/funcs/star_wars_funcs/star_wars.py similarity index 62% rename from funcs/starWarsFuncs/starWars.py rename to funcs/star_wars_funcs/star_wars.py index 48e6f84..43fb417 100644 --- a/funcs/starWarsFuncs/starWars.py +++ b/funcs/star_wars_funcs/star_wars.py @@ -1,6 +1,6 @@ -from .starWarsChar import StarWarsChar -from .starWarsRoll import StarWarsRoll -from .starWarsDestiny import StarWarsDestiny +from .star_wars_char import StarWarsChar +from .star_wars_roll import StarWarsRoll +from .star_wars_destiny import StarWarsDestiny class StarWars(): def __init__(self, bot): diff --git a/funcs/starWarsFuncs/starWarsChar.py b/funcs/star_wars_funcs/star_wars_char.py similarity index 97% rename from funcs/starWarsFuncs/starWarsChar.py rename to funcs/star_wars_funcs/star_wars_char.py index 30cb2f3..b90e8d3 100644 --- a/funcs/starWarsFuncs/starWarsChar.py +++ b/funcs/star_wars_funcs/star_wars_char.py @@ -7,15 +7,15 @@ class StarWarsChar(): self.bot = bot def getCharName(self, user : str): - self.bot.log("Getting name for "+self.bot.databaseFuncs.getName(user)+"'s character") + self.bot.log("Getting name for "+self.bot.database_funcs.getName(user)+"'s character") userCharacter = self.bot.database["starwars characters"].find_one({"_id":user}) if userCharacter != None: self.bot.log("Name is "+userCharacter["Name"]) return userCharacter["Name"] else: - self.bot.log("Just using "+self.bot.databaseFuncs.getName(user)) - return self.bot.databaseFuncs.getName(user) + self.bot.log("Just using "+self.bot.database_funcs.getName(user)) + return self.bot.database_funcs.getName(user) def setUpDict(self, cmd : dict): self.bot.log("Setting up a dictionary in a nice way") @@ -252,7 +252,7 @@ class StarWarsChar(): if cmd == "": break - self.bot.log("Looking for "+self.bot.databaseFuncs.getName(user)+"'s character") + self.bot.log("Looking for "+self.bot.database_funcs.getName(user)+"'s character") if userCharacter != None: self.bot.log("Found it! Looking for "+key+" in the data") if key in userCharacter: @@ -303,7 +303,7 @@ class StarWarsChar(): return cmd[0]+" added to "+key+" for " + userCharacter["Name"] elif key == "Weapons": - with open("resources/starWars/starwarstemplates.json", "r") as f: + with open("resources/star_wars/starwarstemplates.json", "r") as f: templates = json.load(f) newWeapon = templates["Weapon"] self.bot.log("Adding "+cmd+" to "+key) @@ -495,18 +495,18 @@ class StarWarsChar(): 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: + self.bot.log("Makin' a character for "+self.bot.database_funcs.getName(user)) + with open("resources/star_wars/starwarstemplates.json", "r") as f: templates = json.load(f) newChar = templates["Character"] newChar["_id"] = user self.bot.database["starwars characters"].insert_one(newChar) - await ctx.send("Character for " + self.bot.databaseFuncs.getName(user) + " created") + await ctx.send("Character for " + self.bot.database_funcs.getName(user) + " created") else: if cmd == "Purge": - self.bot.log("Deleting "+self.bot.databaseFuncs.getName(user)+"'s character") + self.bot.log("Deleting "+self.bot.database_funcs.getName(user)+"'s character") self.bot.database["starwars characters"].delete_one({"_id":user}) - await ctx.send("Character for " + self.bot.databaseFuncs.getName(user) + " deleted") + await ctx.send("Character for " + self.bot.database_funcs.getName(user) + " deleted") else: await ctx.send(self.replaceWithSpaces(str(self.charData(user,cmd)))) diff --git a/funcs/starWarsFuncs/starWarsDestiny.py b/funcs/star_wars_funcs/star_wars_destiny.py similarity index 76% rename from funcs/starWarsFuncs/starWarsDestiny.py rename to funcs/star_wars_funcs/star_wars_destiny.py index 527d31a..b1fac7f 100644 --- a/funcs/starWarsFuncs/starWarsDestiny.py +++ b/funcs/star_wars_funcs/star_wars_destiny.py @@ -4,16 +4,16 @@ class StarWarsDestiny(): def destinyNew(self, num : int): self.bot.log("Creating a new destiny pool with "+str(num)+" players") - roll, diceResults = self.bot.starWars.roll.roll(0,0,0,0,0,0,num) + roll, diceResults = self.bot.star_wars.roll.roll(0,0,0,0,0,0,num) roll = "".join(sorted(roll)) - with open("resources/starWars/destinyPoints.txt","wt") as f: + with open("resources/star_wars/destinyPoints.txt","wt") as f: f.write(roll) - return "Rolled for Destiny Points and got:\n"+self.bot.starWars.roll.diceResultToEmoji(diceResults)+"\n"+self.bot.starWars.roll.resultToEmoji(roll) + return "Rolled for Destiny Points and got:\n"+self.bot.star_wars.roll.diceResultToEmoji(diceResults)+"\n"+self.bot.star_wars.roll.resultToEmoji(roll) def destinyUse(self, user : str): - with open("resources/starWars/destinyPoints.txt","rt") as f: + with open("resources/star_wars/destinyPoints.txt","rt") as f: points = f.read() if user == "Nikolaj": @@ -21,10 +21,10 @@ class StarWarsDestiny(): if 'B' in points: points = points.replace("B","L",1) points = "".join(sorted(points)) - with open("resources/starWars/destinyPoints.txt","wt") as f: + with open("resources/star_wars/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.starWars.roll.resultToEmoji(points) + return "Used a dark side destiny point. Destiny pool is now:\n"+self.bot.star_wars.roll.resultToEmoji(points) else: self.bot.log("There were no dark side destiny points") return "No dark side destiny points" @@ -33,10 +33,10 @@ class StarWarsDestiny(): if 'L' in points: points = points.replace("L","B",1) points = "".join(sorted(points)) - with open("resources/starWars/destinyPoints.txt","wt") as f: + with open("resources/star_wars/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.starWars.roll.resultToEmoji(points) + return "Used a light side destiny point. Destiny pool is now:\n"+self.bot.star_wars.roll.resultToEmoji(points) else: self.bot.log("There were no dark side destiny points") return "No light side destiny points" @@ -51,8 +51,8 @@ class StarWarsDestiny(): if cmd == "": self.bot.log("Retrieving destiny pool info") - with open("resources/starWars/destinyPoints.txt","rt") as f: - sendMessage = self.bot.starWars.roll.resultToEmoji(f.read()) + with open("resources/star_wars/destinyPoints.txt","rt") as f: + sendMessage = self.bot.star_wars.roll.resultToEmoji(f.read()) else: commands = cmd.upper().split(" ") if commands[0] == "N": diff --git a/funcs/starWarsFuncs/starWarsRoll.py b/funcs/star_wars_funcs/star_wars_roll.py similarity index 96% rename from funcs/starWarsFuncs/starWarsRoll.py rename to funcs/star_wars_funcs/star_wars_roll.py index e7174ad..07ad6e7 100644 --- a/funcs/starWarsFuncs/starWarsRoll.py +++ b/funcs/star_wars_funcs/star_wars_roll.py @@ -3,7 +3,7 @@ import re import string import json -with open("resources/starWars/starwarsskills.json", "r") as f: +with open("resources/star_wars/starwarsskills.json", "r") as f: skillData = json.load(f) class StarWarsRoll(): @@ -302,7 +302,7 @@ class StarWarsRoll(): cmd = re.sub(' +',' ',cmd.upper()) + " " if cmd[0] == " ": cmd = cmd[1:] - cmd = self.bot.starWars.character.replaceSpaces(string.capwords(cmd)) + cmd = self.bot.star_wars.character.replaceSpaces(string.capwords(cmd)) commands = cmd.split(" ") validCommand = False @@ -316,15 +316,15 @@ class StarWarsRoll(): elif string.capwords(commands[0]) in skillData: self.bot.log("Oh look! This guy has skills!") - if self.bot.starWars.character.userHasChar(user): + if self.bot.star_wars.character.userHasChar(user): self.bot.log("They have a character. That much we know") - skillLevel = self.bot.starWars.character.charData(user,"Skills " + string.capwords(commands[0])) + skillLevel = self.bot.star_wars.character.charData(user,"Skills " + string.capwords(commands[0])) if string.capwords(commands[0]) == "Lightsaber": self.bot.log("The skill is lightsaber") - charLevel = self.bot.starWars.character.charData(user,"Characteristics " + self.bot.starWars.character.lightsaberChar(user)) + charLevel = self.bot.star_wars.character.charData(user,"Characteristics " + self.bot.star_wars.character.lightsaberChar(user)) else: - charLevel = self.bot.starWars.character.charData(user,"Characteristics " + skillData[string.capwords(commands[0])]) + charLevel = self.bot.star_wars.character.charData(user,"Characteristics " + skillData[string.capwords(commands[0])]) abilityDice = abs(charLevel-skillLevel) proficiencyDice = min(skillLevel,charLevel) @@ -372,7 +372,7 @@ class StarWarsRoll(): simplified = self.simplify(rollResults) - name = self.bot.starWars.character.getCharName(user) + name = self.bot.star_wars.character.getCharName(user) self.bot.log("Returns results and simplified results") diff --git a/Gwendolyn.py b/gwendolyn.py similarity index 65% rename from Gwendolyn.py rename to gwendolyn.py index 20342c5..fe71638 100644 --- a/Gwendolyn.py +++ b/gwendolyn.py @@ -5,20 +5,21 @@ Contains the Gwendolyn class, and runs it when run as script. --------- Gwendolyn(discord.ext.commands.Bot) """ +import platform # Used to test if the bot is running on windows, in + # order to fix a bug with asyncio +import asyncio # used to set change the loop policy if the bot is + # running on windows + import os # Used for loading cogs in Gwendolyn.addCogs import finnhub # Used to add a finhub client to the bot -import platform # Used to test if the bot is running on windows, in -# order to fix a bug with asyncio -import asyncio # used to set change the loop policy if the bot is -# running on windows import discord # Used for discord.Intents and discord.Status import discord_slash # Used to initialized SlashCommands object from discord.ext import commands # Used to inherit from commands.bot from pymongo import MongoClient # Used for database management from funcs import Money, StarWars, Games, Other, LookupFuncs -from utils import (Options, Credentials, logThis, makeFiles, databaseFuncs, - EventHandler, ErrorHandler, longStrings) +from utils import (Options, Credentials, logThis, makeFiles, DatabaseFuncs, + EventHandler, ErrorHandler, long_strings) class Gwendolyn(commands.Bot): @@ -37,66 +38,66 @@ class Gwendolyn(commands.Bot): """Initialize the bot.""" intents = discord.Intents.default() intents.members = True - initParams = { + initiation_parameters = { "command_prefix": " ", "case_insensitive": True, "intents": intents, "status": discord.Status.dnd } - super().__init__(**initParams) + super().__init__(**initiation_parameters) - self._addClientsAndOptions() - self._addUtilClasses() - self._addFunctionContainers() - self._addCogs() + self._add_clients_and_options() + self._add_util_classes() + self._add_function_containers() + self._add_cogs() - def _addClientsAndOptions(self): + def _add_clients_and_options(self): """Add all the client, option and credentials objects.""" - self.longStrings = longStrings() + self.long_strings = long_strings() self.options = Options() self.credentials = Credentials() - finnhubKey = self.credentials.finnhubKey - self.finnhubClient = finnhub.Client(api_key=finnhubKey) - mongoUser = self.credentials.mongoDBUser - mongoPassword = self.credentials.mongoDBPassword - mongoLink = f"mongodb+srv://{mongoUser}:{mongoPassword}@gwendolyn" - mongoLink += ".qkwfy.mongodb.net/Gwendolyn?retryWrites=true&w=majority" - dataBaseClient = MongoClient(mongoLink) + finnhub_key = self.credentials.finnhub_key + self.finnhub_client = finnhub.Client(api_key=finnhub_key) + mongo_user = self.credentials.mongoDBUser + mongo_password = self.credentials.mongoDBPassword + mongo_url = f"mongodb+srv://{mongo_user}:{mongo_password}@gwendolyn" + mongo_url += ".qkwfy.mongodb.net/Gwendolyn?retryWrites=true&w=majority" + database_clint = MongoClient(mongo_url) if self.options.testing: self.log("Testing mode") - self.database = dataBaseClient["Gwendolyn-Test"] + self.database = database_clint["Gwendolyn-Test"] else: - self.database = dataBaseClient["Gwendolyn"] + self.database = database_clint["Gwendolyn"] - def _addUtilClasses(self): + def _add_util_classes(self): """Add all the classes used as utility.""" - self.databaseFuncs = databaseFuncs(self) - self.eventHandler = EventHandler(self) - self.errorHandler = ErrorHandler(self) - slashParams = { + self.database_funcs = DatabaseFuncs(self) + self.event_handler = EventHandler(self) + self.error_handler = ErrorHandler(self) + slash_parameters = { "sync_commands": True, "sync_on_cog_reload": True, "override_type": True } - self.slash = discord_slash.SlashCommand(self, **slashParams) + self.slash = discord_slash.SlashCommand(self, **slash_parameters) - def _addFunctionContainers(self): + def _add_function_containers(self): """Add all the function containers used for commands.""" - self.starWars = StarWars(self) + self.star_wars = StarWars(self) self.other = Other(self) - self.lookupFuncs = LookupFuncs(self) + self.lookup_funcs = LookupFuncs(self) self.games = Games(self) self.money = Money(self) - def _addCogs(self): + def _add_cogs(self): """Load cogs.""" for filename in os.listdir("./cogs"): if filename.endswith(".py"): self.load_extension(f"cogs.{filename[:-3]}") def log(self, messages, channel: str = "", level: int = 20): - """Log a message. Described in utils/utilFunctions.py.""" + """Log a message. Described in utils/util_functions.py.""" logThis(messages, channel, level) async def stop(self, ctx: discord_slash.context.SlashContext): @@ -116,13 +117,13 @@ class Gwendolyn(commands.Bot): await self.change_presence(status=discord.Status.offline) - self.databaseFuncs.wipeGames() + self.database_funcs.wipeGames() self.log("Logging out", level=25) await self.close() else: - logMessage = f"{ctx.author.display_name} tried to stop me!" - self.log(logMessage, str(ctx.channel_id)) + log_message = f"{ctx.author.display_name} tried to stop me!" + self.log(log_message, str(ctx.channel_id)) await ctx.send(f"I don't think I will, {ctx.author.display_name}") async def defer(self, ctx: discord_slash.context.SlashContext): @@ -146,5 +147,5 @@ if __name__ == "__main__": try: # Runs the whole shabang bot.run(bot.credentials.token) - except Exception: - bot.log(bot.longStrings["Can't log in"]) + except Exception as exception: # pylint: disable=broad-except + bot.log(bot.long_strings[f"Can't log in: {repr(exception)}"]) diff --git a/project-guidelines.md b/project-guidelines.md index 212f61c..33ef2b4 100644 --- a/project-guidelines.md +++ b/project-guidelines.md @@ -42,8 +42,8 @@ Comments, strings, variable names, class names, docstrings, as well as all other # Code ## 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. +All the Python code should follow the [PEP 8 guidelines](https://www.python.org/dev/peps/pep-0008/), with the following additions: ++ Variable and function names must fully consist of either full words or common/understandable abbreviations. + Use f-strings when applicable. ### Documentation diff --git a/resources/games/oldImages/blackjack740652054388932682 b/resources/games/oldImages/blackjack740652054388932682 new file mode 100644 index 0000000..a5f44f7 --- /dev/null +++ b/resources/games/oldImages/blackjack740652054388932682 @@ -0,0 +1 @@ +854063181868695593 \ No newline at end of file diff --git a/resources/games/oldImages/hangman740652054388932682 b/resources/games/oldImages/hangman740652054388932682 new file mode 100644 index 0000000..5372483 --- /dev/null +++ b/resources/games/oldImages/hangman740652054388932682 @@ -0,0 +1,2 @@ +854064833909489734 +854064835040641044 \ No newline at end of file diff --git a/resources/games/oldImages/hex740652054388932682 b/resources/games/oldImages/hex740652054388932682 new file mode 100644 index 0000000..4147771 --- /dev/null +++ b/resources/games/oldImages/hex740652054388932682 @@ -0,0 +1 @@ +854064794356023346 \ No newline at end of file diff --git a/resources/help/help-connectfour.txt b/resources/help/help-connectfour.txt index ee48f68..622dc19 100644 --- a/resources/help/help-connectfour.txt +++ b/resources/help/help-connectfour.txt @@ -1 +1 @@ -Brug `/connectFour start` til at starte et spil imod Gwendolyn. Brug `/connectFour start [modstander]` for at spille imod en anden person. Du kan også bruge `/connectFour start [1-5]`, hvor tallet er sværhedsgraden af Gwendolyn du gerne vil spille imod. \ No newline at end of file +Brug `/connect_four start` til at starte et spil imod Gwendolyn. Brug `/connect_four start [modstander]` for at spille imod en anden person. Du kan også bruge `/connect_four start [1-5]`, hvor tallet er sværhedsgraden af Gwendolyn du gerne vil spille imod. \ No newline at end of file diff --git a/resources/help/help-star_wars_character.txt b/resources/help/help-star_wars_character.txt new file mode 100644 index 0000000..a3a271b --- /dev/null +++ b/resources/help/help-star_wars_character.txt @@ -0,0 +1 @@ + Du kan bruge kommandoer som `/star_wars_character name Jared` eller `/star_wars_character skills astrogation 3` til at ændre din karakters info. Kommandoen `/star_wars_character` vil give dig et character sheet for din karakter. \ No newline at end of file diff --git a/resources/help/help-starwarsroll.txt b/resources/help/help-star_wars_roll.txt similarity index 86% rename from resources/help/help-starwarsroll.txt rename to resources/help/help-star_wars_roll.txt index 4876bf2..f94e0e3 100644 --- a/resources/help/help-starwarsroll.txt +++ b/resources/help/help-star_wars_roll.txt @@ -1 +1 @@ -Lader dig rulle Star Wars terninger. Du kan skrive tal der repræsenterer antallet af hver terning i rækkefølgen: ability, proficiency, difficulty, challenge, boost, setback og force. Du behøver ikke skrive et tal til alle terningerne. Du kan også skrive forbogstavet for terningen du vil rulle før antallet, såsom "/starWarsRoll f2", der ruller 2 force terninger. \ No newline at end of file +Lader dig rulle Star Wars terninger. Du kan skrive tal der repræsenterer antallet af hver terning i rækkefølgen: ability, proficiency, difficulty, challenge, boost, setback og force. Du behøver ikke skrive et tal til alle terningerne. Du kan også skrive forbogstavet for terningen du vil rulle før antallet, såsom "/star_wars_roll f2", der ruller 2 force terninger. \ No newline at end of file diff --git a/resources/help/help-starwarscharacter.txt b/resources/help/help-starwarscharacter.txt deleted file mode 100644 index 794add6..0000000 --- a/resources/help/help-starwarscharacter.txt +++ /dev/null @@ -1 +0,0 @@ - Du kan bruge kommandoer som `/starWarsCharacter name Jared` eller `/starWarsCharacter skills astrogation 3` til at ændre din karakters info. Kommandoen `/starWarsCharacter` vil give dig et character sheet for din karakter. \ No newline at end of file diff --git a/resources/help/help.txt b/resources/help/help.txt index 9501046..e653eba 100644 --- a/resources/help/help.txt +++ b/resources/help/help.txt @@ -7,13 +7,13 @@ `/name` - Genererer et tilfældigt navn. `/tavern` - Genererer en tilfældig tavern. `/give` - Lader dig give GwendoBucks til andre. -`/starWarsCharacter` - Lader dig lave en Star Wars karakter. -`/starWarsRoll` - Lader dig rulle Star Wars terninger. +`/star_wars_character` - Lader dig lave en Star Wars karakter. +`/star_wars_roll` - Lader dig rulle Star Wars terninger. `/balance` - Viser dig hvor mange GwendoBucks du har. `/invest` - Lader dig investere dine GwendoBucks i aktiemarkedet. `/blackjack` - Lader dig spille et spil blackjack. `/trivia` - Lader dig spille et spil trivia, hvor du kan tjene GwendoBucks. -`/connectFour` - Lader dig spille et spil fire på stribe. +`/connect_four` - Lader dig spille et spil fire på stribe. `/hex` - Lader dig spille et spil Hex. `/hangman` - Lader dig spille et spil hangman. `/wolf` - Lader dig slå ting op på Wolfram Alpha. diff --git a/resources/longStrings.json b/resources/long_strings.json similarity index 100% rename from resources/longStrings.json rename to resources/long_strings.json diff --git a/resources/slashParameters.json b/resources/slashParameters.json index 876bc46..e4bd1ca 100644 --- a/resources/slashParameters.json +++ b/resources/slashParameters.json @@ -112,8 +112,8 @@ "name" : "start", "description" : "Start a game of blackjack" }, - "connectFourStartGwendolyn" : { - "base" : "connectFour", + "connect_fourStartGwendolyn" : { + "base" : "connect_four", "subcommand_group" : "start", "name" : "Gwendolyn", "description" : "Start a game of connect four against Gwendolyn", @@ -126,8 +126,8 @@ } ] }, - "connectFourStartUser" : { - "base" : "connectFour", + "connect_fourStartUser" : { + "base" : "connect_four", "subcommand_group" : "start", "name" : "user", "description" : "Start a game of connect four against another user", @@ -140,8 +140,8 @@ } ] }, - "connectFourSurrender" : { - "base" : "connectFour", + "connect_fourSurrender" : { + "base" : "connect_four", "name" : "surrender", "description" : "Surrender the game of connect four" }, @@ -333,8 +333,8 @@ } ] }, - "starWarsCharacter" : { - "name" : "starWarsCharacter", + "star_wars_character" : { + "name" : "star_wars_character", "description" : "Manage your Star Wars character sheet", "options" : [ { @@ -345,8 +345,8 @@ } ] }, - "starWarsCrit" : { - "name" : "starWarsCrit", + "star_wars_crit" : { + "name" : "star_wars_crit", "description" : "Roll a Star Wars critical injury", "options" : [ { @@ -357,8 +357,8 @@ } ] }, - "starWarsDestiny" : { - "name" : "starWarsDestiny", + "star_wars_destiny" : { + "name" : "star_wars_destiny", "description" : "Use and see Star Wars Destiny points", "options" : [ { @@ -369,8 +369,8 @@ } ] }, - "starWarsRoll" : { - "name" : "starWarsRoll", + "star_wars_roll" : { + "name" : "star_wars_roll", "description" : "Roll Star Wars dice", "options" : [ { diff --git a/resources/starWars/starwarsskills.json b/resources/star_wars/starwarsskills.json similarity index 100% rename from resources/starWars/starwarsskills.json rename to resources/star_wars/starwarsskills.json diff --git a/resources/starWars/starwarstemplates.json b/resources/star_wars/starwarstemplates.json similarity index 100% rename from resources/starWars/starwarstemplates.json rename to resources/star_wars/starwarstemplates.json diff --git a/resources/startingFiles.json b/resources/startingFiles.json index b657456..1ad09a0 100644 --- a/resources/startingFiles.json +++ b/resources/startingFiles.json @@ -56,9 +56,9 @@ ] }, "txt": { - "resources/starWars/destinyPoints.txt": "", + "resources/star_wars/destinyPoints.txt": "", "resources/movies.txt": "The Room", - "resources/names.txt": "Gandalf", + "resources/names.txt": "Gandalf\n", "credentials.txt" : "Bot token: TOKEN\nFinnhub API key: KEY\nWordnik API Key: KEY\nMongoDB user: USERNAME\nMongoDB password: PASSWORD\nWolframAlpha AppID: APPID\nRadarr API key: KEY\nSonarr API key: KEY", "options.txt" : "Testing: True\nTesting guild ids:\nAdmins:" }, @@ -69,6 +69,6 @@ "resources/games/hexBoards", "resources/games/hangmanBoards", "resources/bedreNetflix", - "resources/games/oldImages" + "resources/games/old_images" ] } \ No newline at end of file diff --git a/utils/__init__.py b/utils/__init__.py index 43fa506..b6ad44b 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -1,10 +1,10 @@ """A collections of utilities used by Gwendolyn and her functions.""" -__all__ = ["Options", "Credentials", "databaseFuncs", "EventHandler", +__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, longStrings) +from .helper_classes import Options, Credentials, DatabaseFuncs +from .event_handlers import EventHandler, ErrorHandler +from .util_functions import (getParams, logThis, cap, makeFiles, + replaceMultiple, emojiToCommand, long_strings) diff --git a/utils/eventHandlers.py b/utils/event_handlers.py similarity index 87% rename from utils/eventHandlers.py rename to utils/event_handlers.py index 1ec9810..6b87b39 100644 --- a/utils/eventHandlers.py +++ b/utils/event_handlers.py @@ -15,7 +15,7 @@ from discord.ext import commands # Used to compare errors with command # errors from discord_slash.context import SlashContext -from utils.utilFunctions import emojiToCommand +from utils.util_functions import emojiToCommand class EventHandler(): @@ -35,7 +35,9 @@ class EventHandler(): async def on_ready(self): """Log and sets status when it logs in.""" - await self.bot.databaseFuncs.syncCommands() + slashCommandList = await self.bot.slash.to_dict() + print(slashCommandList['guild'][740652054388932679][13]) + await self.bot.database_funcs.syncCommands() name = self.bot.user.name userid = str(self.bot.user.id) loggedInMessage = f"Logged in as {name}, {userid}" @@ -59,14 +61,14 @@ class EventHandler(): args = " ".join([str(i) for i in ctx.args]) fullCommand = f"/{ctx.command}{subcommand}{subcommandGroup}{args}" - logMessage = f"{ctx.author.display_name} ran {fullCommand}" - self.bot.log(logMessage, str(ctx.channel_id), level=25) + log_message = f"{ctx.author.display_name} ran {fullCommand}" + self.bot.log(log_message, str(ctx.channel_id), level=25) async def on_reaction_add(self, reaction: discord.Reaction, user: discord.User): """Take action if the reaction is on a command message.""" if not user.bot: - tests = self.bot.databaseFuncs + tests = self.bot.database_funcs message = reaction.message channel = message.channel reactedMessage = f"{user.display_name} reacted to a message" @@ -80,10 +82,10 @@ class EventHandler(): reactionTestParams = [message, f"#{str(user.id)}"] - if tests.connectFourReactionTest(*reactionTestParams): + if tests.connect_fourReactionTest(*reactionTestParams): column = emojiToCommand(reaction.emoji) params = [message, f"#{user.id}", column-1] - await self.bot.games.connectFour.placePiece(*params) + await self.bot.games.connect_four.placePiece(*params) if plexData[0]: plexFuncs = self.bot.other.bedreNetflix @@ -150,14 +152,14 @@ class ErrorHandler(): 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(self.bot.longStrings["missing parameters"]) + await ctx.send(self.bot.long_strings["missing parameters"]) else: params = [type(error), error, error.__traceback__] exception = traceback.format_exception(*params) exceptionString = "".join(exception) - logMessages = [f"exception in /{ctx.name}", f"{exceptionString}"] - self.bot.log(logMessages, str(ctx.channel_id), 40) + log_messages = [f"exception in /{ctx.name}", f"{exceptionString}"] + self.bot.log(log_messages, str(ctx.channel_id), 40) if isinstance(error, discord.errors.NotFound): self.bot.log("Context is non-existant", level=40) else: @@ -172,5 +174,5 @@ class ErrorHandler(): exception = traceback.format_exc() exceptionString = "".join(exception) - logMessages = [f"exception in {method}", f"{exceptionString}"] - self.bot.log(logMessages, level=40) + log_messages = [f"exception in {method}", f"{exceptionString}"] + self.bot.log(log_messages, level=40) diff --git a/utils/helperClasses.py b/utils/helper_classes.py similarity index 94% rename from utils/helperClasses.py rename to utils/helper_classes.py index ca3ef41..ce86b1f 100644 --- a/utils/helperClasses.py +++ b/utils/helper_classes.py @@ -84,7 +84,7 @@ class Credentials(): data = sanitize(f.read()) self.token = data["bot token"] - self.finnhubKey = data["finnhub api key"] + self.finnhub_key = data["finnhub api key"] self.wordnikKey = data["wordnik api key"] self.mongoDBUser = data["mongodb user"] self.mongoDBPassword = data["mongodb password"] @@ -93,7 +93,7 @@ class Credentials(): self.sonarrKey = data["sonarr api key"] -class databaseFuncs(): +class DatabaseFuncs(): """ Manages database functions. @@ -103,7 +103,7 @@ class databaseFuncs(): getID(userName: str) -> str deleteGame(gameType: str, channel: str) wipeGames() - connectFourReactionTest(message: discord.Message, + connect_fourReactionTest(message: discord.Message, user: discord.User) -> bool hangmanReactionTest(message: discord.Message, user: discord.User) -> bool @@ -195,7 +195,7 @@ class databaseFuncs(): g = git.cmd.Git("") g.pull() - def connectFourReactionTest(self, message: discord.Message, + def connect_fourReactionTest(self, message: discord.Message, user: discord.User): """ Test if the given message is the current connect four game. @@ -219,12 +219,15 @@ class databaseFuncs(): channelSearch = {"_id": str(channel.id)} game = self.bot.database["connect 4 games"].find_one(channelSearch) - filePath = f"resources/games/oldImages/connectFour{channel.id}" - with open(filePath, "r") as f: - oldImage = int(f.read()) + filePath = f"resources/games/old_images/connect_four{channel.id}" + if os.path.isfile(filePath): + with open(filePath, "r") as f: + oldImage = int(f.read()) + else: + oldImage = 0 if message.id == oldImage: - self.bot.log("They reacted to the connectFour game") + self.bot.log("They reacted to the connect_four game") turn = game["turn"] if user == game["players"][turn]: return True @@ -255,7 +258,7 @@ class databaseFuncs(): hangman. """ channel = message.channel - filePath = f"resources/games/oldImages/hangman{channel.id}" + filePath = f"resources/games/old_images/hangman{channel.id}" if os.path.isfile(filePath): with open(filePath, "r") as f: oldMessages = f.read().splitlines() diff --git a/utils/utilFunctions.py b/utils/util_functions.py similarity index 95% rename from utils/utilFunctions.py rename to utils/util_functions.py index a472a21..af27573 100644 --- a/utils/utilFunctions.py +++ b/utils/util_functions.py @@ -3,7 +3,7 @@ Contains utility functions used by parts of the bot. *Functions* ----------- - longstrings() -> dict + long_strings() -> dict getParams() -> dict logThis(messages: Union[str, list], channel: str = "", level: int = 20) @@ -18,7 +18,7 @@ import logging # Used for logging import os # Used by makeFiles() to check if files exist import sys # Used to specify printing for logging import imdb # Used to disable logging for the module -from .helperClasses import Options # Used by getParams() +from .helper_classes import Options # Used by getParams() # All of this is logging configuration @@ -45,16 +45,16 @@ imdb._logging.setLevel("CRITICAL") # Basically disables imdbpy # logging, since it's printed to the terminal. -def longStrings(): +def long_strings(): """ - Get the data from resources/longStrings.json. + Get the data from resources/long_strings.json. *Returns* --------- data: dict The long strings and their keys. """ - with open("resources/longStrings.json", "r") as f: + with open("resources/long_strings.json", "r") as f: data = json.load(f) return data @@ -116,8 +116,8 @@ def logThis(messages, channel: str = "", level: int = 20): if level >= 25: printer.log(level, printMessage) - for logMessage in messages: - logger.log(level, logMessage) + for log_message in messages: + logger.log(level, log_message) def cap(s: str): From b68d62cf6c70628c5bf0c23ee6c420482235465a Mon Sep 17 00:00:00 2001 From: Nikolaj Date: Wed, 16 Jun 2021 14:12:21 +0200 Subject: [PATCH 22/23] :sparkles: PEP in utils --- .gitignore | 24 +- gwendolyn/__init__.py | 6 + {cogs => gwendolyn/cogs}/event_cog.py | 2 +- {cogs => gwendolyn/cogs}/game_cog.py | 84 ++--- {cogs => gwendolyn/cogs}/lookup_cog.py | 4 +- {cogs => gwendolyn/cogs}/misc_cog.py | 30 +- {cogs => gwendolyn/cogs}/star_wars_cog.py | 4 +- {funcs => gwendolyn/funcs}/__init__.py | 0 {funcs => gwendolyn/funcs}/games/__init__.py | 0 {funcs => gwendolyn/funcs}/games/blackjack.py | 238 ++++++------ .../funcs}/games/connect_four.py | 84 ++--- .../funcs}/games/games_container.py | 0 {funcs => gwendolyn/funcs}/games/hangman.py | 72 ++-- {funcs => gwendolyn/funcs}/games/hex.py | 102 +++--- {funcs => gwendolyn/funcs}/games/invest.py | 14 +- {funcs => gwendolyn/funcs}/games/money.py | 16 +- {funcs => gwendolyn/funcs}/games/trivia.py | 8 +- {funcs => gwendolyn/funcs}/lookup/__init__.py | 0 .../funcs}/lookup/lookup_funcs.py | 6 +- {funcs => gwendolyn/funcs}/other/__init__.py | 0 .../funcs/other/bedre_netflix.py | 48 +-- .../funcs}/other/generators.py | 2 +- .../funcs/other/nerd_shit.py | 22 +- {funcs => gwendolyn/funcs}/other/other.py | 16 +- .../funcs}/star_wars_funcs/__init__.py | 0 .../funcs}/star_wars_funcs/star_wars.py | 0 .../funcs}/star_wars_funcs/star_wars_char.py | 20 +- .../star_wars_funcs/star_wars_destiny.py | 10 +- .../funcs}/star_wars_funcs/star_wars_roll.py | 2 +- gwendolyn.py => gwendolyn/gwendolyn_client.py | 58 ++- .../resources}/fonts/comic-sans-bold.ttf | Bin .../resources}/fonts/futura-bold.ttf | Bin .../resources}/fonts/times-new-roman.ttf | Bin .../resources/games/blackjack_table.png | Bin .../resources}/games/cards/0C.png | Bin .../resources}/games/cards/0D.png | Bin .../resources}/games/cards/0H.png | Bin .../resources}/games/cards/0S.png | Bin .../resources}/games/cards/2C.png | Bin .../resources}/games/cards/2D.png | Bin .../resources}/games/cards/2H.png | Bin .../resources}/games/cards/2S.png | Bin .../resources}/games/cards/3C.png | Bin .../resources}/games/cards/3D.png | Bin .../resources}/games/cards/3H.png | Bin .../resources}/games/cards/3S.png | Bin .../resources}/games/cards/4C.png | Bin .../resources}/games/cards/4D.png | Bin .../resources}/games/cards/4H.png | Bin .../resources}/games/cards/4S.png | Bin .../resources}/games/cards/5C.png | Bin .../resources}/games/cards/5D.png | Bin .../resources}/games/cards/5H.png | Bin .../resources}/games/cards/5S.png | Bin .../resources}/games/cards/6C.png | Bin .../resources}/games/cards/6D.png | Bin .../resources}/games/cards/6H.png | Bin .../resources}/games/cards/6S.png | Bin .../resources}/games/cards/7C.png | Bin .../resources}/games/cards/7D.png | Bin .../resources}/games/cards/7H.png | Bin .../resources}/games/cards/7S.png | Bin .../resources}/games/cards/8C.png | Bin .../resources}/games/cards/8D.png | Bin .../resources}/games/cards/8H.png | Bin .../resources}/games/cards/8S.png | Bin .../resources}/games/cards/9C.png | Bin .../resources}/games/cards/9D.png | Bin .../resources}/games/cards/9H.png | Bin .../resources}/games/cards/9S.png | Bin .../resources}/games/cards/AC.png | Bin .../resources}/games/cards/AD.png | Bin .../resources}/games/cards/AH.png | Bin .../resources}/games/cards/AS.png | Bin .../resources}/games/cards/JC.png | Bin .../resources}/games/cards/JD.png | Bin .../resources}/games/cards/JH.png | Bin .../resources}/games/cards/JS.png | Bin .../resources}/games/cards/KC.png | Bin .../resources}/games/cards/KD.png | Bin .../resources}/games/cards/KH.png | Bin .../resources}/games/cards/KS.png | Bin .../resources}/games/cards/QC.png | Bin .../resources}/games/cards/QD.png | Bin .../resources}/games/cards/QH.png | Bin .../resources}/games/cards/QS.png | Bin .../resources}/games/cards/blue_back.png | Bin .../resources}/games/cards/gray_back.png | Bin .../resources}/games/cards/green_back.png | Bin .../resources}/games/cards/purple_back.png | Bin .../resources}/games/cards/red_back.png | Bin .../resources}/games/cards/yellow_back.png | Bin .../resources/games/deck_of_cards.txt | 0 gwendolyn/resources/help/help-add_movie.txt | 1 + .../resources/help/help-add_show.txt | 2 +- .../resources}/help/help-balance.txt | 0 .../resources}/help/help-blackjack.txt | 0 .../resources/help/help-connect_four.txt | 0 .../resources}/help/help-downloading.txt | 0 .../resources}/help/help-give.txt | 0 .../resources}/help/help-hangman.txt | 0 .../resources}/help/help-hello.txt | 0 .../resources}/help/help-hex.txt | 0 .../resources}/help/help-image.txt | 0 .../resources}/help/help-invest.txt | 0 .../resources}/help/help-monster.txt | 0 .../resources}/help/help-movie.txt | 0 .../resources}/help/help-name.txt | 0 .../resources}/help/help-roll.txt | 0 .../resources}/help/help-spell.txt | 0 .../help/help-star_wars_character.txt | 0 .../resources}/help/help-star_wars_roll.txt | 0 .../resources}/help/help-tavern.txt | 0 .../resources}/help/help-thank.txt | 0 .../resources}/help/help-trivia.txt | 0 .../resources}/help/help-wolf.txt | 0 .../resources}/help/help.txt | 4 +- .../resources}/long_strings.json | 0 {resources => gwendolyn/resources}/paper.jpg | Bin .../resources/slash_parameters.json | 48 +-- .../resources}/star_wars/starwarsskills.json | 0 .../star_wars/starwarstemplates.json | 0 .../resources/starting_files.json | 24 +- gwendolyn/utils/__init__.py | 11 + {utils => gwendolyn/utils}/event_handlers.py | 91 +++-- gwendolyn/utils/helper_classes.py | 260 ++++++++++++++ gwendolyn/utils/util_functions.py | 340 ++++++++++++++++++ main.py | 30 ++ project-guidelines.md | 4 +- resources/errorCodes.txt | 128 ------- .../oldImages/blackjack740652054388932682 | 1 - .../games/oldImages/hangman740652054388932682 | 2 - .../games/oldImages/hex740652054388932682 | 1 - resources/help/help-addshow.txt | 1 - utils/__init__.py | 10 - utils/helper_classes.py | 330 ----------------- utils/util_functions.py | 250 ------------- 137 files changed, 1159 insertions(+), 1251 deletions(-) create mode 100644 gwendolyn/__init__.py rename {cogs => gwendolyn/cogs}/event_cog.py (93%) rename {cogs => gwendolyn/cogs}/game_cog.py (65%) rename {cogs => gwendolyn/cogs}/lookup_cog.py (89%) rename {cogs => gwendolyn/cogs}/misc_cog.py (77%) rename {cogs => gwendolyn/cogs}/star_wars_cog.py (93%) rename {funcs => gwendolyn/funcs}/__init__.py (100%) rename {funcs => gwendolyn/funcs}/games/__init__.py (100%) rename {funcs => gwendolyn/funcs}/games/blackjack.py (85%) rename {funcs => gwendolyn/funcs}/games/connect_four.py (93%) rename {funcs => gwendolyn/funcs}/games/games_container.py (100%) rename {funcs => gwendolyn/funcs}/games/hangman.py (90%) rename {funcs => gwendolyn/funcs}/games/hex.py (88%) rename {funcs => gwendolyn/funcs}/games/invest.py (95%) rename {funcs => gwendolyn/funcs}/games/money.py (90%) rename {funcs => gwendolyn/funcs}/games/trivia.py (96%) rename {funcs => gwendolyn/funcs}/lookup/__init__.py (100%) rename {funcs => gwendolyn/funcs}/lookup/lookup_funcs.py (97%) rename {funcs => gwendolyn/funcs}/other/__init__.py (100%) rename funcs/other/bedreNetflix.py => gwendolyn/funcs/other/bedre_netflix.py (92%) rename {funcs => gwendolyn/funcs}/other/generators.py (98%) rename funcs/other/nerdShit.py => gwendolyn/funcs/other/nerd_shit.py (76%) rename {funcs => gwendolyn/funcs}/other/other.py (94%) rename {funcs => gwendolyn/funcs}/star_wars_funcs/__init__.py (100%) rename {funcs => gwendolyn/funcs}/star_wars_funcs/star_wars.py (100%) rename {funcs => gwendolyn/funcs}/star_wars_funcs/star_wars_char.py (97%) rename {funcs => gwendolyn/funcs}/star_wars_funcs/star_wars_destiny.py (86%) rename {funcs => gwendolyn/funcs}/star_wars_funcs/star_wars_roll.py (99%) rename gwendolyn.py => gwendolyn/gwendolyn_client.py (72%) rename {resources => gwendolyn/resources}/fonts/comic-sans-bold.ttf (100%) rename {resources => gwendolyn/resources}/fonts/futura-bold.ttf (100%) rename {resources => gwendolyn/resources}/fonts/times-new-roman.ttf (100%) rename resources/games/blackjackTable.png => gwendolyn/resources/games/blackjack_table.png (100%) rename {resources => gwendolyn/resources}/games/cards/0C.png (100%) rename {resources => gwendolyn/resources}/games/cards/0D.png (100%) rename {resources => gwendolyn/resources}/games/cards/0H.png (100%) rename {resources => gwendolyn/resources}/games/cards/0S.png (100%) rename {resources => gwendolyn/resources}/games/cards/2C.png (100%) rename {resources => gwendolyn/resources}/games/cards/2D.png (100%) rename {resources => gwendolyn/resources}/games/cards/2H.png (100%) rename {resources => gwendolyn/resources}/games/cards/2S.png (100%) rename {resources => gwendolyn/resources}/games/cards/3C.png (100%) rename {resources => gwendolyn/resources}/games/cards/3D.png (100%) rename {resources => gwendolyn/resources}/games/cards/3H.png (100%) rename {resources => gwendolyn/resources}/games/cards/3S.png (100%) rename {resources => gwendolyn/resources}/games/cards/4C.png (100%) rename {resources => gwendolyn/resources}/games/cards/4D.png (100%) rename {resources => gwendolyn/resources}/games/cards/4H.png (100%) rename {resources => gwendolyn/resources}/games/cards/4S.png (100%) rename {resources => gwendolyn/resources}/games/cards/5C.png (100%) rename {resources => gwendolyn/resources}/games/cards/5D.png (100%) rename {resources => gwendolyn/resources}/games/cards/5H.png (100%) rename {resources => gwendolyn/resources}/games/cards/5S.png (100%) rename {resources => gwendolyn/resources}/games/cards/6C.png (100%) rename {resources => gwendolyn/resources}/games/cards/6D.png (100%) rename {resources => gwendolyn/resources}/games/cards/6H.png (100%) rename {resources => gwendolyn/resources}/games/cards/6S.png (100%) rename {resources => gwendolyn/resources}/games/cards/7C.png (100%) rename {resources => gwendolyn/resources}/games/cards/7D.png (100%) rename {resources => gwendolyn/resources}/games/cards/7H.png (100%) rename {resources => gwendolyn/resources}/games/cards/7S.png (100%) rename {resources => gwendolyn/resources}/games/cards/8C.png (100%) rename {resources => gwendolyn/resources}/games/cards/8D.png (100%) rename {resources => gwendolyn/resources}/games/cards/8H.png (100%) rename {resources => gwendolyn/resources}/games/cards/8S.png (100%) rename {resources => gwendolyn/resources}/games/cards/9C.png (100%) rename {resources => gwendolyn/resources}/games/cards/9D.png (100%) rename {resources => gwendolyn/resources}/games/cards/9H.png (100%) rename {resources => gwendolyn/resources}/games/cards/9S.png (100%) rename {resources => gwendolyn/resources}/games/cards/AC.png (100%) rename {resources => gwendolyn/resources}/games/cards/AD.png (100%) rename {resources => gwendolyn/resources}/games/cards/AH.png (100%) rename {resources => gwendolyn/resources}/games/cards/AS.png (100%) rename {resources => gwendolyn/resources}/games/cards/JC.png (100%) rename {resources => gwendolyn/resources}/games/cards/JD.png (100%) rename {resources => gwendolyn/resources}/games/cards/JH.png (100%) rename {resources => gwendolyn/resources}/games/cards/JS.png (100%) rename {resources => gwendolyn/resources}/games/cards/KC.png (100%) rename {resources => gwendolyn/resources}/games/cards/KD.png (100%) rename {resources => gwendolyn/resources}/games/cards/KH.png (100%) rename {resources => gwendolyn/resources}/games/cards/KS.png (100%) rename {resources => gwendolyn/resources}/games/cards/QC.png (100%) rename {resources => gwendolyn/resources}/games/cards/QD.png (100%) rename {resources => gwendolyn/resources}/games/cards/QH.png (100%) rename {resources => gwendolyn/resources}/games/cards/QS.png (100%) rename {resources => gwendolyn/resources}/games/cards/blue_back.png (100%) rename {resources => gwendolyn/resources}/games/cards/gray_back.png (100%) rename {resources => gwendolyn/resources}/games/cards/green_back.png (100%) rename {resources => gwendolyn/resources}/games/cards/purple_back.png (100%) rename {resources => gwendolyn/resources}/games/cards/red_back.png (100%) rename {resources => gwendolyn/resources}/games/cards/yellow_back.png (100%) rename resources/games/deckofCards.txt => gwendolyn/resources/games/deck_of_cards.txt (100%) create mode 100644 gwendolyn/resources/help/help-add_movie.txt rename resources/help/help-addmovie.txt => gwendolyn/resources/help/help-add_show.txt (60%) rename {resources => gwendolyn/resources}/help/help-balance.txt (100%) rename {resources => gwendolyn/resources}/help/help-blackjack.txt (100%) rename resources/help/help-connectfour.txt => gwendolyn/resources/help/help-connect_four.txt (100%) rename {resources => gwendolyn/resources}/help/help-downloading.txt (100%) rename {resources => gwendolyn/resources}/help/help-give.txt (100%) rename {resources => gwendolyn/resources}/help/help-hangman.txt (100%) rename {resources => gwendolyn/resources}/help/help-hello.txt (100%) rename {resources => gwendolyn/resources}/help/help-hex.txt (100%) rename {resources => gwendolyn/resources}/help/help-image.txt (100%) rename {resources => gwendolyn/resources}/help/help-invest.txt (100%) rename {resources => gwendolyn/resources}/help/help-monster.txt (100%) rename {resources => gwendolyn/resources}/help/help-movie.txt (100%) rename {resources => gwendolyn/resources}/help/help-name.txt (100%) rename {resources => gwendolyn/resources}/help/help-roll.txt (100%) rename {resources => gwendolyn/resources}/help/help-spell.txt (100%) rename {resources => gwendolyn/resources}/help/help-star_wars_character.txt (100%) rename {resources => gwendolyn/resources}/help/help-star_wars_roll.txt (100%) rename {resources => gwendolyn/resources}/help/help-tavern.txt (100%) rename {resources => gwendolyn/resources}/help/help-thank.txt (100%) rename {resources => gwendolyn/resources}/help/help-trivia.txt (100%) rename {resources => gwendolyn/resources}/help/help-wolf.txt (100%) rename {resources => gwendolyn/resources}/help/help.txt (90%) rename {resources => gwendolyn/resources}/long_strings.json (100%) rename {resources => gwendolyn/resources}/paper.jpg (100%) rename resources/slashParameters.json => gwendolyn/resources/slash_parameters.json (95%) rename {resources => gwendolyn/resources}/star_wars/starwarsskills.json (100%) rename {resources => gwendolyn/resources}/star_wars/starwarstemplates.json (100%) rename resources/startingFiles.json => gwendolyn/resources/starting_files.json (82%) create mode 100644 gwendolyn/utils/__init__.py rename {utils => gwendolyn/utils}/event_handlers.py (67%) create mode 100644 gwendolyn/utils/helper_classes.py create mode 100644 gwendolyn/utils/util_functions.py create mode 100644 main.py delete mode 100644 resources/errorCodes.txt delete mode 100644 resources/games/oldImages/blackjack740652054388932682 delete mode 100644 resources/games/oldImages/hangman740652054388932682 delete mode 100644 resources/games/oldImages/hex740652054388932682 delete mode 100644 resources/help/help-addshow.txt delete mode 100644 utils/__init__.py delete mode 100644 utils/helper_classes.py delete mode 100644 utils/util_functions.py diff --git a/.gitignore b/.gitignore index e10cdc3..59cd0e4 100644 --- a/.gitignore +++ b/.gitignore @@ -153,16 +153,16 @@ static token.txt credentials.txt options.txt -resources/star_wars/destinyPoints.txt -resources/bedreNetflix/ -resources/games/hilo/ -resources/games/blackjackTables/ -resources/games/old_images/ -resources/games/connect4Boards/ -resources/games/hexBoards/ -resources/games/hangmanBoards/ -resources/lookup/monsters.json -resources/lookup/spells.json -resources/movies.txt -resources/names.txt +gwendolyn/resources/star_wars/destinyPoints.txt +gwendolyn/resources/bedre_netflix/ +gwendolyn/resources/games/hilo/ +gwendolyn/resources/games/blackjack_tables/ +gwendolyn/resources/games/old_images/ +gwendolyn/resources/games/connect4Boards/ +gwendolyn/resources/games/hex_boards/ +gwendolyn/resources/games/hangman_boards/ +gwendolyn/resources/lookup/monsters.json +gwendolyn/resources/lookup/spells.json +gwendolyn/resources/movies.txt +gwendolyn/resources/names.txt gwendolynTest.py diff --git a/gwendolyn/__init__.py b/gwendolyn/__init__.py new file mode 100644 index 0000000..17b7d0d --- /dev/null +++ b/gwendolyn/__init__.py @@ -0,0 +1,6 @@ +"""The main module for Gwendolyn.""" +# pylint: disable=invalid-name + +__all__ = ["funcs", "utils", "Gwendolyn"] + +from .gwendolyn_client import Gwendolyn diff --git a/cogs/event_cog.py b/gwendolyn/cogs/event_cog.py similarity index 93% rename from cogs/event_cog.py rename to gwendolyn/cogs/event_cog.py index 6216ae7..4737f8c 100644 --- a/cogs/event_cog.py +++ b/gwendolyn/cogs/event_cog.py @@ -25,7 +25,7 @@ class EventCog(commands.Cog): """Log when a slash error occurs.""" await self.bot.error_handler.on_slash_command_error(ctx, error) - async def on_error(self, method): + async def on_error(self, method, *args, **kwargs): # pylint: disable=unused-argument """Log when an error occurs.""" await self.bot.error_handler.on_error(method) diff --git a/cogs/game_cog.py b/gwendolyn/cogs/game_cog.py similarity index 65% rename from cogs/game_cog.py rename to gwendolyn/cogs/game_cog.py index d6bb92f..0caad15 100644 --- a/cogs/game_cog.py +++ b/gwendolyn/cogs/game_cog.py @@ -2,9 +2,9 @@ from discord.ext import commands # Has the cog class from discord_slash import cog_ext # Used for slash commands -from utils import getParams # pylint: disable=import-error +from gwendolyn.utils import get_params # pylint: disable=import-error -params = getParams() +params = get_params() class GamesCog(commands.Cog): @@ -42,48 +42,48 @@ class BlackjackCog(commands.Cog): """Initialize the cog.""" self.bot = bot - @cog_ext.cog_subcommand(**params["blackjackStart"]) - async def blackjackStart(self, ctx): + @cog_ext.cog_subcommand(**params["blackjack_start"]) + async def blackjack_start(self, ctx): """Start a game of blackjack.""" await self.bot.games.blackjack.start(ctx) - @cog_ext.cog_subcommand(**params["blackjackBet"]) - async def blackjackBet(self, ctx, bet): + @cog_ext.cog_subcommand(**params["blackjack_bet"]) + async def blackjack_bet(self, ctx, bet): """Enter the game of blackjack with a bet.""" await self.bot.games.blackjack.enterGame(ctx, bet) - @cog_ext.cog_subcommand(**params["blackjackStand"]) - async def blackjackStand(self, ctx, hand=""): + @cog_ext.cog_subcommand(**params["blackjack_stand"]) + async def blackjack_stand(self, ctx, hand=""): """Stand on your hand in blackjack.""" await self.bot.games.blackjack.stand(ctx, hand) - @cog_ext.cog_subcommand(**params["blackjackHit"]) - async def blackjackHit(self, ctx, hand=0): + @cog_ext.cog_subcommand(**params["blackjack_hit"]) + async def blackjack_hit(self, ctx, hand=0): """Hit on your hand in blackjack.""" await self.bot.games.blackjack.hit(ctx, hand) - @cog_ext.cog_subcommand(**params["blackjackDouble"]) - async def blackjackDouble(self, ctx, hand=0): + @cog_ext.cog_subcommand(**params["blackjack_double"]) + async def blackjack_double(self, ctx, hand=0): """Double in blackjack.""" await self.bot.games.blackjack.double(ctx, hand) - @cog_ext.cog_subcommand(**params["blackjackSplit"]) - async def blackjackSplit(self, ctx, hand=0): + @cog_ext.cog_subcommand(**params["blackjack_split"]) + async def blackjack_split(self, ctx, hand=0): """Split your hand in blackjack.""" await self.bot.games.blackjack.split(ctx, hand) - @cog_ext.cog_subcommand(**params["blackjackHilo"]) - async def blackjackHilo(self, ctx): + @cog_ext.cog_subcommand(**params["blackjack_hilo"]) + async def blackjack_hilo(self, ctx): """Get the hilo value for the deck in blackjack.""" await self.bot.games.blackjack.hilo(ctx) - @cog_ext.cog_subcommand(**params["blackjackShuffle"]) - async def blackjackShuffle(self, ctx): + @cog_ext.cog_subcommand(**params["blackjack_shuffle"]) + async def blackjack_shuffle(self, ctx): """Shuffle the blackjack game.""" await self.bot.games.blackjack.shuffle(ctx) - @cog_ext.cog_subcommand(**params["blackjackCards"]) - async def blackjackCards(self, ctx): + @cog_ext.cog_subcommand(**params["blackjack_cards"]) + async def blackjack_cards(self, ctx): """Get the amount of cards left in the blackjack deck.""" await self.bot.games.blackjack.cards(ctx) @@ -95,18 +95,18 @@ class ConnectFourCog(commands.Cog): """Initialize the cog.""" self.bot = bot - @cog_ext.cog_subcommand(**params["connect_fourStartUser"]) - async def connect_fourStartUser(self, ctx, user): + @cog_ext.cog_subcommand(**params["connect_four_start_user"]) + async def connect_four_start_user(self, ctx, user): """Start a game of connect four against another user.""" await self.bot.games.connect_four.start(ctx, user) - @cog_ext.cog_subcommand(**params["connect_fourStartGwendolyn"]) - async def connect_fourStartGwendolyn(self, ctx, difficulty=3): + @cog_ext.cog_subcommand(**params["connect_four_start_gwendolyn"]) + async def connect_four_start_gwendolyn(self, ctx, difficulty=3): """Start a game of connect four against Gwendolyn.""" await self.bot.games.connect_four.start(ctx, difficulty) - @cog_ext.cog_subcommand(**params["connect_fourSurrender"]) - async def connect_fourSurrender(self, ctx): + @cog_ext.cog_subcommand(**params["connect_four_surrender"]) + async def connect_four_surrender(self, ctx): """Surrender the game of connect four.""" await self.bot.games.connect_four.surrender(ctx) @@ -118,13 +118,13 @@ class HangmanCog(commands.Cog): """Initialize the cog.""" self.bot = bot - @cog_ext.cog_subcommand(**params["hangmanStart"]) - async def hangmanStart(self, ctx): + @cog_ext.cog_subcommand(**params["hangman_start"]) + async def hangman_start(self, ctx): """Start a game of hangman.""" await self.bot.games.hangman.start(ctx) - @cog_ext.cog_subcommand(**params["hangmanStop"]) - async def hangmanStop(self, ctx): + @cog_ext.cog_subcommand(**params["hangman_stop"]) + async def hangman_stop(self, ctx): """Stop the current game of hangman.""" await self.bot.games.hangman.stop(ctx) @@ -137,33 +137,33 @@ class HexCog(commands.Cog): self.bot = bot self.hex = self.bot.games.hex - @cog_ext.cog_subcommand(**params["hexStartUser"]) - async def hexStartUser(self, ctx, user): + @cog_ext.cog_subcommand(**params["hex_start_user"]) + async def hex_start_user(self, ctx, user): """Start a game of hex against another player.""" await self.hex.start(ctx, user) - @cog_ext.cog_subcommand(**params["hexStartGwendolyn"]) - async def hexStartGwendolyn(self, ctx, difficulty=2): + @cog_ext.cog_subcommand(**params["hex_start_gwendolyn"]) + async def hex_start_gwendolyn(self, ctx, difficulty=2): """Start a game of hex against Gwendolyn.""" await self.hex.start(ctx, difficulty) - @cog_ext.cog_subcommand(**params["hexPlace"]) - async def hexPlace(self, ctx, coordinates): + @cog_ext.cog_subcommand(**params["hex_place"]) + async def hex_place(self, ctx, coordinates): """Place a piece in the hex game.""" await self.hex.placeHex(ctx, coordinates, f"#{ctx.author.id}") - @cog_ext.cog_subcommand(**params["hexUndo"]) - async def hexUndo(self, ctx): + @cog_ext.cog_subcommand(**params["hex_undo"]) + async def hex_undo(self, ctx): """Undo your last hex move.""" await self.hex.undo(ctx) - @cog_ext.cog_subcommand(**params["hexSwap"]) - async def hexSwap(self, ctx): + @cog_ext.cog_subcommand(**params["hex_swap"]) + async def hex_swap(self, ctx): """Perform a hex swap.""" await self.hex.swap(ctx) - @cog_ext.cog_subcommand(**params["hexSurrender"]) - async def hexSurrender(self, ctx): + @cog_ext.cog_subcommand(**params["hex_surrender"]) + async def hex_surrender(self, ctx): """Surrender the hex game.""" await self.hex.surrender(ctx) diff --git a/cogs/lookup_cog.py b/gwendolyn/cogs/lookup_cog.py similarity index 89% rename from cogs/lookup_cog.py rename to gwendolyn/cogs/lookup_cog.py index 91167da..77c1114 100644 --- a/cogs/lookup_cog.py +++ b/gwendolyn/cogs/lookup_cog.py @@ -2,9 +2,9 @@ from discord.ext import commands # Has the cog class from discord_slash import cog_ext # Used for slash commands -from utils import getParams # pylint: disable=import-error +from gwendolyn.utils import get_params # pylint: disable=import-error -params = getParams() +params = get_params() class LookupCog(commands.Cog): diff --git a/cogs/misc_cog.py b/gwendolyn/cogs/misc_cog.py similarity index 77% rename from cogs/misc_cog.py rename to gwendolyn/cogs/misc_cog.py index ab6e146..cd2bffe 100644 --- a/cogs/misc_cog.py +++ b/gwendolyn/cogs/misc_cog.py @@ -2,9 +2,9 @@ from discord.ext import commands # Has the cog class from discord_slash import cog_ext # Used for slash commands -from utils import getParams # pylint: disable=import-error +from gwendolyn.utils import get_params # pylint: disable=import-error -params = getParams() +params = get_params() class MiscCog(commands.Cog): @@ -15,8 +15,8 @@ class MiscCog(commands.Cog): self.bot = bot self.bot.remove_command("help") self.generators = bot.other.generators - self.bedreNetflix = bot.other.bedreNetflix - self.nerdShit = bot.other.nerdShit + self.bedre_netflix = bot.other.bedre_netflix + self.nerd_shit = bot.other.nerd_shit @cog_ext.cog_slash(**params["ping"]) async def ping(self, ctx): @@ -29,7 +29,7 @@ class MiscCog(commands.Cog): await self.bot.stop(ctx) @cog_ext.cog_slash(**params["help"]) - async def helpCommand(self, ctx, command=""): + async def help_command(self, ctx, command=""): """Get help for commands.""" await self.bot.other.helpFunc(ctx, command) @@ -69,29 +69,29 @@ class MiscCog(commands.Cog): await self.generators.tavernGen(ctx) @cog_ext.cog_slash(**params["wiki"]) - async def wiki(self, ctx, wikiPage=""): + async def wiki(self, ctx, wiki_page=""): """Get a page on a fandom wiki.""" - await self.bot.other.findWikiPage(ctx, wikiPage) + await self.bot.other.findWikiPage(ctx, wiki_page) - @cog_ext.cog_slash(**params["addMovie"]) - async def addMovie(self, ctx, movie): + @cog_ext.cog_slash(**params["add_movie"]) + async def add_movie(self, ctx, movie): """Search for a movie and add it to the Plex server.""" - await self.bedreNetflix.requestMovie(ctx, movie) + await self.bedre_netflix.requestMovie(ctx, movie) - @cog_ext.cog_slash(**params["addShow"]) - async def addShow(self, ctx, show): + @cog_ext.cog_slash(**params["add_show"]) + async def add_show(self, ctx, show): """Search for a show and add it to the Plex server.""" - await self.bedreNetflix.requestShow(ctx, show) + await self.bedre_netflix.requestShow(ctx, show) @cog_ext.cog_slash(**params["downloading"]) async def downloading(self, ctx, parameters="-d"): """Get the current downloading torrents.""" - await self.bedreNetflix.downloading(ctx, parameters) + await self.bedre_netflix.downloading(ctx, parameters) @cog_ext.cog_slash(**params["wolf"]) async def wolf(self, ctx, query): """Perform a search on Wolfram Alpha.""" - await self.nerdShit.wolfSearch(ctx, query) + await self.nerd_shit.wolfSearch(ctx, query) def setup(bot): diff --git a/cogs/star_wars_cog.py b/gwendolyn/cogs/star_wars_cog.py similarity index 93% rename from cogs/star_wars_cog.py rename to gwendolyn/cogs/star_wars_cog.py index b029f9f..10101f0 100644 --- a/cogs/star_wars_cog.py +++ b/gwendolyn/cogs/star_wars_cog.py @@ -2,9 +2,9 @@ from discord.ext import commands from discord_slash import cog_ext -from utils import getParams # pylint: disable=import-error +from gwendolyn.utils import get_params # pylint: disable=import-error -params = getParams() +params = get_params() class StarWarsCog(commands.Cog): diff --git a/funcs/__init__.py b/gwendolyn/funcs/__init__.py similarity index 100% rename from funcs/__init__.py rename to gwendolyn/funcs/__init__.py diff --git a/funcs/games/__init__.py b/gwendolyn/funcs/games/__init__.py similarity index 100% rename from funcs/games/__init__.py rename to gwendolyn/funcs/games/__init__.py diff --git a/funcs/games/blackjack.py b/gwendolyn/funcs/games/blackjack.py similarity index 85% rename from funcs/games/blackjack.py rename to gwendolyn/funcs/games/blackjack.py index d02e087..4b5b1be 100644 --- a/funcs/games/blackjack.py +++ b/gwendolyn/funcs/games/blackjack.py @@ -18,7 +18,7 @@ from PIL import Image, ImageDraw, ImageFont from shutil import copyfile -from utils import replaceMultiple +from gwendolyn.utils import replace_multiple class Blackjack(): @@ -48,7 +48,7 @@ class Blackjack(): self.draw = DrawBlackjack(bot) self.decks = 4 - def _blackjackShuffle(self, channel: str): + def _blackjack_shuffle(self, channel: str): """ Shuffle an amount of decks equal to self.decks. @@ -62,23 +62,23 @@ class Blackjack(): """ self.bot.log("Shuffling the blackjack deck") - with open("resources/games/deckOfCards.txt", "r") as f: + with open("gwendolyn/resources/games/deck_of_cards.txt", "r") as f: deck = f.read() allDecks = deck.split("\n") * self.decks random.shuffle(allDecks) - blackjackCards = self.bot.database["blackjack cards"] + blackjack_cards = self.bot.database["blackjack cards"] cards = {"_id": channel} cardUpdater = {"$set": {"_id": channel, "cards": allDecks}} - blackjackCards.update_one(cards, cardUpdater, upsert=True) + blackjack_cards.update_one(cards, cardUpdater, upsert=True) # Creates hilo file self.bot.log(f"creating hilo doc for {channel}") data = 0 - blackjackHilo = self.bot.database["hilo"] + blackjack_hilo = self.bot.database["hilo"] hiloUpdater = {"$set": {"_id": channel, "hilo": data}} - blackjackHilo.update_one({"_id": channel}, hiloUpdater, upsert=True) + blackjack_hilo.update_one({"_id": channel}, hiloUpdater, upsert=True) def _calcHandValue(self, hand: list): """ @@ -103,7 +103,7 @@ class Blackjack(): for card in hand: cardValue = card[0] - cardValue = replaceMultiple(cardValue, ["0", "k", "q", "j"], "10") + cardValue = replace_multiple(cardValue, ["0", "k", "q", "j"], "10") if cardValue == "a": length = len(values) for x in range(length): @@ -135,17 +135,17 @@ class Blackjack(): """ self.bot.log("drawing a card") - blackjackCards = self.bot.database["blackjack cards"] - drawnCard = blackjackCards.find_one({"_id": channel})["cards"][0] - blackjackCards.update_one({"_id": channel}, {"$pop": {"cards": -1}}) + blackjack_cards = self.bot.database["blackjack cards"] + drawnCard = blackjack_cards.find_one({"_id": channel})["cards"][0] + blackjack_cards.update_one({"_id": channel}, {"$pop": {"cards": -1}}) value = self._calcHandValue([drawnCard]) - blackjackHilo = self.bot.database["hilo"] + blackjack_hilo = self.bot.database["hilo"] if value <= 6: - blackjackHilo.update_one({"_id": channel}, {"$inc": {"hilo": 1}}) + blackjack_hilo.update_one({"_id": channel}, {"$inc": {"hilo": 1}}) elif value >= 10: - blackjackHilo.update_one({"_id": channel}, {"$inc": {"hilo": -1}}) + blackjack_hilo.update_one({"_id": channel}, {"$inc": {"hilo": -1}}) return drawnCard @@ -168,22 +168,22 @@ class Blackjack(): done = False dealerHand = game["dealer hand"] - blackjackGames = self.bot.database["blackjack games"] + blackjack_games = self.bot.database["blackjack games"] if self._calcHandValue(dealerHand) < 17: dealerHand.append(self._drawCard(channel)) dealerUpdater = {"$set": {"dealer hand": dealerHand}} - blackjackGames.update_one({"_id": channel}, dealerUpdater) + blackjack_games.update_one({"_id": channel}, dealerUpdater) else: done = True if self._calcHandValue(dealerHand) > 21: dealerUpdater = {"$set": {"dealer busted": True}} - blackjackGames.update_one({"_id": channel}, dealerUpdater) + blackjack_games.update_one({"_id": channel}, dealerUpdater) return done - def _blackjackContinue(self, channel: str): + def _blackjack_continue(self, channel: str): """ Continues the blackjack game to the next round. @@ -206,8 +206,8 @@ class Blackjack(): done = False - blackjackGames = self.bot.database["blackjack games"] - blackjackGames.update_one({"_id": channel}, {"$inc": {"round": 1}}) + blackjack_games = self.bot.database["blackjack games"] + blackjack_games.update_one({"_id": channel}, {"$inc": {"round": 1}}) allStanding = True preAllStanding = True @@ -219,20 +219,20 @@ class Blackjack(): done = self._dealerDraw(channel) message = "The dealer draws a card." - blackjackGames.find_one({"_id": channel}) + blackjack_games.find_one({"_id": channel}) self.bot.log("Testing if all are standing") for user in game["user hands"]: userHand = game["user hands"][user] - testParams = [userHand, allStanding, preAllStanding, True] - standingTest = (self._testIfStanding(*testParams)) + test_parameters = [userHand, allStanding, preAllStanding, True] + standingTest = (self._testIfStanding(*test_parameters)) newUser, allStanding, preAllStanding = standingTest handUpdater = {"$set": {"user hands."+user: newUser}} - blackjackGames.update_one({"_id": channel}, handUpdater) + blackjack_games.update_one({"_id": channel}, handUpdater) if allStanding: gameUpdater = {"$set": {"all standing": True}} - blackjackGames.update_one({"_id": channel}, gameUpdater) + blackjack_games.update_one({"_id": channel}, gameUpdater) self.draw.drawImage(channel) @@ -303,23 +303,23 @@ class Blackjack(): if topLevel: if hand["split"] >= 1: testHand = hand["other hand"] - testParams = [testHand, allStanding, preAllStanding, False] - standingTest = (self._testIfStanding(*testParams)) + test_parameters = [testHand, allStanding, preAllStanding, False] + standingTest = (self._testIfStanding(*test_parameters)) hand["other hand"], allStanding, preAllStanding = standingTest if hand["split"] >= 2: testHand = hand["third hand"] - testParams = [testHand, allStanding, preAllStanding, False] - standingTest = (self._testIfStanding(*testParams)) + test_parameters = [testHand, allStanding, preAllStanding, False] + standingTest = (self._testIfStanding(*test_parameters)) hand["third hand"], allStanding, preAllStanding = standingTest if hand["split"] >= 3: testHand = hand["fourth hand"] - testParams = [testHand, allStanding, preAllStanding, False] - standingTest = (self._testIfStanding(*testParams)) + test_parameters = [testHand, allStanding, preAllStanding, False] + standingTest = (self._testIfStanding(*test_parameters)) hand["fourth hand"], allStanding, preAllStanding = standingTest return hand, allStanding, preAllStanding - def _blackjackFinish(self, channel: str): + def _blackjack_finish(self, channel: str): """ Generate the winnings message after the blackjack game ends. @@ -352,21 +352,21 @@ class Blackjack(): winningCalc = (self._calcWinnings(*_calcWinningsParams)) winnings, netWinnings, reason = winningCalc - userName = self.bot.database_funcs.getName(user) + user_name = self.bot.database_funcs.get_name(user) if winnings < 0: if winnings == -1: - finalWinnings += f"{userName} lost 1 GwendoBuck {reason}\n" + finalWinnings += f"{user_name} lost 1 GwendoBuck {reason}\n" else: moneyLost = -1 * winnings - winningText = f"{userName} lost {moneyLost} GwendoBucks" + winningText = f"{user_name} lost {moneyLost} GwendoBucks" winningText += f" {reason}\n" finalWinnings += winningText else: if winnings == 1: - finalWinnings += f"{userName} won 1 GwendoBuck {reason}\n" + finalWinnings += f"{user_name} won 1 GwendoBuck {reason}\n" else: - winningText = f"{userName} won {winnings} GwendoBucks" + winningText = f"{user_name} won {winnings} GwendoBucks" winningText += f" {reason}\n" finalWinnings += winningText @@ -554,7 +554,7 @@ class Blackjack(): return roundDone - async def _blackjackLoop(self, channel, gameRound: int, gameID: str): + async def _blackjack_loop(self, channel, gameRound: int, gameID: str): """ Run blackjack logic and continue if enough time passes. @@ -569,31 +569,31 @@ class Blackjack(): """ self.bot.log("Loop "+str(gameRound), str(channel.id)) - oldImagePath = f"resources/games/old_images/blackjack{channel.id}" - with open(oldImagePath, "r") as f: - oldImage = await channel.fetch_message(int(f.read())) + old_imagePath = f"gwendolyn/resources/games/old_images/blackjack{channel.id}" + with open(old_imagePath, "r") as f: + old_image = await channel.fetch_message(int(f.read())) - continueData = (self._blackjackContinue(str(channel.id))) + continueData = (self._blackjack_continue(str(channel.id))) new_message, allStanding, gamedone = continueData if new_message != "": self.bot.log(new_message, str(channel.id)) await channel.send(new_message) if not gamedone: - await oldImage.delete() - tablesPath = "resources/games/blackjackTables/" - filePath = f"{tablesPath}blackjackTable{channel.id}.png" - oldImage = await channel.send(file=discord.File(filePath)) - with open(oldImagePath, "w") as f: - f.write(str(oldImage.id)) + await old_image.delete() + tablesPath = "gwendolyn/resources/games/blackjack_tables/" + file_path = f"{tablesPath}blackjack_table{channel.id}.png" + old_image = await channel.send(file=discord.File(file_path)) + with open(old_imagePath, "w") as f: + f.write(str(old_image.id)) if allStanding: await asyncio.sleep(5) else: await asyncio.sleep(120) - blackjackGames = self.bot.database["blackjack games"] - game = blackjackGames.find_one({"_id": str(channel.id)}) + blackjack_games = self.bot.database["blackjack games"] + game = blackjack_games.find_one({"_id": str(channel.id)}) if game is None: rightRound = False @@ -604,11 +604,11 @@ class Blackjack(): if rightRound: if not gamedone: - log_message = f"Loop {gameRound} calling self._blackjackLoop()" + log_message = f"Loop {gameRound} calling self._blackjack_loop()" self.bot.log(log_message, str(channel.id)) - await self._blackjackLoop(channel, gameRound+1, gameID) + await self._blackjack_loop(channel, gameRound+1, gameID) else: - new_message = self._blackjackFinish(str(channel.id)) + new_message = self._blackjack_finish(str(channel.id)) await channel.send(new_message) else: log_message = f"Ending loop on round {gameRound}" @@ -631,8 +631,8 @@ class Blackjack(): user = f"#{ctx.author.id}" roundDone = False - blackjackGames = self.bot.database["blackjack games"] - game = blackjackGames.find_one({"_id": channel}) + blackjack_games = self.bot.database["blackjack games"] + game = blackjack_games.find_one({"_id": channel}) if user in game["user hands"]: @@ -670,8 +670,8 @@ class Blackjack(): handPath = f"user hands.{user}" gameUpdater = {"$set": {handPath: hand}} - blackjackGames.update_one({"_id": channel}, gameUpdater) - game = blackjackGames.find_one({"_id": channel}) + blackjack_games.update_one({"_id": channel}, gameUpdater) + game = blackjack_games.find_one({"_id": channel}) roundDone = self._isRoundDone(game) sendMessage = f"{ctx.author.display_name} hit" @@ -685,8 +685,8 @@ class Blackjack(): if roundDone: gameID = game["gameID"] - self.bot.log("Hit calling self._blackjackLoop()", channel) - await self._blackjackLoop(ctx.channel, game["round"]+1, gameID) + self.bot.log("Hit calling self._blackjack_loop()", channel) + await self._blackjack_loop(ctx.channel, game["round"]+1, gameID) async def double(self, ctx: discord_slash.context.SlashContext, handNumber: int = 0): @@ -704,9 +704,9 @@ class Blackjack(): channel = str(ctx.channel_id) user = f"#{ctx.author.id}" roundDone = False - blackjackGames = self.bot.database["blackjack games"] + blackjack_games = self.bot.database["blackjack games"] - game = blackjackGames.find_one({"_id": channel}) + game = blackjack_games.find_one({"_id": channel}) if user in game["user hands"]: handParams = [game["user hands"][user], handNumber] @@ -754,14 +754,14 @@ class Blackjack(): handPath = f"user hands.{user}" gameUpdater = {"$set": {handPath: hand}} - blackjackGames.update_one({"_id": channel}, gameUpdater) + blackjack_games.update_one({"_id": channel}, gameUpdater) - game = blackjackGames.find_one({"_id": channel}) + game = blackjack_games.find_one({"_id": channel}) roundDone = self._isRoundDone(game) sendMessage = self.bot.long_strings["Blackjack double"] - userName = self.bot.database_funcs.getName(user) - sendMessage = sendMessage.format(bet, userName) + user_name = self.bot.database_funcs.get_name(user) + sendMessage = sendMessage.format(bet, user_name) log_message = "They succeeded" else: log_message = "They tried to double without being in the game" @@ -772,8 +772,8 @@ class Blackjack(): if roundDone: gameID = game["gameID"] - self.bot.log("Double calling self._blackjackLoop()", channel) - await self._blackjackLoop(ctx.channel, game["round"]+1, gameID) + self.bot.log("Double calling self._blackjack_loop()", channel) + await self._blackjack_loop(ctx.channel, game["round"]+1, gameID) async def stand(self, ctx: discord_slash.context.SlashContext, handNumber: int = 0): @@ -792,8 +792,8 @@ class Blackjack(): user = f"#{ctx.author.id}" roundDone = False - blackjackGames = self.bot.database["blackjack games"] - game = blackjackGames.find_one({"_id": channel}) + blackjack_games = self.bot.database["blackjack games"] + game = blackjack_games.find_one({"_id": channel}) if game is not None and user in game["user hands"]: handParams = [game["user hands"][user], handNumber] @@ -824,8 +824,8 @@ class Blackjack(): handPath = f"user hands.{user}" gameUpdater = {"$set": {handPath: hand}} - blackjackGames.update_one({"_id": channel}, gameUpdater) - game = blackjackGames.find_one({"_id": channel}) + blackjack_games.update_one({"_id": channel}, gameUpdater) + game = blackjack_games.find_one({"_id": channel}) roundDone = self._isRoundDone(game) sendMessage = f"{ctx.author.display_name} is standing" @@ -840,8 +840,8 @@ class Blackjack(): if roundDone: gameID = game["gameID"] - self.bot.log("Stand calling self._blackjackLoop()", channel) - await self._blackjackLoop(ctx.channel, game["round"]+1, gameID) + self.bot.log("Stand calling self._blackjack_loop()", channel) + await self._blackjack_loop(ctx.channel, game["round"]+1, gameID) async def split(self, ctx: discord_slash.context.SlashContext, handNumber: int = 0): @@ -861,8 +861,8 @@ class Blackjack(): roundDone = False handNumberError = False - blackjackGames = self.bot.database["blackjack games"] - game = blackjackGames.find_one({"_id": channel}) + blackjack_games = self.bot.database["blackjack games"] + game = blackjack_games.find_one({"_id": channel}) if game["user hands"][user]["split"] == 0: hand = game["user hands"][user] @@ -954,7 +954,7 @@ class Blackjack(): handPath = f"user hands.{user}" gameUpdater = {"$set": {handPath: hand}} - blackjackGames.update_one({"_id": channel}, gameUpdater) + blackjack_games.update_one({"_id": channel}, gameUpdater) if otherHand == 3: otherHandPath = f"user hands.{user}.third hand" @@ -964,17 +964,17 @@ class Blackjack(): otherHandPath = f"user hands.{user}.other hand" gameUpdater = {"$set": {otherHandPath: newHand}} - blackjackGames.update_one({"_id": channel}, gameUpdater) + blackjack_games.update_one({"_id": channel}, gameUpdater) splitUpdater = {"$inc": {"user hands."+user+".split": 1}} - blackjackGames.update_one({"_id": channel}, splitUpdater) + blackjack_games.update_one({"_id": channel}, splitUpdater) - game = blackjackGames.find_one({"_id": channel}) + game = blackjack_games.find_one({"_id": channel}) roundDone = self._isRoundDone(game) sendMessage = self.bot.long_strings["Blackjack split"] - userName = self.bot.database_funcs.getName(user) - sendMessage = sendMessage.format(userName) + user_name = self.bot.database_funcs.get_name(user) + sendMessage = sendMessage.format(user_name) log_message = "They succeeded" await ctx.send(sendMessage) @@ -982,8 +982,8 @@ class Blackjack(): if roundDone: gameID = game["gameID"] - self.bot.log("Stand calling self._blackjackLoop()", channel) - await self._blackjackLoop(ctx.channel, game["round"]+1, gameID) + self.bot.log("Stand calling self._blackjack_loop()", channel) + await self._blackjack_loop(ctx.channel, game["round"]+1, gameID) async def enterGame(self, ctx: discord_slash.context.SlashContext, bet: int): @@ -1002,9 +1002,9 @@ class Blackjack(): user = f"#{ctx.author.id}" collection = self.bot.database["blackjack games"] game = collection.find_one({"_id": channel}) - userName = self.bot.database_funcs.getName(user) + user_name = self.bot.database_funcs.get_name(user) - self.bot.log(f"{userName} is trying to join the Blackjack game") + self.bot.log(f"{user_name} is trying to join the Blackjack game") if game is None: sendMessage = "There is no game going on in this channel" @@ -1031,13 +1031,13 @@ class Blackjack(): handValue = self._calcHandValue(playerHand) if handValue == 21: - blackjackHand = True + blackjack_hand = True else: - blackjackHand = False + blackjack_hand = False newHand = { "hand": playerHand, "bet": bet, "standing": False, - "busted": False, "blackjack": blackjackHand, + "busted": False, "blackjack": blackjack_hand, "hit": True, "doubled": False, "split": 0, "other hand": {}, "third hand": {}, "fourth hand": {} } @@ -1047,7 +1047,7 @@ class Blackjack(): enterGameText = "entered the game with a bet of" betText = f"{bet} GwendoBucks" - sendMessage = f"{userName} {enterGameText} {betText}" + sendMessage = f"{user_name} {enterGameText} {betText}" log_message = sendMessage self.bot.log(log_message) @@ -1064,7 +1064,7 @@ class Blackjack(): """ await self.bot.defer(ctx) channel = str(ctx.channel_id) - blackjackMinCards = 50 + blackjack_minCards = 50 self.bot.log("Starting blackjack game") await ctx.send("Starting a new game of blackjack") @@ -1074,8 +1074,8 @@ class Blackjack(): cardsLeft = len(cards["cards"]) # Shuffles if not enough cards - if cardsLeft < blackjackMinCards: - self._blackjackShuffle(channel) + if cardsLeft < blackjack_minCards: + self._blackjack_shuffle(channel) self.bot.log("Shuffling the blackjack deck...", channel) await ctx.channel.send("Shuffling the deck...") @@ -1101,9 +1101,9 @@ class Blackjack(): self.bot.database["blackjack games"].insert_one(newGame) - tableImagesPath = "resources/games/blackjackTables/" - emptyTableImagePath = f"resources/games/blackjackTable.png" - newTableImagePath = f"{tableImagesPath}blackjackTable{channel}.png" + tableImagesPath = "gwendolyn/resources/games/blackjack_tables/" + emptyTableImagePath = f"gwendolyn/resources/games/blackjack_table.png" + newTableImagePath = f"{tableImagesPath}blackjack_table{channel}.png" copyfile(emptyTableImagePath, newTableImagePath) gameStarted = True @@ -1112,20 +1112,20 @@ class Blackjack(): sendMessage = self.bot.long_strings["Blackjack started"] await ctx.channel.send(sendMessage) - tableImagesPath = "resources/games/blackjackTables/" - filePath = f"{tableImagesPath}blackjackTable{channel}.png" + tableImagesPath = "gwendolyn/resources/games/blackjack_tables/" + file_path = f"{tableImagesPath}blackjack_table{channel}.png" - oldImage = await ctx.channel.send(file=discord.File(filePath)) + old_image = await ctx.channel.send(file=discord.File(file_path)) - with open("resources/games/old_images/blackjack"+channel, "w") as f: - f.write(str(oldImage.id)) + with open("gwendolyn/resources/games/old_images/blackjack"+channel, "w") as f: + f.write(str(old_image.id)) await asyncio.sleep(30) gamedone = False - blackjackGames = self.bot.database["blackjack games"] - game = blackjackGames.find_one({"_id": channel}) + blackjack_games = self.bot.database["blackjack games"] + game = blackjack_games.find_one({"_id": channel}) if len(game["user hands"]) == 0: gamedone = True @@ -1136,10 +1136,10 @@ class Blackjack(): # Loop of game rounds if not gamedone: - self.bot.log("start() calling _blackjackLoop()", channel) - await self._blackjackLoop(ctx.channel, 1, gameID) + self.bot.log("start() calling _blackjack_loop()", channel) + await self._blackjack_loop(ctx.channel, 1, gameID) else: - new_message = self._blackjackFinish(channel) + new_message = self._blackjack_finish(channel) await ctx.channel.send(new_message) else: sendMessage = self.bot.long_strings["Blackjack going on"] @@ -1173,7 +1173,7 @@ class Blackjack(): The context of the command. """ channel = ctx.channel_id - self._blackjackShuffle(str(channel)) + self._blackjack_shuffle(str(channel)) self.bot.log("Shuffling the blackjack deck...", str(channel)) await ctx.send("Shuffling the deck...") @@ -1188,8 +1188,8 @@ class Blackjack(): """ channel = ctx.channel_id cardsLeft = 0 - blackjackGames = self.bot.database["blackjack cards"] - cards = blackjackGames.find_one({"_id": str(channel)}) + blackjack_games = self.bot.database["blackjack cards"] + cards = blackjack_games.find_one({"_id": str(channel)}) if cards is not None: cardsLeft = len(cards["cards"]) @@ -1234,11 +1234,11 @@ class DrawBlackjack(): self.bot.log("Drawing blackjack table", channel) game = self.bot.database["blackjack games"].find_one({"_id": channel}) - font = ImageFont.truetype('resources/fonts/futura-bold.ttf', 50) - smallFont = ImageFont.truetype('resources/fonts/futura-bold.ttf', 40) + font = ImageFont.truetype('gwendolyn/resources/fonts/futura-bold.ttf', 50) + smallFont = ImageFont.truetype('gwendolyn/resources/fonts/futura-bold.ttf', 40) self.SMALLBORDER = int(self.BORDER/3.5) - table = Image.open("resources/games/blackjackTable.png") + table = Image.open("gwendolyn/resources/games/blackjack_table.png") textImage = ImageDraw.Draw(table) hands = game["user hands"] @@ -1267,7 +1267,7 @@ class DrawBlackjack(): for x in range(len(hands)): key, value = list(hands.items())[x] - key = self.bot.database_funcs.getName(key) + key = self.bot.database_funcs.get_name(key) handParams = [ value["hand"], False, @@ -1359,8 +1359,8 @@ class DrawBlackjack(): textImage.text((textX, 1015), key, fill=white, font=smallFont) self.bot.log("Saving table image") - tableImagesPath = "resources/games/blackjackTables/" - tableImagePath = f"{tableImagesPath}blackjackTable{channel}.png" + tableImagesPath = "gwendolyn/resources/games/blackjack_tables/" + tableImagePath = f"{tableImagesPath}blackjack_table{channel}.png" table.save(tableImagePath) return @@ -1388,8 +1388,8 @@ class DrawBlackjack(): The image of the hand. """ self.bot.log("Drawing hand {hand}, {busted}, {blackjack}") - font = ImageFont.truetype('resources/fonts/futura-bold.ttf', 200) - font2 = ImageFont.truetype('resources/fonts/futura-bold.ttf', 120) + font = ImageFont.truetype('gwendolyn/resources/fonts/futura-bold.ttf', 200) + font2 = ImageFont.truetype('gwendolyn/resources/fonts/futura-bold.ttf', 120) length = len(hand) backgroundWidth = (self.BORDER*2)+691+(125*(length-1)) backgroundSize = (backgroundWidth, (self.BORDER*2)+1065) @@ -1398,15 +1398,15 @@ class DrawBlackjack(): cardY = self.BORDER+self.PLACEMENT[1] if dealer: - img = Image.open("resources/games/cards/"+hand[0].upper()+".png") + img = Image.open("gwendolyn/resources/games/cards/"+hand[0].upper()+".png") cardPosition = (self.BORDER+self.PLACEMENT[0], cardY) background.paste(img, cardPosition, img) - img = Image.open("resources/games/cards/red_back.png") + img = Image.open("gwendolyn/resources/games/cards/red_back.png") cardPosition = (125+self.BORDER+self.PLACEMENT[0], cardY) background.paste(img, cardPosition, img) else: for x in range(length): - cardPath = f"resources/games/cards/{hand[x].upper()}.png" + cardPath = f"gwendolyn/resources/games/cards/{hand[x].upper()}.png" img = Image.open(cardPath) cardPosition = (self.BORDER+(x*125)+self.PLACEMENT[0], cardY) background.paste(img, cardPosition, img) diff --git a/funcs/games/connect_four.py b/gwendolyn/funcs/games/connect_four.py similarity index 93% rename from funcs/games/connect_four.py rename to gwendolyn/funcs/games/connect_four.py index 4667882..430b38c 100644 --- a/funcs/games/connect_four.py +++ b/gwendolyn/funcs/games/connect_four.py @@ -133,8 +133,8 @@ class ConnectFour(): gwendoTurn = (players[0] == f"#{self.bot.user.id}") startedGame = True - opponentName = self.bot.database_funcs.getName(opponent) - turnName = self.bot.database_funcs.getName(players[0]) + opponentName = self.bot.database_funcs.get_name(opponent) + turnName = self.bot.database_funcs.get_name(players[0]) startedText = f"Started game against {opponentName}{diffText}." turnText = f"It's {turnName}'s turn" @@ -146,20 +146,20 @@ class ConnectFour(): # Sets the whole game in motion if startedGame: - boardsPath = "resources/games/connect4Boards/" - filePath = f"{boardsPath}board{ctx.channel_id}.png" - oldImage = await ctx.channel.send(file=discord.File(filePath)) + boardsPath = "gwendolyn/resources/games/connect4Boards/" + file_path = f"{boardsPath}board{ctx.channel_id}.png" + old_image = await ctx.channel.send(file=discord.File(file_path)) - old_imagesPath = "resources/games/old_images/" - oldImagePath = f"{old_imagesPath}connect_four{ctx.channel_id}" - with open(oldImagePath, "w") as f: - f.write(str(oldImage.id)) + old_imagesPath = "gwendolyn/resources/games/old_images/" + old_imagePath = f"{old_imagesPath}connect_four{ctx.channel_id}" + with open(old_imagePath, "w") as f: + f.write(str(old_image.id)) if gwendoTurn: await self._connect_fourAI(ctx) else: for reaction in self.REACTIONS: - await oldImage.add_reaction(reaction) + await old_image.add_reaction(reaction) async def placePiece(self, ctx: Union[SlashContext, discord.Message], user: int, column: int): @@ -183,7 +183,7 @@ class ConnectFour(): connect4Games = self.bot.database["connect 4 games"] game = connect4Games.find_one({"_id": channel}) playerNumber = game["players"].index(user)+1 - userName = self.bot.database_funcs.getName(user) + user_name = self.bot.database_funcs.get_name(user) placedPiece = False if game is None: @@ -216,8 +216,8 @@ class ConnectFour(): connect4Games.update_one({"_id": channel}, updater) sendMessage = "{} placed a piece in column {} and won. " - sendMessage = sendMessage.format(userName, column+1) - log_message = f"{userName} won" + sendMessage = sendMessage.format(user_name, column+1) + log_message = f"{user_name} won" winAmount = int(game["difficulty"])**2+5 if game["players"][won-1] != f"#{self.bot.user.id}": sendMessage += "Adding {} GwendoBucks to their account" @@ -229,9 +229,9 @@ class ConnectFour(): else: gameWon = False otherUserId = game["players"][turn] - otherUserName = self.bot.database_funcs.getName(otherUserId) + otherUserName = self.bot.database_funcs.get_name(otherUserId) sendMessage = self.bot.long_strings["Connect 4 placed"] - formatParams = [userName, column+1, otherUserName] + formatParams = [user_name, column+1, otherUserName] sendMessage = sendMessage.format(*formatParams) log_message = "They placed the piece" @@ -245,28 +245,28 @@ class ConnectFour(): if placedPiece: self.draw.drawImage(channel) - oldImagePath = f"resources/games/old_images/connect_four{channel}" - with open(oldImagePath, "r") as f: - oldImage = await ctx.channel.fetch_message(int(f.read())) + old_imagePath = f"gwendolyn/resources/games/old_images/connect_four{channel}" + with open(old_imagePath, "r") as f: + old_image = await ctx.channel.fetch_message(int(f.read())) - if oldImage is not None: - await oldImage.delete() + if old_image is not None: + await old_image.delete() else: 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)) + file_path = f"gwendolyn/resources/games/connect4Boards/board{channel}.png" + old_image = await ctx.channel.send(file=discord.File(file_path)) if gameWon: self._endGame(channel) else: - with open(oldImagePath, "w") as f: - f.write(str(oldImage.id)) + with open(old_imagePath, "w") as f: + f.write(str(old_image.id)) if gwendoTurn: await self._connect_fourAI(ctx) else: for reaction in self.REACTIONS: - await oldImage.add_reaction(reaction) + await old_image.add_reaction(reaction) async def surrender(self, ctx: SlashContext): """ @@ -285,7 +285,7 @@ class ConnectFour(): loserIndex = game["players"].index(f"#{ctx.author.id}") winnerIndex = (loserIndex+1) % 2 winnerID = game["players"][winnerIndex] - winnerName = self.bot.database_funcs.getName(winnerID) + winnerName = self.bot.database_funcs.get_name(winnerID) sendMessage = f"{ctx.author.display_name} surrenders." sendMessage += f" This means {winnerName} is the winner." @@ -295,12 +295,12 @@ class ConnectFour(): sendMessage += f" Adding {reward} to their account" await ctx.send(sendMessage) - oldImagePath = f"resources/games/old_images/connect_four{channel}" - with open(oldImagePath, "r") as f: - oldImage = await ctx.channel.fetch_message(int(f.read())) + old_imagePath = f"gwendolyn/resources/games/old_images/connect_four{channel}" + with open(old_imagePath, "r") as f: + old_image = await ctx.channel.fetch_message(int(f.read())) - if oldImage is not None: - await oldImage.delete() + if old_image is not None: + await old_image.delete() else: self.bot.log("The old image was already deleted") @@ -331,7 +331,7 @@ class ConnectFour(): """ placementX, placementY = -1, column - for x, line in enumerate(board): + for x, line in list(enumerate(board))[::-1]: if line[column] == 0: placementX = x break @@ -353,7 +353,7 @@ class ConnectFour(): reward = difficulty**2 + 5 self.bot.money.addMoney(game["players"][winner-1], reward) - self.bot.database_funcs.deleteGame("connect 4 games", channel) + self.bot.database_funcs.delete_game("connect 4 games", channel) def _isWon(self, board: dict): won = 0 @@ -444,7 +444,7 @@ class ConnectFour(): player = game["players"].index(f"#{self.bot.user.id}")+1 difficulty = game["difficulty"] - scores = [int(-math.inf) for _ in range(COLUMNCOUNT)] + scores = [-math.inf for _ in range(COLUMNCOUNT)] for column in range(COLUMNCOUNT): testBoard = copy.deepcopy(board) testBoard = self._placeOnBoard(testBoard, player, column) @@ -454,8 +454,8 @@ class ConnectFour(): difficulty, player % 2+1, player, - int(-math.inf), - int(math.inf), + -math.inf, + math.inf, False ] scores[column] = await self._minimax(*_minimaxParams) @@ -564,7 +564,7 @@ class ConnectFour(): if depth == 0 or terminal or (points > 5000 or points < -6000): return points if maximizingPlayer: - value = int(-math.inf) + value = -math.inf for column in range(0, COLUMNCOUNT): testBoard = copy.deepcopy(board) testBoard = self._placeOnBoard(testBoard, player, column) @@ -587,7 +587,7 @@ class ConnectFour(): break return value else: - value = int(math.inf) + value = math.inf for column in range(0, COLUMNCOUNT): testBoard = copy.deepcopy(board) testBoard = self._placeOnBoard(testBoard, player, column) @@ -694,7 +694,7 @@ class DrawConnectFour(): winbarAlpha = 160 self.WINBARCOLOR = (250, 250, 250, 255) self.PIECESIZE = 300 - fontPath = "resources/fonts/futura-bold.ttf" + fontPath = "gwendolyn/resources/fonts/futura-bold.ttf" self.FONT = ImageFont.truetype(fontPath, self.TEXTSIZE) @@ -740,7 +740,7 @@ class DrawConnectFour(): self._drawFooter(d, game) - background.save(f"resources/games/connect4Boards/board{channel}.png") + background.save(f"gwendolyn/resources/games/connect4Boards/board{channel}.png") def _drawBoard(self, d: ImageDraw): # This whole part was the easiest way to make a rectangle with @@ -1023,12 +1023,12 @@ class DrawConnectFour(): if game["players"][0] == "Gwendolyn": player1 = "Gwendolyn" else: - player1 = self.bot.database_funcs.getName(game["players"][0]) + player1 = self.bot.database_funcs.get_name(game["players"][0]) if game["players"][1] == "Gwendolyn": player2 = "Gwendolyn" else: - player2 = self.bot.database_funcs.getName(game["players"][1]) + player2 = self.bot.database_funcs.get_name(game["players"][1]) exampleHeight = self.HEIGHT - self.BORDER exampleHeight += (self.BOTTOMBORDER+self.BORDER)//2 - self.TEXTSIZE//2 diff --git a/funcs/games/games_container.py b/gwendolyn/funcs/games/games_container.py similarity index 100% rename from funcs/games/games_container.py rename to gwendolyn/funcs/games/games_container.py diff --git a/funcs/games/hangman.py b/gwendolyn/funcs/games/hangman.py similarity index 90% rename from funcs/games/hangman.py rename to gwendolyn/funcs/games/hangman.py index 4b65d70..46e01da 100644 --- a/funcs/games/hangman.py +++ b/gwendolyn/funcs/games/hangman.py @@ -46,7 +46,7 @@ class Hangman(): self.__bot = bot self.__draw = DrawHangman(bot) self.__APIURL = "https://api.wordnik.com/v4/words.json/randomWords?" - apiKey = self.__bot.credentials.wordnikKey + apiKey = self.__bot.credentials["wordnik_key"] self.__APIPARAMS = { "hasDictionaryDef": True, "minCorpusCount": 5000, @@ -72,7 +72,7 @@ class Hangman(): channel = str(ctx.channel_id) user = f"#{ctx.author.id}" game = self.__bot.database["hangman games"].find_one({"_id": channel}) - userName = self.__bot.database_funcs.getName(user) + user_name = self.__bot.database_funcs.get_name(user) startedGame = False if game is None: @@ -100,7 +100,7 @@ class Hangman(): self.__draw.drawImage(channel) log_message = "Game started" - sendMessage = f"{userName} started game of hangman." + sendMessage = f"{user_name} started game of hangman." startedGame = True else: log_message = "There was already a game going on" @@ -110,9 +110,9 @@ class Hangman(): await ctx.send(sendMessage) if startedGame: - boardsPath = "resources/games/hangmanBoards/" - filePath = f"{boardsPath}hangmanBoard{channel}.png" - newImage = await ctx.channel.send(file=discord.File(filePath)) + boardsPath = "gwendolyn/resources/games/hangman_boards/" + file_path = f"{boardsPath}hangman_board{channel}.png" + newImage = await ctx.channel.send(file=discord.File(file_path)) blankMessage = await ctx.channel.send("_ _") reactionMessages = { @@ -120,10 +120,10 @@ class Hangman(): blankMessage: remainingLetters[15:] } - oldMessages = f"{newImage.id}\n{blankMessage.id}" + old_messages = f"{newImage.id}\n{blankMessage.id}" - with open(f"resources/games/old_images/hangman{channel}", "w") as f: - f.write(oldMessages) + with open(f"gwendolyn/resources/games/old_images/hangman{channel}", "w") as f: + f.write(old_messages) for message, letters in reactionMessages.items(): for letter in letters: @@ -149,13 +149,13 @@ class Hangman(): else: self.__bot.database["hangman games"].delete_one({"_id": channel}) - with open(f"resources/games/old_images/hangman{channel}", "r") as f: + with open(f"gwendolyn/resources/games/old_images/hangman{channel}", "r") as f: messages = f.read().splitlines() for message in messages: - oldMessage = await ctx.channel.fetch_message(int(message)) + old_message = await ctx.channel.fetch_message(int(message)) self.__bot.log("Deleting old message") - await oldMessage.delete() + await old_message.delete() await ctx.send("Game stopped") @@ -173,8 +173,8 @@ class Hangman(): The guess. """ channel = str(message.channel.id) - hangmanGames = self.__bot.database["hangman games"] - game = hangmanGames.find_one({"_id": channel}) + hangman_games = self.__bot.database["hangman games"] + game = hangman_games.find_one({"_id": channel}) gameExists = (game is not None) singleLetter = (len(guess) == 1 and guess.isalpha()) @@ -189,18 +189,18 @@ class Hangman(): if guess == letter: correctGuess += 1 updater = {"$set": {f"guessed.{x}": True}} - hangmanGames.update_one({"_id": channel}, updater) + hangman_games.update_one({"_id": channel}, updater) if correctGuess == 0: updater = {"$inc": {"misses": 1}} - hangmanGames.update_one({"_id": channel}, updater) + hangman_games.update_one({"_id": channel}, updater) updater = {"$push": {"guessed letters": guess}} - hangmanGames.update_one({"_id": channel}, updater) + hangman_games.update_one({"_id": channel}, updater) remainingLetters = list(string.ascii_uppercase) - game = hangmanGames.find_one({"_id": channel}) + game = hangman_games.find_one({"_id": channel}) for letter in game["guessed letters"]: remainingLetters.remove(letter) @@ -215,28 +215,28 @@ class Hangman(): self.__draw.drawImage(channel) if game["misses"] == 6: - hangmanGames.delete_one({"_id": channel}) + hangman_games.delete_one({"_id": channel}) sendMessage += self.__bot.long_strings["Hangman lost game"] remainingLetters = [] elif all(game["guessed"]): - hangmanGames.delete_one({"_id": channel}) + hangman_games.delete_one({"_id": channel}) self.__bot.money.addMoney(user, 15) sendMessage += self.__bot.long_strings["Hangman guessed word"] remainingLetters = [] await message.channel.send(sendMessage) - with open(f"resources/games/old_images/hangman{channel}", "r") as f: - oldMessageIDs = f.read().splitlines() + with open(f"gwendolyn/resources/games/old_images/hangman{channel}", "r") as f: + old_message_ids = f.read().splitlines() - for oldID in oldMessageIDs: - oldMessage = await message.channel.fetch_message(int(oldID)) + for oldID in old_message_ids: + old_message = await message.channel.fetch_message(int(oldID)) self.__bot.log("Deleting old message") - await oldMessage.delete() + await old_message.delete() - boardsPath = "resources/games/hangmanBoards/" - filePath = f"{boardsPath}hangmanBoard{channel}.png" - newImage = await message.channel.send(file=discord.File(filePath)) + boardsPath = "gwendolyn/resources/games/hangman_boards/" + file_path = f"{boardsPath}hangman_board{channel}.png" + newImage = await message.channel.send(file=discord.File(file_path)) if len(remainingLetters) > 0: if len(remainingLetters) > 15: @@ -250,13 +250,13 @@ class Hangman(): reactionMessages = {newImage: remainingLetters} if blankMessage != "": - oldMessages = f"{newImage.id}\n{blankMessage.id}" + old_messages = f"{newImage.id}\n{blankMessage.id}" else: - oldMessages = str(newImage.id) + old_messages = str(newImage.id) - oldImagePath = f"resources/games/old_images/hangman{channel}" - with open(oldImagePath, "w") as f: - f.write(oldMessages) + old_imagePath = f"gwendolyn/resources/games/old_images/hangman{channel}" + with open(old_imagePath, "w") as f: + f.write(old_messages) for message, letters in reactionMessages.items(): for letter in letters: @@ -315,7 +315,7 @@ class DrawHangman(): LETTERSIZE = 75 # Wrong guesses letter size WORDSIZE = 70 # Correct guesses letter size - FONTPATH = "resources/fonts/comic-sans-bold.ttf" + FONTPATH = "gwendolyn/resources/fonts/comic-sans-bold.ttf" self.__FONT = ImageFont.truetype(FONTPATH, LETTERSIZE) self.__SMALLFONT = ImageFont.truetype(FONTPATH, WORDSIZE) @@ -588,7 +588,7 @@ class DrawHangman(): random.seed(game["game ID"]) - background = Image.open("resources/paper.jpg") + background = Image.open("gwendolyn/resources/paper.jpg") gallow = self.__drawGallows() man = self.__drawMan(game["misses"], game["game ID"]) @@ -608,5 +608,5 @@ class DrawHangman(): missesTextWidth = missesText.size[0] background.paste(missesText, (850-missesTextWidth//2, 50), missesText) - boardPath = f"resources/games/hangmanBoards/hangmanBoard{channel}.png" + boardPath = f"gwendolyn/resources/games/hangman_boards/hangman_board{channel}.png" background.save(boardPath) diff --git a/funcs/games/hex.py b/gwendolyn/funcs/games/hex.py similarity index 88% rename from funcs/games/hex.py rename to gwendolyn/funcs/games/hex.py index 4f5279b..5700bd8 100644 --- a/funcs/games/hex.py +++ b/gwendolyn/funcs/games/hex.py @@ -28,24 +28,24 @@ class HexGame(): await ctx.send("You can't surrender when you're not a player.") else: opponent = (players.index(user) + 1) % 2 - opponentName = self.bot.database_funcs.getName(players[opponent]) + opponentName = self.bot.database_funcs.get_name(players[opponent]) self.bot.database["hex games"].update_one({"_id":channel},{"$set":{"winner":opponent + 1}}) await ctx.send(f"{ctx.author.display_name} surrendered") - with open(f"resources/games/old_images/hex{channel}", "r") as f: - oldImage = await ctx.channel.fetch_message(int(f.read())) + with open(f"gwendolyn/resources/games/old_images/hex{channel}", "r") as f: + old_image = await ctx.channel.fetch_message(int(f.read())) - if oldImage is not None: - await oldImage.delete() + if old_image is not None: + await old_image.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)) + file_path = f"gwendolyn/resources/games/hex_boards/board{channel}.png" + old_image = await ctx.channel.send(file = discord.File(file_path)) - with open(f"resources/games/old_images/hex{channel}", "w") as f: - f.write(str(oldImage.id)) + with open(f"gwendolyn/resources/games/old_images/hex{channel}", "w") as f: + f.write(str(old_image.id)) self.bot.database["hex games"].delete_one({"_id":channel}) @@ -72,23 +72,23 @@ class HexGame(): opponent = game["players"][::-1][game["turn"]-1] gwendoTurn = (opponent == f"#{self.bot.user.id}") - opponentName = self.bot.database_funcs.getName(opponent) + opponentName = self.bot.database_funcs.get_name(opponent) await ctx.send(f"The color of the players were swapped. It is now {opponentName}'s turn") - with open(f"resources/games/old_images/hex{channel}", "r") as f: - oldImage = await ctx.channel.fetch_message(int(f.read())) + with open(f"gwendolyn/resources/games/old_images/hex{channel}", "r") as f: + old_image = await ctx.channel.fetch_message(int(f.read())) - if oldImage is not None: - await oldImage.delete() + if old_image is not None: + await old_image.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)) + file_path = f"gwendolyn/resources/games/hex_boards/board{channel}.png" + old_image = await ctx.channel.send(file = discord.File(file_path)) - with open(f"resources/games/old_images/hex{channel}", "w") as f: - f.write(str(oldImage.id)) + with open(f"gwendolyn/resources/games/old_images/hex{channel}", "w") as f: + f.write(str(old_image.id)) if gwendoTurn: await self.hexAI(ctx) @@ -167,7 +167,7 @@ class HexGame(): gwendoTurn = (players[0] == f"#{self.bot.user.id}") startedGame = True - turnName = self.bot.database_funcs.getName(players[0]) + turnName = self.bot.database_funcs.get_name(players[0]) sendMessage = f"Started Hex game against {opponentName}{diffText}. It's {turnName}'s turn" log_message = "Game started" @@ -175,10 +175,10 @@ class HexGame(): self.bot.log(log_message) if startedGame: - filePath = f"resources/games/hexBoards/board{ctx.channel_id}.png" - newImage = await ctx.channel.send(file = discord.File(filePath)) + file_path = f"gwendolyn/resources/games/hex_boards/board{ctx.channel_id}.png" + newImage = await ctx.channel.send(file = discord.File(file_path)) - with open(f"resources/games/old_images/hex{ctx.channel_id}", "w") as f: + with open(f"gwendolyn/resources/games/old_images/hex{ctx.channel_id}", "w") as f: f.write(str(newImage.id)) if gwendoTurn: @@ -199,7 +199,7 @@ class HexGame(): else: 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.database_funcs.getName(game['players'][0])} and {self.bot.database_funcs.getName(game['players'][1])}." + sendMessage = f"You can't place when you're not in the game. The game's players are: {self.bot.database_funcs.get_name(game['players'][0])} and {self.bot.database_funcs.get_name(game['players'][1])}." self.bot.log("They aren't in the game") elif players[game["turn"]-1] != user: sendMessage = "It's not your turn" @@ -228,12 +228,12 @@ class HexGame(): if winner == 0: # Continue with the game. gameWon = False - sendMessage = self.bot.database_funcs.getName(game["players"][player-1])+" placed at "+position.upper()+". It's now "+self.bot.database_funcs.getName(game["players"][turn-1])+"'s turn."# The score is "+str(score) + sendMessage = self.bot.database_funcs.get_name(game["players"][player-1])+" placed at "+position.upper()+". It's now "+self.bot.database_funcs.get_name(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.database_funcs.getName(game["players"][player-1])+" placed at "+position.upper()+" and won!" + sendMessage = self.bot.database_funcs.get_name(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." @@ -258,17 +258,17 @@ class HexGame(): # Update the board self.draw.drawHexPlacement(channel,player, position) - with open(f"resources/games/old_images/hex{channel}", "r") as f: - oldImage = await ctx.channel.fetch_message(int(f.read())) + with open(f"gwendolyn/resources/games/old_images/hex{channel}", "r") as f: + old_image = await ctx.channel.fetch_message(int(f.read())) - if oldImage is not None: - await oldImage.delete() + if old_image is not None: + await old_image.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)) + file_path = f"gwendolyn/resources/games/hex_boards/board{channel}.png" + old_image = await ctx.channel.send(file = discord.File(file_path)) if gameWon: self.bot.log("Dealing with the winning player") @@ -281,8 +281,8 @@ class HexGame(): self.bot.database["hex games"].delete_one({"_id":channel}) else: - with open(f"resources/games/old_images/hex{channel}", "w") as f: - f.write(str(oldImage.id)) + with open(f"gwendolyn/resources/games/old_images/hex{channel}", "w") as f: + f.write(str(old_image.id)) if gwendoTurn: await self.hexAI(ctx) @@ -321,7 +321,7 @@ class HexGame(): sendMessage = "It's not your turn" else: turn = game["turn"] - self.bot.log("Undoing {}'s last move".format(self.bot.database_funcs.getName(user))) + self.bot.log("Undoing {}'s last move".format(self.bot.database_funcs.get_name(user))) lastMove = game["gameHistory"].pop() game["board"][lastMove[0]][lastMove[1]] = 0 @@ -337,20 +337,20 @@ class HexGame(): await ctx.send(sendMessage) if undid: - with open(f"resources/games/old_images/hex{channel}", "r") as f: - oldImage = await ctx.channel.fetch_message(int(f.read())) + with open(f"gwendolyn/resources/games/old_images/hex{channel}", "r") as f: + old_image = await ctx.channel.fetch_message(int(f.read())) - if oldImage is not None: - await oldImage.delete() + if old_image is not None: + await old_image.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)) + file_path = f"gwendolyn/resources/games/hex_boards/board{channel}.png" + old_image = await ctx.channel.send(file = discord.File(file_path)) - with open(f"resources/games/old_images/hex{channel}", "w") as f: - f.write(str(oldImage.id)) + with open(f"gwendolyn/resources/games/old_images/hex{channel}", "w") as f: + f.write(str(old_image.id)) # Plays as the AI @@ -474,7 +474,7 @@ class DrawHex(): self.HEXAGONHEIGHT = 1.5 * self.SIDELENGTH self.FONTSIZE = 45 self.TEXTCOLOR = (0,0,0) - self.FONT = ImageFont.truetype('resources/fonts/futura-bold.ttf', self.FONTSIZE) + self.FONT = ImageFont.truetype('gwendolyn/resources/fonts/futura-bold.ttf', self.FONTSIZE) self.LINETHICKNESS = 15 self.HEXTHICKNESS = 6 # This is half the width of the background lining between every hex @@ -490,7 +490,7 @@ class DrawHex(): self.COLYTHICKNESS = self.COLHEXTHICKNESS * math.sin(math.pi/6) # The Name display things: self.NAMESIZE = 60 - self.NAMEFONT = ImageFont.truetype('resources/fonts/futura-bold.ttf', self.NAMESIZE) + self.NAMEFONT = ImageFont.truetype('gwendolyn/resources/fonts/futura-bold.ttf', self.NAMESIZE) self.XNAME = {1:175, 2:self.CANVASWIDTH-100} self.YNAME = {1:self.CANVASHEIGHT-150, 2:150} self.NAMEHEXPADDING = 90 @@ -556,7 +556,7 @@ class DrawHex(): game = self.bot.database["hex games"].find_one({"_id":channel}) for p in [1,2]: - playername = self.bot.database_funcs.getName(game["players"][p-1]) + playername = self.bot.database_funcs.get_name(game["players"][p-1]) # Draw name x = self.XNAME[p] x -= self.NAMEFONT.getsize(playername)[0] if p==2 else 0 # player2's name is right-aligned @@ -573,13 +573,13 @@ class DrawHex(): (x, y+self.SMALLSIDELENGTH), ],fill = self.PIECECOLOR[p]) - im.save("resources/games/hexBoards/board"+channel+".png") + im.save("gwendolyn/resources/games/hex_boards/board"+channel+".png") def drawHexPlacement(self, channel,player,position): - FILEPATH = "resources/games/hexBoards/board"+channel+".png" + FILEPATH = "gwendolyn/resources/games/hex_boards/board"+channel+".png" self.bot.log(f"Drawing a newly placed hex. Filename: board{channel}.png") # Translates position @@ -589,7 +589,7 @@ class DrawHex(): row = int(position[1:])-1 # Find the coordinates for the filled hex drawing - hexCoords = [ + hex_coords = [ (self.BOARDCOORDINATES[row][column][0]+self.COLXTHICKNESS, self.BOARDCOORDINATES[row][column][1] + self.COLYTHICKNESS), (self.BOARDCOORDINATES[row][column][0]+self.HEXAGONWIDTH/2, self.BOARDCOORDINATES[row][column][1]-0.5*self.SIDELENGTH + self.COLHEXTHICKNESS), (self.BOARDCOORDINATES[row][column][0]+self.HEXAGONWIDTH-self.COLXTHICKNESS, self.BOARDCOORDINATES[row][column][1] + self.COLYTHICKNESS), @@ -603,7 +603,7 @@ class DrawHex(): with Image.open(FILEPATH) as im: d = ImageDraw.Draw(im,"RGBA") # Draws the hex piece - d.polygon(hexCoords,fill = self.PIECECOLOR[player], outline = self.BETWEENCOLOR) + d.polygon(hex_coords,fill = self.PIECECOLOR[player], outline = self.BETWEENCOLOR) # Save im.save(FILEPATH) @@ -611,7 +611,7 @@ class DrawHex(): self.bot.log("Error drawing new hex on board (error code 1541") def drawSwap(self, channel): - FILEPATH = "resources/games/hexBoards/board"+channel+".png" + FILEPATH = "gwendolyn/resources/games/hex_boards/board"+channel+".png" game = self.bot.database["hex games"].find_one({"_id":channel}) # Opens the image try: @@ -620,7 +620,7 @@ class DrawHex(): # Write player names and color for p in [1,2]: - playername = self.bot.database_funcs.getName(game["players"][p%2]) + playername = self.bot.database_funcs.get_name(game["players"][p%2]) x = self.XNAME[p] x -= self.NAMEFONT.getsize(playername)[0] if p==2 else 0 # player2's name is right-aligned diff --git a/funcs/games/invest.py b/gwendolyn/funcs/games/invest.py similarity index 95% rename from funcs/games/invest.py rename to gwendolyn/funcs/games/invest.py index 5e65c90..8977503 100644 --- a/funcs/games/invest.py +++ b/gwendolyn/funcs/games/invest.py @@ -65,12 +65,12 @@ class Invest(): investmentsDatabase = self.bot.database["investments"] userInvestments = investmentsDatabase.find_one({"_id": user}) - userName = self.bot.database_funcs.getName(user) + user_name = self.bot.database_funcs.get_name(user) if userInvestments in [None, {}]: - return f"{userName} does not have a stock portfolio." + return f"{user_name} does not have a stock portfolio." else: - portfolio = f"**Stock portfolio for {userName}**" + portfolio = f"**Stock portfolio for {user_name}**" for key, value in list(userInvestments["investments"].items()): purchaseValue = value["purchased for"] @@ -162,9 +162,9 @@ class Invest(): } investmentsDatabase.insert_one(newUser) - userName = self.bot.database_funcs.getName(user) + user_name = self.bot.database_funcs.get_name(user) sendMessage = "{} bought {} GwendoBucks worth of {} stock" - sendMessage = sendMessage.format(userName, buyAmount, stock) + sendMessage = sendMessage.format(user_name, buyAmount, stock) return sendMessage def sellStock(self, user: str, stock: str, sellAmount: int): @@ -219,9 +219,9 @@ class Invest(): updater = {"$unset": {f"investments.{stock}": ""}} investmentsDatabase.update_one({"_id": user}, updater) - userName = self.bot.database_funcs.getName(user) + user_name = self.bot.database_funcs.get_name(user) sendMessage = "{} sold {} GwendoBucks worth of {} stock" - return sendMessage.format(userName, sellAmount, stock) + return sendMessage.format(user_name, sellAmount, stock) else: return f"You don't have enough {stock} stocks to do that" else: diff --git a/funcs/games/money.py b/gwendolyn/funcs/games/money.py similarity index 90% rename from funcs/games/money.py rename to gwendolyn/funcs/games/money.py index 1113fbe..51d179a 100644 --- a/funcs/games/money.py +++ b/gwendolyn/funcs/games/money.py @@ -69,11 +69,11 @@ class Money(): """ await self.bot.defer(ctx) response = self.checkBalance("#"+str(ctx.author.id)) - userName = ctx.author.display_name + user_name = ctx.author.display_name if response == 1: - new_message = f"{userName} has {response} GwendoBuck" + new_message = f"{user_name} has {response} GwendoBuck" else: - new_message = f"{userName} has {response} GwendoBucks" + new_message = f"{user_name} has {response} GwendoBucks" await ctx.send(new_message) # Adds money to the account of a user @@ -98,7 +98,7 @@ class Money(): else: newUser = { "_id": user, - "user name": self.bot.database_funcs.getName(user), + "user name": self.bot.database_funcs.get_name(user), "money": amount } self.database["users"].insert_one(newUser) @@ -120,13 +120,13 @@ class Money(): """ await self.bot.defer(ctx) username = user.display_name - if self.bot.database_funcs.getID(username) is None: + if self.bot.database_funcs.get_id(username) is None: async for member in ctx.guild.fetch_members(limit=None): if member.display_name.lower() == username.lower(): username = member.display_name - userID = f"#{member.id}" + user_id = f"#{member.id}" newUser = { - "_id": userID, + "_id": user_id, "user name": username, "money": 0 } @@ -134,7 +134,7 @@ class Money(): userid = f"#{ctx.author.id}" userData = self.database["users"].find_one({"_id": userid}) - targetUser = self.bot.database_funcs.getID(username) + targetUser = self.bot.database_funcs.get_id(username) if amount <= 0: self.bot.log("They tried to steal") diff --git a/funcs/games/trivia.py b/gwendolyn/funcs/games/trivia.py similarity index 96% rename from funcs/games/trivia.py rename to gwendolyn/funcs/games/trivia.py index 8068e65..d03c2be 100644 --- a/funcs/games/trivia.py +++ b/gwendolyn/funcs/games/trivia.py @@ -181,8 +181,8 @@ class Trivia(): self.triviaCountPoints(channelId) - deleteGameParams = ["trivia questions", channelId] - self.bot.database_funcs.deleteGame(*deleteGameParams) + delete_gameParams = ["trivia questions", channelId] + self.bot.database_funcs.delete_game(*delete_gameParams) self.bot.log("Time's up for the trivia question", channelId) sendMessage = self.bot.long_strings["Trivia time up"] @@ -196,8 +196,8 @@ class Trivia(): userId = f"#{ctx.author.id}" response = self.triviaAnswer(userId, channelId, answer) if response is None: - userName = ctx.author.display_name - await ctx.send(f"{userName} answered **{answer}**") + user_name = ctx.author.display_name + await ctx.send(f"{user_name} answered **{answer}**") else: await ctx.send(response) else: diff --git a/funcs/lookup/__init__.py b/gwendolyn/funcs/lookup/__init__.py similarity index 100% rename from funcs/lookup/__init__.py rename to gwendolyn/funcs/lookup/__init__.py diff --git a/funcs/lookup/lookup_funcs.py b/gwendolyn/funcs/lookup/lookup_funcs.py similarity index 97% rename from funcs/lookup/lookup_funcs.py rename to gwendolyn/funcs/lookup/lookup_funcs.py index 7d0654c..efe24ca 100644 --- a/funcs/lookup/lookup_funcs.py +++ b/gwendolyn/funcs/lookup/lookup_funcs.py @@ -2,7 +2,7 @@ import math import json import discord -from utils import cap +from gwendolyn.utils import cap class LookupFuncs(): @@ -29,7 +29,7 @@ class LookupFuncs(): await ctx.send("I don't know that monster...") else: # Opens "monsters.json" - data = json.load(open('resources/lookup/monsters.json', encoding = "utf8")) + data = json.load(open('gwendolyn/resources/lookup/monsters.json', encoding = "utf8")) for monster in data: if "name" in monster and str(query) == monster["name"]: self.bot.log("Found it!") @@ -149,7 +149,7 @@ class LookupFuncs(): self.bot.log("Looking up "+query) # Opens "spells.json" - data = json.load(open('resources/lookup/spells.json', encoding = "utf8")) + data = json.load(open('gwendolyn/resources/lookup/spells.json', encoding = "utf8")) if query in data: self.bot.log("Returning spell information") 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']}") diff --git a/funcs/other/__init__.py b/gwendolyn/funcs/other/__init__.py similarity index 100% rename from funcs/other/__init__.py rename to gwendolyn/funcs/other/__init__.py diff --git a/funcs/other/bedreNetflix.py b/gwendolyn/funcs/other/bedre_netflix.py similarity index 92% rename from funcs/other/bedreNetflix.py rename to gwendolyn/funcs/other/bedre_netflix.py index 5fb6c4a..f8226ec 100644 --- a/funcs/other/bedreNetflix.py +++ b/gwendolyn/funcs/other/bedre_netflix.py @@ -3,7 +3,7 @@ import requests, imdb, discord, json, math, time, asyncio class BedreNetflix(): def __init__(self,bot): self.bot = bot - ip = ["localhost", "192.168.0.40"][self.bot.options.testing] + ip = ["localhost", "192.168.0.40"][self.bot.options["testing"]] self.radarrURL = "http://"+ip+":7878/api/v3/" self.sonarrURL = "http://"+ip+":8989/api/" @@ -30,7 +30,7 @@ class BedreNetflix(): messageTitle = "**Is it any of these movies?**" messageText = "" - imdbIds = [] + imdb_ids = [] for x, movie in enumerate(movies): try: @@ -40,17 +40,17 @@ class BedreNetflix(): messageText += "\n"+str(x+1)+") "+movie["title"] except: messageText += "Error" - imdbIds.append(movie.movieID) + imdb_ids.append(movie.movieID) - self.bot.log("Returning a list of "+str(len(movies))+" possible movies: "+str(imdbIds)) + self.bot.log("Returning a list of "+str(len(movies))+" possible movies: "+str(imdb_ids)) em = discord.Embed(title=messageTitle,description=messageText,colour=0x00FF00) message = await ctx.send(embed=em) - messageData = {"messageID":message.id,"imdbIds":imdbIds} + messageData = {"message_id":message.id,"imdb_ids":imdb_ids} - with open("resources/bedreNetflix/oldMessage"+str(ctx.channel.id),"w") as f: + with open("gwendolyn/resources/bedre_netflix/old_message"+str(ctx.channel.id),"w") as f: json.dump(messageData,f) if len(movies) == 1: @@ -66,7 +66,7 @@ class BedreNetflix(): await message.clear_reactions() #Adds the requested movie to Bedre Netflix - async def addMovie(self, message, imdbId, editMessage = True): + async def add_movie(self, message, imdbId, editMessage = True): if imdbId == None: self.bot.log("Did not find what the user was searching for") if editMessage: @@ -75,7 +75,7 @@ class BedreNetflix(): await message.channel.send("Try searching for the IMDB id") else: self.bot.log("Trying to add movie "+str(imdbId)) - apiKey = self.bot.credentials.radarrKey + apiKey = self.bot.credentials["radarr_key"] response = requests.get(self.radarrURL+"movie/lookup/imdb?imdbId=tt"+imdbId+"&apiKey="+apiKey) lookupData = response.json() postData = {"qualityProfileId": 1, @@ -126,7 +126,7 @@ class BedreNetflix(): messageTitle = "**Is it any of these shows?**" messageText = "" - imdbNames = [] + imdb_names = [] for x, show in enumerate(shows): try: @@ -136,17 +136,17 @@ class BedreNetflix(): messageText += "\n"+str(x+1)+") "+show["title"] except: messageText += "Error" - imdbNames.append(show["title"]) + imdb_names.append(show["title"]) - self.bot.log("Returning a list of "+str(len(shows))+" possible shows: "+str(imdbNames)) + self.bot.log("Returning a list of "+str(len(shows))+" possible shows: "+str(imdb_names)) em = discord.Embed(title=messageTitle,description=messageText,colour=0x00FF00) message = await ctx.send(embed=em) - messageData = {"messageID":message.id,"imdbNames":imdbNames} + messageData = {"message_id":message.id,"imdb_names":imdb_names} - with open("resources/bedreNetflix/oldMessage"+str(ctx.channel.id),"w") as f: + with open("gwendolyn/resources/bedre_netflix/old_message"+str(ctx.channel.id),"w") as f: json.dump(messageData,f) if len(shows) == 1: @@ -162,14 +162,14 @@ class BedreNetflix(): await message.clear_reactions() #Adds the requested show to Bedre Netflix - async def addShow(self, message, imdbName): - if imdbName == None: + async def add_show(self, message, imdb_name): + if imdb_name == None: self.bot.log("Did not find what the user was searching for") await message.edit(embed = None, content = "Try searching for the IMDB id") else: - self.bot.log("Trying to add show "+str(imdbName)) - apiKey = self.bot.credentials.sonarrKey - response = requests.get(self.sonarrURL+"series/lookup?term="+imdbName.replace(" ","%20")+"&apiKey="+apiKey) + self.bot.log("Trying to add show "+str(imdb_name)) + apiKey = self.bot.credentials["sonarr_key"] + response = requests.get(self.sonarrURL+"series/lookup?term="+imdb_name.replace(" ","%20")+"&apiKey="+apiKey) lookupData = response.json()[0] postData = {"ProfileId" : 1, "rootFolderPath" : self.showPath, @@ -264,8 +264,8 @@ class BedreNetflix(): movieSectionTitle = "*Missing movies not downloading*" movieSectionTitleLine = "-"*((titleWidth-len(movieSectionTitle))//2) message.append(movieSectionTitleLine+movieSectionTitle+movieSectionTitleLine) - movieList = requests.get(self.radarrURL+"movie?apiKey="+self.bot.credentials.radarrKey).json() - movieQueue = requests.get(self.radarrURL+"queue?apiKey="+self.bot.credentials.radarrKey).json() + movieList = requests.get(self.radarrURL+"movie?apiKey="+self.bot.credentials["radarr_key"]).json() + movieQueue = requests.get(self.radarrURL+"queue?apiKey="+self.bot.credentials["radarr_key"]).json() movieQueueIDs = [] for queueItem in movieQueue["records"]: @@ -297,7 +297,7 @@ class BedreNetflix(): showSectionTitleLine = "-"*((titleWidth-len(showSectionTitle))//2) message.append(showSectionTitleLine+showSectionTitle+showSectionTitleLine) - showList = requests.get(self.sonarrURL+"series?apiKey="+self.bot.credentials.sonarrKey).json() + showList = requests.get(self.sonarrURL+"series?apiKey="+self.bot.credentials["sonarr_key"]).json() for show in showList: if show["seasons"][0]["seasonNumber"] == 0: @@ -381,14 +381,14 @@ class BedreNetflix(): if not allDownloaded: updatesLeft = 60 messageText = messageText[:-3]+"\nThis message will update every 10 seconds for "+str(math.ceil(updatesLeft/6))+" more minutes\n```" - oldMessage = await ctx.send(messageText) + old_message = await ctx.send(messageText) while ((not allDownloaded) and updatesLeft > 0): await asyncio.sleep(10) updatesLeft -= 1 messageText, allDownloaded = await self.genDownloadList(*params) messageText = messageText[:-3]+"\nThis message will update every 10 seconds for "+str(math.ceil(updatesLeft/6))+" more minutes\n```" - await oldMessage.edit(content = messageText) + await old_message.edit(content = messageText) messageText, allDownloaded = await self.genDownloadList(*params) @@ -399,7 +399,7 @@ class BedreNetflix(): messageText = messageText[:-3]+"\nThis message will not update anymore\n```" self.bot.log("The message updated 20 times") - await oldMessage.edit(content = messageText) + await old_message.edit(content = messageText) else: await ctx.send(messageText) diff --git a/funcs/other/generators.py b/gwendolyn/funcs/other/generators.py similarity index 98% rename from funcs/other/generators.py rename to gwendolyn/funcs/other/generators.py index a1621b3..8b8b5d9 100644 --- a/funcs/other/generators.py +++ b/gwendolyn/funcs/other/generators.py @@ -17,7 +17,7 @@ class Generators(): # Generates a random name async def nameGen(self, ctx): # Makes a list of all names from "names.txt" - names = open('resources/names.txt', encoding='utf8').read() + names = open('gwendolyn/resources/names.txt', encoding='utf8').read() corpus = list(names) # Makes a list of pairs diff --git a/funcs/other/nerdShit.py b/gwendolyn/funcs/other/nerd_shit.py similarity index 76% rename from funcs/other/nerdShit.py rename to gwendolyn/funcs/other/nerd_shit.py index 53f5306..1e8274a 100644 --- a/funcs/other/nerdShit.py +++ b/gwendolyn/funcs/other/nerd_shit.py @@ -8,9 +8,9 @@ class NerdShit(): async def wolfSearch(self,ctx,content): await self.bot.defer(ctx) - fnt = ImageFont.truetype('resources/fonts/times-new-roman.ttf', 20) + fnt = ImageFont.truetype('gwendolyn/resources/fonts/times-new-roman.ttf', 20) self.bot.log("Requesting data") - bot = wolframalpha.Client(self.bot.credentials.wolfKey) + bot = wolframalpha.Client(self.bot.credentials["wolfram_alpha_key"]) res = bot.query(content) self.bot.log("Processing data") @@ -49,33 +49,33 @@ class NerdShit(): for count, pod in enumerate(chunk): response = requests.get(pod.img["@src"]) - file = open("resources/wolfTemp.png", "wb") + file = open("gwendolyn/resources/wolfTemp.png", "wb") file.write(response.content) file.close() - oldImage = Image.open("resources/wolfTemp.png") - oldSize = oldImage.size + old_image = Image.open("gwendolyn/resources/wolfTemp.png") + oldSize = old_image.size if titleChucks[x][count] == "": placeForText = 0 else: placeForText = 30 newSize = (width,int(oldSize[1]+10+placeForText)) newImage = Image.new("RGB",newSize,color=(255,255,255)) - newImage.paste(oldImage, (int((int(oldSize[0]+10)-oldSize[0])/2),int(((newSize[1]-placeForText)-oldSize[1])/2)+placeForText)) + newImage.paste(old_image, (int((int(oldSize[0]+10)-oldSize[0])/2),int(((newSize[1]-placeForText)-oldSize[1])/2)+placeForText)) if titleChucks[x][count] != "": d = ImageDraw.Draw(newImage,"RGB") d.text((5,7),titleChucks[x][count],font=fnt,fill=(150,150,150)) wolfImage.paste(newImage,(0,heights[count])) newImage.close() - oldImage.close() + old_image.close() count += 1 - wolfImage.save("resources/wolf.png") + wolfImage.save("gwendolyn/resources/wolf.png") wolfImage.close() - await ctx.channel.send(file = discord.File("resources/wolf.png")) + await ctx.channel.send(file = discord.File("gwendolyn/resources/wolf.png")) - os.remove("resources/wolf.png") - os.remove("resources/wolfTemp.png") + os.remove("gwendolyn/resources/wolf.png") + os.remove("gwendolyn/resources/wolfTemp.png") else: self.bot.log("No returned data") await ctx.send("Could not find anything relating to your search") \ No newline at end of file diff --git a/funcs/other/other.py b/gwendolyn/funcs/other/other.py similarity index 94% rename from funcs/other/other.py rename to gwendolyn/funcs/other/other.py index 2955641..f901e1d 100644 --- a/funcs/other/other.py +++ b/gwendolyn/funcs/other/other.py @@ -7,11 +7,11 @@ import lxml # Used in imageFunc import fandom # Used in findWikiPage import d20 # Used in rollDice import ast -from .bedreNetflix import BedreNetflix -from .nerdShit import NerdShit +from .bedre_netflix import BedreNetflix +from .nerd_shit import NerdShit from .generators import Generators -from utils import cap +from gwendolyn.utils import cap fandom.set_lang("da") fandom.set_wiki("senkulpa") @@ -28,8 +28,8 @@ class MyStringifier(d20.MarkdownStringifier): class Other(): def __init__(self, bot): self.bot = bot - self.bedreNetflix = BedreNetflix(self.bot) - self.nerdShit = NerdShit(self.bot) + self.bedre_netflix = BedreNetflix(self.bot) + self.nerd_shit = NerdShit(self.bot) self.generators = Generators(self.bot) # Picks a random movie and returns information about it @@ -40,7 +40,7 @@ class Other(): imdbClient = imdb.IMDb() self.bot.log("Picking a movie") - with open("resources/movies.txt", "r") as f: + with open("gwendolyn/resources/movies.txt", "r") as f: movieList = f.read().split("\n") movieName = random.choice(movieList) @@ -183,13 +183,13 @@ class Other(): async def helpFunc(self, ctx, command): if command == "": - with open("resources/help/help.txt",encoding="utf-8") as f: + with open("gwendolyn/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: + with open(f"gwendolyn/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/star_wars_funcs/__init__.py b/gwendolyn/funcs/star_wars_funcs/__init__.py similarity index 100% rename from funcs/star_wars_funcs/__init__.py rename to gwendolyn/funcs/star_wars_funcs/__init__.py diff --git a/funcs/star_wars_funcs/star_wars.py b/gwendolyn/funcs/star_wars_funcs/star_wars.py similarity index 100% rename from funcs/star_wars_funcs/star_wars.py rename to gwendolyn/funcs/star_wars_funcs/star_wars.py diff --git a/funcs/star_wars_funcs/star_wars_char.py b/gwendolyn/funcs/star_wars_funcs/star_wars_char.py similarity index 97% rename from funcs/star_wars_funcs/star_wars_char.py rename to gwendolyn/funcs/star_wars_funcs/star_wars_char.py index b90e8d3..fd749f8 100644 --- a/funcs/star_wars_funcs/star_wars_char.py +++ b/gwendolyn/funcs/star_wars_funcs/star_wars_char.py @@ -7,15 +7,15 @@ class StarWarsChar(): self.bot = bot def getCharName(self, user : str): - self.bot.log("Getting name for "+self.bot.database_funcs.getName(user)+"'s character") + self.bot.log("Getting name for "+self.bot.database_funcs.get_name(user)+"'s character") userCharacter = self.bot.database["starwars characters"].find_one({"_id":user}) if userCharacter != None: self.bot.log("Name is "+userCharacter["Name"]) return userCharacter["Name"] else: - self.bot.log("Just using "+self.bot.database_funcs.getName(user)) - return self.bot.database_funcs.getName(user) + self.bot.log("Just using "+self.bot.database_funcs.get_name(user)) + return self.bot.database_funcs.get_name(user) def setUpDict(self, cmd : dict): self.bot.log("Setting up a dictionary in a nice way") @@ -252,7 +252,7 @@ class StarWarsChar(): if cmd == "": break - self.bot.log("Looking for "+self.bot.database_funcs.getName(user)+"'s character") + self.bot.log("Looking for "+self.bot.database_funcs.get_name(user)+"'s character") if userCharacter != None: self.bot.log("Found it! Looking for "+key+" in the data") if key in userCharacter: @@ -303,7 +303,7 @@ class StarWarsChar(): return cmd[0]+" added to "+key+" for " + userCharacter["Name"] elif key == "Weapons": - with open("resources/star_wars/starwarstemplates.json", "r") as f: + with open("gwendolyn/resources/star_wars/starwarstemplates.json", "r") as f: templates = json.load(f) newWeapon = templates["Weapon"] self.bot.log("Adding "+cmd+" to "+key) @@ -495,18 +495,18 @@ class StarWarsChar(): text = self.replaceWithSpaces(text) returnEmbed = True else: - self.bot.log("Makin' a character for "+self.bot.database_funcs.getName(user)) - with open("resources/star_wars/starwarstemplates.json", "r") as f: + self.bot.log("Makin' a character for "+self.bot.database_funcs.get_name(user)) + with open("gwendolyn/resources/star_wars/starwarstemplates.json", "r") as f: templates = json.load(f) newChar = templates["Character"] newChar["_id"] = user self.bot.database["starwars characters"].insert_one(newChar) - await ctx.send("Character for " + self.bot.database_funcs.getName(user) + " created") + await ctx.send("Character for " + self.bot.database_funcs.get_name(user) + " created") else: if cmd == "Purge": - self.bot.log("Deleting "+self.bot.database_funcs.getName(user)+"'s character") + self.bot.log("Deleting "+self.bot.database_funcs.get_name(user)+"'s character") self.bot.database["starwars characters"].delete_one({"_id":user}) - await ctx.send("Character for " + self.bot.database_funcs.getName(user) + " deleted") + await ctx.send("Character for " + self.bot.database_funcs.get_name(user) + " deleted") else: await ctx.send(self.replaceWithSpaces(str(self.charData(user,cmd)))) diff --git a/funcs/star_wars_funcs/star_wars_destiny.py b/gwendolyn/funcs/star_wars_funcs/star_wars_destiny.py similarity index 86% rename from funcs/star_wars_funcs/star_wars_destiny.py rename to gwendolyn/funcs/star_wars_funcs/star_wars_destiny.py index b1fac7f..acb2cc6 100644 --- a/funcs/star_wars_funcs/star_wars_destiny.py +++ b/gwendolyn/funcs/star_wars_funcs/star_wars_destiny.py @@ -7,13 +7,13 @@ class StarWarsDestiny(): roll, diceResults = self.bot.star_wars.roll.roll(0,0,0,0,0,0,num) roll = "".join(sorted(roll)) - with open("resources/star_wars/destinyPoints.txt","wt") as f: + with open("gwendolyn/resources/star_wars/destinyPoints.txt","wt") as f: f.write(roll) return "Rolled for Destiny Points and got:\n"+self.bot.star_wars.roll.diceResultToEmoji(diceResults)+"\n"+self.bot.star_wars.roll.resultToEmoji(roll) def destinyUse(self, user : str): - with open("resources/star_wars/destinyPoints.txt","rt") as f: + with open("gwendolyn/resources/star_wars/destinyPoints.txt","rt") as f: points = f.read() if user == "Nikolaj": @@ -21,7 +21,7 @@ class StarWarsDestiny(): if 'B' in points: points = points.replace("B","L",1) points = "".join(sorted(points)) - with open("resources/star_wars/destinyPoints.txt","wt") as f: + with open("gwendolyn/resources/star_wars/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.star_wars.roll.resultToEmoji(points) @@ -33,7 +33,7 @@ class StarWarsDestiny(): if 'L' in points: points = points.replace("L","B",1) points = "".join(sorted(points)) - with open("resources/star_wars/destinyPoints.txt","wt") as f: + with open("gwendolyn/resources/star_wars/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.star_wars.roll.resultToEmoji(points) @@ -51,7 +51,7 @@ class StarWarsDestiny(): if cmd == "": self.bot.log("Retrieving destiny pool info") - with open("resources/star_wars/destinyPoints.txt","rt") as f: + with open("gwendolyn/resources/star_wars/destinyPoints.txt","rt") as f: sendMessage = self.bot.star_wars.roll.resultToEmoji(f.read()) else: commands = cmd.upper().split(" ") diff --git a/funcs/star_wars_funcs/star_wars_roll.py b/gwendolyn/funcs/star_wars_funcs/star_wars_roll.py similarity index 99% rename from funcs/star_wars_funcs/star_wars_roll.py rename to gwendolyn/funcs/star_wars_funcs/star_wars_roll.py index 07ad6e7..bd17710 100644 --- a/funcs/star_wars_funcs/star_wars_roll.py +++ b/gwendolyn/funcs/star_wars_funcs/star_wars_roll.py @@ -3,7 +3,7 @@ import re import string import json -with open("resources/star_wars/starwarsskills.json", "r") as f: +with open("gwendolyn/resources/star_wars/starwarsskills.json", "r") as f: skillData = json.load(f) class StarWarsRoll(): diff --git a/gwendolyn.py b/gwendolyn/gwendolyn_client.py similarity index 72% rename from gwendolyn.py rename to gwendolyn/gwendolyn_client.py index fe71638..ff4c683 100644 --- a/gwendolyn.py +++ b/gwendolyn/gwendolyn_client.py @@ -1,25 +1,22 @@ """ -Contains the Gwendolyn class, and runs it when run as script. +Contains the Gwendolyn class, a subclass of the discord command bot. *Classes* --------- Gwendolyn(discord.ext.commands.Bot) """ -import platform # Used to test if the bot is running on windows, in - # order to fix a bug with asyncio -import asyncio # used to set change the loop policy if the bot is - # running on windows - import os # Used for loading cogs in Gwendolyn.addCogs import finnhub # Used to add a finhub client to the bot import discord # Used for discord.Intents and discord.Status import discord_slash # Used to initialized SlashCommands object +import git # Used to pull when stopping from discord.ext import commands # Used to inherit from commands.bot from pymongo import MongoClient # Used for database management -from funcs import Money, StarWars, Games, Other, LookupFuncs -from utils import (Options, Credentials, logThis, makeFiles, DatabaseFuncs, - EventHandler, ErrorHandler, long_strings) +from gwendolyn.funcs import Money, StarWars, Games, Other, LookupFuncs +from gwendolyn.utils import (get_options, get_credentials, log_this, + DatabaseFuncs, EventHandler, ErrorHandler, + long_strings) class Gwendolyn(commands.Bot): @@ -33,6 +30,7 @@ class Gwendolyn(commands.Bot): stop(ctx: discord_slash.context.SlashContext) defer(ctx: discord_slash.context.SlashContext) """ + # pylint: disable=too-many-instance-attributes def __init__(self): """Initialize the bot.""" @@ -54,17 +52,17 @@ class Gwendolyn(commands.Bot): def _add_clients_and_options(self): """Add all the client, option and credentials objects.""" self.long_strings = long_strings() - self.options = Options() - self.credentials = Credentials() - finnhub_key = self.credentials.finnhub_key + self.options = get_options() + self.credentials = get_credentials() + finnhub_key = self.credentials["finnhub_key"] self.finnhub_client = finnhub.Client(api_key=finnhub_key) - mongo_user = self.credentials.mongoDBUser - mongo_password = self.credentials.mongoDBPassword + mongo_user = self.credentials["mongo_db_user"] + mongo_password = self.credentials["mongo_db_password"] mongo_url = f"mongodb+srv://{mongo_user}:{mongo_password}@gwendolyn" mongo_url += ".qkwfy.mongodb.net/Gwendolyn?retryWrites=true&w=majority" database_clint = MongoClient(mongo_url) - if self.options.testing: + if self.options["testing"]: self.log("Testing mode") self.database = database_clint["Gwendolyn-Test"] else: @@ -92,13 +90,13 @@ class Gwendolyn(commands.Bot): def _add_cogs(self): """Load cogs.""" - for filename in os.listdir("./cogs"): + for filename in os.listdir("./gwendolyn/cogs"): if filename.endswith(".py"): - self.load_extension(f"cogs.{filename[:-3]}") + self.load_extension(f"gwendolyn.cogs.{filename[:-3]}") def log(self, messages, channel: str = "", level: int = 20): """Log a message. Described in utils/util_functions.py.""" - logThis(messages, channel, level) + log_this(messages, channel, level) async def stop(self, ctx: discord_slash.context.SlashContext): """ @@ -112,12 +110,15 @@ class Gwendolyn(commands.Bot): ctx: discord_slash.context.SlashContext The context of the "/stop" slash command. """ - if f"#{ctx.author.id}" in self.options.admins: + 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.database_funcs.wipeGames() + self.database_funcs.wipe_games() + if not self.options["testing"]: + git_client = git.cmd.Git("") + git_client.pull() self.log("Logging out", level=25) await self.close() @@ -132,20 +133,3 @@ class Gwendolyn(commands.Bot): await ctx.defer() except discord_slash.error.AlreadyResponded: self.log("defer failed") - - -if __name__ == "__main__": - if platform.system() == "Windows": - asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) - - # Creates the required files - makeFiles() - - # Creates the Bot - bot = Gwendolyn() - - try: - # Runs the whole shabang - bot.run(bot.credentials.token) - except Exception as exception: # pylint: disable=broad-except - bot.log(bot.long_strings[f"Can't log in: {repr(exception)}"]) diff --git a/resources/fonts/comic-sans-bold.ttf b/gwendolyn/resources/fonts/comic-sans-bold.ttf similarity index 100% rename from resources/fonts/comic-sans-bold.ttf rename to gwendolyn/resources/fonts/comic-sans-bold.ttf diff --git a/resources/fonts/futura-bold.ttf b/gwendolyn/resources/fonts/futura-bold.ttf similarity index 100% rename from resources/fonts/futura-bold.ttf rename to gwendolyn/resources/fonts/futura-bold.ttf diff --git a/resources/fonts/times-new-roman.ttf b/gwendolyn/resources/fonts/times-new-roman.ttf similarity index 100% rename from resources/fonts/times-new-roman.ttf rename to gwendolyn/resources/fonts/times-new-roman.ttf diff --git a/resources/games/blackjackTable.png b/gwendolyn/resources/games/blackjack_table.png similarity index 100% rename from resources/games/blackjackTable.png rename to gwendolyn/resources/games/blackjack_table.png diff --git a/resources/games/cards/0C.png b/gwendolyn/resources/games/cards/0C.png similarity index 100% rename from resources/games/cards/0C.png rename to gwendolyn/resources/games/cards/0C.png diff --git a/resources/games/cards/0D.png b/gwendolyn/resources/games/cards/0D.png similarity index 100% rename from resources/games/cards/0D.png rename to gwendolyn/resources/games/cards/0D.png diff --git a/resources/games/cards/0H.png b/gwendolyn/resources/games/cards/0H.png similarity index 100% rename from resources/games/cards/0H.png rename to gwendolyn/resources/games/cards/0H.png diff --git a/resources/games/cards/0S.png b/gwendolyn/resources/games/cards/0S.png similarity index 100% rename from resources/games/cards/0S.png rename to gwendolyn/resources/games/cards/0S.png diff --git a/resources/games/cards/2C.png b/gwendolyn/resources/games/cards/2C.png similarity index 100% rename from resources/games/cards/2C.png rename to gwendolyn/resources/games/cards/2C.png diff --git a/resources/games/cards/2D.png b/gwendolyn/resources/games/cards/2D.png similarity index 100% rename from resources/games/cards/2D.png rename to gwendolyn/resources/games/cards/2D.png diff --git a/resources/games/cards/2H.png b/gwendolyn/resources/games/cards/2H.png similarity index 100% rename from resources/games/cards/2H.png rename to gwendolyn/resources/games/cards/2H.png diff --git a/resources/games/cards/2S.png b/gwendolyn/resources/games/cards/2S.png similarity index 100% rename from resources/games/cards/2S.png rename to gwendolyn/resources/games/cards/2S.png diff --git a/resources/games/cards/3C.png b/gwendolyn/resources/games/cards/3C.png similarity index 100% rename from resources/games/cards/3C.png rename to gwendolyn/resources/games/cards/3C.png diff --git a/resources/games/cards/3D.png b/gwendolyn/resources/games/cards/3D.png similarity index 100% rename from resources/games/cards/3D.png rename to gwendolyn/resources/games/cards/3D.png diff --git a/resources/games/cards/3H.png b/gwendolyn/resources/games/cards/3H.png similarity index 100% rename from resources/games/cards/3H.png rename to gwendolyn/resources/games/cards/3H.png diff --git a/resources/games/cards/3S.png b/gwendolyn/resources/games/cards/3S.png similarity index 100% rename from resources/games/cards/3S.png rename to gwendolyn/resources/games/cards/3S.png diff --git a/resources/games/cards/4C.png b/gwendolyn/resources/games/cards/4C.png similarity index 100% rename from resources/games/cards/4C.png rename to gwendolyn/resources/games/cards/4C.png diff --git a/resources/games/cards/4D.png b/gwendolyn/resources/games/cards/4D.png similarity index 100% rename from resources/games/cards/4D.png rename to gwendolyn/resources/games/cards/4D.png diff --git a/resources/games/cards/4H.png b/gwendolyn/resources/games/cards/4H.png similarity index 100% rename from resources/games/cards/4H.png rename to gwendolyn/resources/games/cards/4H.png diff --git a/resources/games/cards/4S.png b/gwendolyn/resources/games/cards/4S.png similarity index 100% rename from resources/games/cards/4S.png rename to gwendolyn/resources/games/cards/4S.png diff --git a/resources/games/cards/5C.png b/gwendolyn/resources/games/cards/5C.png similarity index 100% rename from resources/games/cards/5C.png rename to gwendolyn/resources/games/cards/5C.png diff --git a/resources/games/cards/5D.png b/gwendolyn/resources/games/cards/5D.png similarity index 100% rename from resources/games/cards/5D.png rename to gwendolyn/resources/games/cards/5D.png diff --git a/resources/games/cards/5H.png b/gwendolyn/resources/games/cards/5H.png similarity index 100% rename from resources/games/cards/5H.png rename to gwendolyn/resources/games/cards/5H.png diff --git a/resources/games/cards/5S.png b/gwendolyn/resources/games/cards/5S.png similarity index 100% rename from resources/games/cards/5S.png rename to gwendolyn/resources/games/cards/5S.png diff --git a/resources/games/cards/6C.png b/gwendolyn/resources/games/cards/6C.png similarity index 100% rename from resources/games/cards/6C.png rename to gwendolyn/resources/games/cards/6C.png diff --git a/resources/games/cards/6D.png b/gwendolyn/resources/games/cards/6D.png similarity index 100% rename from resources/games/cards/6D.png rename to gwendolyn/resources/games/cards/6D.png diff --git a/resources/games/cards/6H.png b/gwendolyn/resources/games/cards/6H.png similarity index 100% rename from resources/games/cards/6H.png rename to gwendolyn/resources/games/cards/6H.png diff --git a/resources/games/cards/6S.png b/gwendolyn/resources/games/cards/6S.png similarity index 100% rename from resources/games/cards/6S.png rename to gwendolyn/resources/games/cards/6S.png diff --git a/resources/games/cards/7C.png b/gwendolyn/resources/games/cards/7C.png similarity index 100% rename from resources/games/cards/7C.png rename to gwendolyn/resources/games/cards/7C.png diff --git a/resources/games/cards/7D.png b/gwendolyn/resources/games/cards/7D.png similarity index 100% rename from resources/games/cards/7D.png rename to gwendolyn/resources/games/cards/7D.png diff --git a/resources/games/cards/7H.png b/gwendolyn/resources/games/cards/7H.png similarity index 100% rename from resources/games/cards/7H.png rename to gwendolyn/resources/games/cards/7H.png diff --git a/resources/games/cards/7S.png b/gwendolyn/resources/games/cards/7S.png similarity index 100% rename from resources/games/cards/7S.png rename to gwendolyn/resources/games/cards/7S.png diff --git a/resources/games/cards/8C.png b/gwendolyn/resources/games/cards/8C.png similarity index 100% rename from resources/games/cards/8C.png rename to gwendolyn/resources/games/cards/8C.png diff --git a/resources/games/cards/8D.png b/gwendolyn/resources/games/cards/8D.png similarity index 100% rename from resources/games/cards/8D.png rename to gwendolyn/resources/games/cards/8D.png diff --git a/resources/games/cards/8H.png b/gwendolyn/resources/games/cards/8H.png similarity index 100% rename from resources/games/cards/8H.png rename to gwendolyn/resources/games/cards/8H.png diff --git a/resources/games/cards/8S.png b/gwendolyn/resources/games/cards/8S.png similarity index 100% rename from resources/games/cards/8S.png rename to gwendolyn/resources/games/cards/8S.png diff --git a/resources/games/cards/9C.png b/gwendolyn/resources/games/cards/9C.png similarity index 100% rename from resources/games/cards/9C.png rename to gwendolyn/resources/games/cards/9C.png diff --git a/resources/games/cards/9D.png b/gwendolyn/resources/games/cards/9D.png similarity index 100% rename from resources/games/cards/9D.png rename to gwendolyn/resources/games/cards/9D.png diff --git a/resources/games/cards/9H.png b/gwendolyn/resources/games/cards/9H.png similarity index 100% rename from resources/games/cards/9H.png rename to gwendolyn/resources/games/cards/9H.png diff --git a/resources/games/cards/9S.png b/gwendolyn/resources/games/cards/9S.png similarity index 100% rename from resources/games/cards/9S.png rename to gwendolyn/resources/games/cards/9S.png diff --git a/resources/games/cards/AC.png b/gwendolyn/resources/games/cards/AC.png similarity index 100% rename from resources/games/cards/AC.png rename to gwendolyn/resources/games/cards/AC.png diff --git a/resources/games/cards/AD.png b/gwendolyn/resources/games/cards/AD.png similarity index 100% rename from resources/games/cards/AD.png rename to gwendolyn/resources/games/cards/AD.png diff --git a/resources/games/cards/AH.png b/gwendolyn/resources/games/cards/AH.png similarity index 100% rename from resources/games/cards/AH.png rename to gwendolyn/resources/games/cards/AH.png diff --git a/resources/games/cards/AS.png b/gwendolyn/resources/games/cards/AS.png similarity index 100% rename from resources/games/cards/AS.png rename to gwendolyn/resources/games/cards/AS.png diff --git a/resources/games/cards/JC.png b/gwendolyn/resources/games/cards/JC.png similarity index 100% rename from resources/games/cards/JC.png rename to gwendolyn/resources/games/cards/JC.png diff --git a/resources/games/cards/JD.png b/gwendolyn/resources/games/cards/JD.png similarity index 100% rename from resources/games/cards/JD.png rename to gwendolyn/resources/games/cards/JD.png diff --git a/resources/games/cards/JH.png b/gwendolyn/resources/games/cards/JH.png similarity index 100% rename from resources/games/cards/JH.png rename to gwendolyn/resources/games/cards/JH.png diff --git a/resources/games/cards/JS.png b/gwendolyn/resources/games/cards/JS.png similarity index 100% rename from resources/games/cards/JS.png rename to gwendolyn/resources/games/cards/JS.png diff --git a/resources/games/cards/KC.png b/gwendolyn/resources/games/cards/KC.png similarity index 100% rename from resources/games/cards/KC.png rename to gwendolyn/resources/games/cards/KC.png diff --git a/resources/games/cards/KD.png b/gwendolyn/resources/games/cards/KD.png similarity index 100% rename from resources/games/cards/KD.png rename to gwendolyn/resources/games/cards/KD.png diff --git a/resources/games/cards/KH.png b/gwendolyn/resources/games/cards/KH.png similarity index 100% rename from resources/games/cards/KH.png rename to gwendolyn/resources/games/cards/KH.png diff --git a/resources/games/cards/KS.png b/gwendolyn/resources/games/cards/KS.png similarity index 100% rename from resources/games/cards/KS.png rename to gwendolyn/resources/games/cards/KS.png diff --git a/resources/games/cards/QC.png b/gwendolyn/resources/games/cards/QC.png similarity index 100% rename from resources/games/cards/QC.png rename to gwendolyn/resources/games/cards/QC.png diff --git a/resources/games/cards/QD.png b/gwendolyn/resources/games/cards/QD.png similarity index 100% rename from resources/games/cards/QD.png rename to gwendolyn/resources/games/cards/QD.png diff --git a/resources/games/cards/QH.png b/gwendolyn/resources/games/cards/QH.png similarity index 100% rename from resources/games/cards/QH.png rename to gwendolyn/resources/games/cards/QH.png diff --git a/resources/games/cards/QS.png b/gwendolyn/resources/games/cards/QS.png similarity index 100% rename from resources/games/cards/QS.png rename to gwendolyn/resources/games/cards/QS.png diff --git a/resources/games/cards/blue_back.png b/gwendolyn/resources/games/cards/blue_back.png similarity index 100% rename from resources/games/cards/blue_back.png rename to gwendolyn/resources/games/cards/blue_back.png diff --git a/resources/games/cards/gray_back.png b/gwendolyn/resources/games/cards/gray_back.png similarity index 100% rename from resources/games/cards/gray_back.png rename to gwendolyn/resources/games/cards/gray_back.png diff --git a/resources/games/cards/green_back.png b/gwendolyn/resources/games/cards/green_back.png similarity index 100% rename from resources/games/cards/green_back.png rename to gwendolyn/resources/games/cards/green_back.png diff --git a/resources/games/cards/purple_back.png b/gwendolyn/resources/games/cards/purple_back.png similarity index 100% rename from resources/games/cards/purple_back.png rename to gwendolyn/resources/games/cards/purple_back.png diff --git a/resources/games/cards/red_back.png b/gwendolyn/resources/games/cards/red_back.png similarity index 100% rename from resources/games/cards/red_back.png rename to gwendolyn/resources/games/cards/red_back.png diff --git a/resources/games/cards/yellow_back.png b/gwendolyn/resources/games/cards/yellow_back.png similarity index 100% rename from resources/games/cards/yellow_back.png rename to gwendolyn/resources/games/cards/yellow_back.png diff --git a/resources/games/deckofCards.txt b/gwendolyn/resources/games/deck_of_cards.txt similarity index 100% rename from resources/games/deckofCards.txt rename to gwendolyn/resources/games/deck_of_cards.txt diff --git a/gwendolyn/resources/help/help-add_movie.txt b/gwendolyn/resources/help/help-add_movie.txt new file mode 100644 index 0000000..fae39ff --- /dev/null +++ b/gwendolyn/resources/help/help-add_movie.txt @@ -0,0 +1 @@ +Du kan søge efter en film ved at skrive `/add_movie [søgning]`. Gwendolyn vil derefter vise dig resultater baseret på din søgning. Du kan derfra reagere på Gwendolyns besked for at downloade en specifik film. \ No newline at end of file diff --git a/resources/help/help-addmovie.txt b/gwendolyn/resources/help/help-add_show.txt similarity index 60% rename from resources/help/help-addmovie.txt rename to gwendolyn/resources/help/help-add_show.txt index daa1ac6..4868941 100644 --- a/resources/help/help-addmovie.txt +++ b/gwendolyn/resources/help/help-add_show.txt @@ -1 +1 @@ -Du kan søge efter en film ved at skrive `/addmovie [søgning]`. Gwendolyn vil derefter vise dig resultater baseret på din søgning. Du kan derfra reagere på Gwendolyns besked for at downloade en specifik film. \ No newline at end of file +Du kan søge efter et show ved at skrive `/add_show [søgning]`. Gwendolyn vil derefter vise dig resultater baseret på din søgning. Du kan derfra reagere på Gwendolyns besked for at downloade et specifikt show. \ No newline at end of file diff --git a/resources/help/help-balance.txt b/gwendolyn/resources/help/help-balance.txt similarity index 100% rename from resources/help/help-balance.txt rename to gwendolyn/resources/help/help-balance.txt diff --git a/resources/help/help-blackjack.txt b/gwendolyn/resources/help/help-blackjack.txt similarity index 100% rename from resources/help/help-blackjack.txt rename to gwendolyn/resources/help/help-blackjack.txt diff --git a/resources/help/help-connectfour.txt b/gwendolyn/resources/help/help-connect_four.txt similarity index 100% rename from resources/help/help-connectfour.txt rename to gwendolyn/resources/help/help-connect_four.txt diff --git a/resources/help/help-downloading.txt b/gwendolyn/resources/help/help-downloading.txt similarity index 100% rename from resources/help/help-downloading.txt rename to gwendolyn/resources/help/help-downloading.txt diff --git a/resources/help/help-give.txt b/gwendolyn/resources/help/help-give.txt similarity index 100% rename from resources/help/help-give.txt rename to gwendolyn/resources/help/help-give.txt diff --git a/resources/help/help-hangman.txt b/gwendolyn/resources/help/help-hangman.txt similarity index 100% rename from resources/help/help-hangman.txt rename to gwendolyn/resources/help/help-hangman.txt diff --git a/resources/help/help-hello.txt b/gwendolyn/resources/help/help-hello.txt similarity index 100% rename from resources/help/help-hello.txt rename to gwendolyn/resources/help/help-hello.txt diff --git a/resources/help/help-hex.txt b/gwendolyn/resources/help/help-hex.txt similarity index 100% rename from resources/help/help-hex.txt rename to gwendolyn/resources/help/help-hex.txt diff --git a/resources/help/help-image.txt b/gwendolyn/resources/help/help-image.txt similarity index 100% rename from resources/help/help-image.txt rename to gwendolyn/resources/help/help-image.txt diff --git a/resources/help/help-invest.txt b/gwendolyn/resources/help/help-invest.txt similarity index 100% rename from resources/help/help-invest.txt rename to gwendolyn/resources/help/help-invest.txt diff --git a/resources/help/help-monster.txt b/gwendolyn/resources/help/help-monster.txt similarity index 100% rename from resources/help/help-monster.txt rename to gwendolyn/resources/help/help-monster.txt diff --git a/resources/help/help-movie.txt b/gwendolyn/resources/help/help-movie.txt similarity index 100% rename from resources/help/help-movie.txt rename to gwendolyn/resources/help/help-movie.txt diff --git a/resources/help/help-name.txt b/gwendolyn/resources/help/help-name.txt similarity index 100% rename from resources/help/help-name.txt rename to gwendolyn/resources/help/help-name.txt diff --git a/resources/help/help-roll.txt b/gwendolyn/resources/help/help-roll.txt similarity index 100% rename from resources/help/help-roll.txt rename to gwendolyn/resources/help/help-roll.txt diff --git a/resources/help/help-spell.txt b/gwendolyn/resources/help/help-spell.txt similarity index 100% rename from resources/help/help-spell.txt rename to gwendolyn/resources/help/help-spell.txt diff --git a/resources/help/help-star_wars_character.txt b/gwendolyn/resources/help/help-star_wars_character.txt similarity index 100% rename from resources/help/help-star_wars_character.txt rename to gwendolyn/resources/help/help-star_wars_character.txt diff --git a/resources/help/help-star_wars_roll.txt b/gwendolyn/resources/help/help-star_wars_roll.txt similarity index 100% rename from resources/help/help-star_wars_roll.txt rename to gwendolyn/resources/help/help-star_wars_roll.txt diff --git a/resources/help/help-tavern.txt b/gwendolyn/resources/help/help-tavern.txt similarity index 100% rename from resources/help/help-tavern.txt rename to gwendolyn/resources/help/help-tavern.txt diff --git a/resources/help/help-thank.txt b/gwendolyn/resources/help/help-thank.txt similarity index 100% rename from resources/help/help-thank.txt rename to gwendolyn/resources/help/help-thank.txt diff --git a/resources/help/help-trivia.txt b/gwendolyn/resources/help/help-trivia.txt similarity index 100% rename from resources/help/help-trivia.txt rename to gwendolyn/resources/help/help-trivia.txt diff --git a/resources/help/help-wolf.txt b/gwendolyn/resources/help/help-wolf.txt similarity index 100% rename from resources/help/help-wolf.txt rename to gwendolyn/resources/help/help-wolf.txt diff --git a/resources/help/help.txt b/gwendolyn/resources/help/help.txt similarity index 90% rename from resources/help/help.txt rename to gwendolyn/resources/help/help.txt index e653eba..8db0a65 100644 --- a/resources/help/help.txt +++ b/gwendolyn/resources/help/help.txt @@ -17,8 +17,8 @@ `/hex` - Lader dig spille et spil Hex. `/hangman` - Lader dig spille et spil hangman. `/wolf` - Lader dig slå ting op på Wolfram Alpha. -`/addmovie` - Lader dig tilføje film til Bedre Netflix. -`/addshow` - Lader dig tilføje tv shows til Bedre Netflix. +`/add_movie` - Lader dig tilføje film til Bedre Netflix. +`/add_show` - Lader dig tilføje tv shows til Bedre Netflix. `/downloading` - Viser dig hvor langt de torrents der er ved at downloade er kommet. `/thank` - Lader dig takke Gwendolyn. Du kan få ekstra information om kommandoerne med "/help [kommando]". diff --git a/resources/long_strings.json b/gwendolyn/resources/long_strings.json similarity index 100% rename from resources/long_strings.json rename to gwendolyn/resources/long_strings.json diff --git a/resources/paper.jpg b/gwendolyn/resources/paper.jpg similarity index 100% rename from resources/paper.jpg rename to gwendolyn/resources/paper.jpg diff --git a/resources/slashParameters.json b/gwendolyn/resources/slash_parameters.json similarity index 95% rename from resources/slashParameters.json rename to gwendolyn/resources/slash_parameters.json index e4bd1ca..119dd88 100644 --- a/resources/slashParameters.json +++ b/gwendolyn/resources/slash_parameters.json @@ -1,6 +1,6 @@ { - "addMovie" : { - "name" : "addMovie", + "add_movie" : { + "name" : "add_movie", "description" : "Request a movie for Bedre Netflix", "options" : [ { @@ -11,8 +11,8 @@ } ] }, - "addShow" : { - "name" : "addShow", + "add_show" : { + "name" : "add_show", "description" : "Request a show for Bedre Netflix", "options" : [ { @@ -27,7 +27,7 @@ "name" : "balance", "description" : "See your balance of GwendoBucks" }, - "blackjackBet" : { + "blackjack_bet" : { "base" : "blackjack", "name" : "bet", "description" : "Enter the current blackjack game with a bet", @@ -40,12 +40,12 @@ } ] }, - "blackjackCards" : { + "blackjack_cards" : { "base" : "blackjack", "name" : "cards", "description" : "Get a count of the cards used in blackjack games" }, - "blackjackDouble" : { + "blackjack_double" : { "base" : "blackjack", "name" : "double", "description" : "Double your bet in blackjack", @@ -58,12 +58,12 @@ } ] }, - "blackjackHilo" : { + "blackjack_hilo" : { "base" : "blackjack", "name" : "hilo", "description" : "Get the current hi-lo value for the cards used in blackjack games" }, - "blackjackHit" : { + "blackjack_hit" : { "base" : "blackjack", "name" : "hit", "description" : "Hit on your hand in blackjack", @@ -76,12 +76,12 @@ } ] }, - "blackjackShuffle" : { + "blackjack_shuffle" : { "base" : "blackjack", "name" : "shuffle", "description" : "Shuffle the cards used in blackjack games" }, - "blackjackSplit" : { + "blackjack_split" : { "base" : "blackjack", "name" : "split", "description" : "Split your hand in blackjack", @@ -94,7 +94,7 @@ } ] }, - "blackjackStand" : { + "blackjack_stand" : { "base" : "blackjack", "name" : "stand", "description" : "Stand on your hand in blackjack", @@ -107,12 +107,12 @@ } ] }, - "blackjackStart" : { + "blackjack_start" : { "base" : "blackjack", "name" : "start", "description" : "Start a game of blackjack" }, - "connect_fourStartGwendolyn" : { + "connect_four_start_gwendolyn" : { "base" : "connect_four", "subcommand_group" : "start", "name" : "Gwendolyn", @@ -126,7 +126,7 @@ } ] }, - "connect_fourStartUser" : { + "connect_four_start_user" : { "base" : "connect_four", "subcommand_group" : "start", "name" : "user", @@ -140,7 +140,7 @@ } ] }, - "connect_fourSurrender" : { + "connect_four_surrender" : { "base" : "connect_four", "name" : "surrender", "description" : "Surrender the game of connect four" @@ -187,12 +187,12 @@ } ] }, - "hangmanStart" : { + "hangman_start" : { "base" : "hangman", "name" : "start", "description" : "Start a game of hangman" }, - "hangmanStop" : { + "hangman_stop" : { "base" : "hangman", "name" : "stop", "description" : "Stop the current game of hangman" @@ -213,7 +213,7 @@ } ] }, - "hexPlace" : { + "hex_place" : { "base" : "hex", "name" : "place", "description" : "Place a piece on the hex board", @@ -226,7 +226,7 @@ } ] }, - "hexStartGwendolyn" : { + "hex_start_gwendolyn" : { "base" : "hex", "subcommand_group" : "start", "name" : "Gwendolyn", @@ -240,7 +240,7 @@ } ] }, - "hexStartUser" : { + "hex_start_user" : { "base" : "hex", "subcommand_group" : "start", "name" : "user", @@ -254,17 +254,17 @@ } ] }, - "hexSurrender" : { + "hex_surrender" : { "base" : "hex", "name" : "surrender", "description" : "Surrender the game of hex" }, - "hexSwap" : { + "hex_swap" : { "base" : "hex", "name" : "swap", "description" : "Perform a hex swap" }, - "hexUndo" : { + "hex_undo" : { "base" : "hex", "name" : "undo", "description" : "Undo your last hex move" diff --git a/resources/star_wars/starwarsskills.json b/gwendolyn/resources/star_wars/starwarsskills.json similarity index 100% rename from resources/star_wars/starwarsskills.json rename to gwendolyn/resources/star_wars/starwarsskills.json diff --git a/resources/star_wars/starwarstemplates.json b/gwendolyn/resources/star_wars/starwarstemplates.json similarity index 100% rename from resources/star_wars/starwarstemplates.json rename to gwendolyn/resources/star_wars/starwarstemplates.json diff --git a/resources/startingFiles.json b/gwendolyn/resources/starting_files.json similarity index 82% rename from resources/startingFiles.json rename to gwendolyn/resources/starting_files.json index 1ad09a0..b22855e 100644 --- a/resources/startingFiles.json +++ b/gwendolyn/resources/starting_files.json @@ -1,6 +1,6 @@ { "json":{ - "resources/lookup/spells.json" : { + "gwendolyn/resources/lookup/spells.json" : { "Fireball" : { "casting_time" : "1 action", "components" : "V, S, M (a tiny ball of bat guano and sulfur)", @@ -12,7 +12,7 @@ "ritual" : false } }, - "resources/lookup/monsters.json" : [ + "gwendolyn/resources/lookup/monsters.json" : [ { "name": "Bandit", "size": "Medium", @@ -56,19 +56,19 @@ ] }, "txt": { - "resources/star_wars/destinyPoints.txt": "", - "resources/movies.txt": "The Room", - "resources/names.txt": "Gandalf\n", + "gwendolyn/resources/star_wars/destinyPoints.txt": "", + "gwendolyn/resources/movies.txt": "The Room", + "gwendolyn/resources/names.txt": "Gandalf\n", "credentials.txt" : "Bot token: TOKEN\nFinnhub API key: KEY\nWordnik API Key: KEY\nMongoDB user: USERNAME\nMongoDB password: PASSWORD\nWolframAlpha AppID: APPID\nRadarr API key: KEY\nSonarr API key: KEY", "options.txt" : "Testing: True\nTesting guild ids:\nAdmins:" }, "folder" : [ - "resources/lookup", - "resources/games/blackjackTables", - "resources/games/connect4Boards", - "resources/games/hexBoards", - "resources/games/hangmanBoards", - "resources/bedreNetflix", - "resources/games/old_images" + "gwendolyn/resources/lookup", + "gwendolyn/resources/games/blackjack_tables", + "gwendolyn/resources/games/connect4Boards", + "gwendolyn/resources/games/hex_boards", + "gwendolyn/resources/games/hangman_boards", + "gwendolyn/resources/bedre_netflix", + "gwendolyn/resources/games/old_images" ] } \ No newline at end of file diff --git a/gwendolyn/utils/__init__.py b/gwendolyn/utils/__init__.py new file mode 100644 index 0000000..863b018 --- /dev/null +++ b/gwendolyn/utils/__init__.py @@ -0,0 +1,11 @@ +"""A collections of utilities used by Gwendolyn and her functions.""" + +__all__ = ["get_options", "get_credentials", "DatabaseFuncs", "EventHandler", + "ErrorHandler", "get_params", "log_this", "cap", "make_files", + "replace_multiple", "emoji_to_command"] + +from .helper_classes import DatabaseFuncs +from .event_handlers import EventHandler, ErrorHandler +from .util_functions import (get_params, log_this, cap, make_files, + replace_multiple, emoji_to_command, long_strings, + sanitize, get_options, get_credentials) diff --git a/utils/event_handlers.py b/gwendolyn/utils/event_handlers.py similarity index 67% rename from utils/event_handlers.py rename to gwendolyn/utils/event_handlers.py index 6b87b39..80e73bb 100644 --- a/utils/event_handlers.py +++ b/gwendolyn/utils/event_handlers.py @@ -6,16 +6,17 @@ Classes used to handle bot events and errors. EventHandler ErrorHandler """ -import discord # Used to init discord.Game and discord.Status, as well -# as compare errors to discord errors and as typehints import traceback # Used to get the traceback of errors import sys # Used to get traceback when the specific error is not -# available + # available + +import discord # Used to init discord.Game and discord.Status, as well + # as compare errors to discord errors and as typehints from discord.ext import commands # Used to compare errors with command -# errors + # errors from discord_slash.context import SlashContext -from utils.util_functions import emojiToCommand +from gwendolyn.utils.util_functions import emoji_to_command class EventHandler(): @@ -35,17 +36,15 @@ class EventHandler(): async def on_ready(self): """Log and sets status when it logs in.""" - slashCommandList = await self.bot.slash.to_dict() - print(slashCommandList['guild'][740652054388932679][13]) - await self.bot.database_funcs.syncCommands() + await self.bot.database_funcs.imdb_commands() name = self.bot.user.name userid = str(self.bot.user.id) - loggedInMessage = f"Logged in as {name}, {userid}" - self.bot.log(loggedInMessage, level=25) + logged_in_message = f"Logged in as {name}, {userid}" + self.bot.log(logged_in_message, level=25) game = discord.Game("Use /help for commands") - onlineStatus = discord.Status.online - await self.bot.change_presence(activity=game, status=onlineStatus) + online_status = discord.Status.online + await self.bot.change_presence(activity=game, status=online_status) async def on_slash_command(self, ctx: SlashContext): """Log when a slash command is given.""" @@ -55,13 +54,13 @@ class EventHandler(): subcommand = " " if ctx.subcommand_group is not None: - subcommandGroup = f"{ctx.subcommand_group} " + sub_command_group = f"{ctx.subcommand_group} " else: - subcommandGroup = "" + sub_command_group = "" args = " ".join([str(i) for i in ctx.args]) - fullCommand = f"/{ctx.command}{subcommand}{subcommandGroup}{args}" - log_message = f"{ctx.author.display_name} ran {fullCommand}" + full_command = f"/{ctx.command}{subcommand}{sub_command_group}{args}" + log_message = f"{ctx.author.display_name} ran {full_command}" self.bot.log(log_message, str(ctx.channel_id), level=25) async def on_reaction_add(self, reaction: discord.Reaction, @@ -71,52 +70,52 @@ class EventHandler(): tests = self.bot.database_funcs message = reaction.message channel = message.channel - reactedMessage = f"{user.display_name} reacted to a message" - self.bot.log(reactedMessage, str(channel.id)) - plexData = tests.bedreNetflixReactionTest(message) - # plexData is a list containing 3 elements: whether it was - # the addshow/addmovie command message the reaction was to + reacted_message = f"{user.display_name} reacted to a message" + self.bot.log(reacted_message, str(channel.id)) + plex_data = tests.bedre_netflix_reaction_test(message) + # plex_data is a list containing 3 elements: whether it was + # the add_show/add_movie command message the reaction was to # (bool), whether it's a movie (bool) (if false, it's a # show), and the imdb ids/names for the for the movies or # shows listed in the message (list). - reactionTestParams = [message, f"#{str(user.id)}"] + reaction_test_parameters = [message, f"#{str(user.id)}"] - if tests.connect_fourReactionTest(*reactionTestParams): - column = emojiToCommand(reaction.emoji) + if tests.connect_four_reaction_test(*reaction_test_parameters): + column = emoji_to_command(reaction.emoji) params = [message, f"#{user.id}", column-1] await self.bot.games.connect_four.placePiece(*params) - if plexData[0]: - plexFuncs = self.bot.other.bedreNetflix - if plexData[1]: - moviePick = emojiToCommand(reaction.emoji) - if moviePick == "none": - imdbID = None + if plex_data[0]: + plex_functions = self.bot.other.bedre_netflix + if plex_data[1]: + movie_pick = emoji_to_command(reaction.emoji) + if movie_pick == "none": + imdb_id = None else: - imdbID = plexData[2][moviePick-1] + imdb_id = plex_data[2][movie_pick-1] if isinstance(channel, discord.DMChannel): await message.delete() - await plexFuncs.addMovie(message, imdbID, False) + await plex_functions.add_movie(message, imdb_id, False) else: await message.clear_reactions() - await plexFuncs.addMovie(message, imdbID) + await plex_functions.add_movie(message, imdb_id) else: - showPick = emojiToCommand(reaction.emoji) - if showPick == "none": - imdbName = None + show_pick = emoji_to_command(reaction.emoji) + if show_pick == "none": + imdb_name = None else: - imdbName = plexData[2][showPick-1] + imdb_name = plex_data[2][show_pick-1] if isinstance(channel, discord.DMChannel): await message.delete() - await plexFuncs.addShow(message, imdbName, False) + await plex_functions.add_show(message, imdb_name, False) else: await message.clear_reactions() - await plexFuncs.addShow(message, imdbName) + await plex_functions.add_show(message, imdb_name) - elif tests.hangmanReactionTest(*reactionTestParams): + elif tests.hangman_reaction_test(*reaction_test_parameters): self.bot.log("They reacted to the hangman message") if ord(reaction.emoji) in range(127462, 127488): # The range is letter-emojis @@ -157,8 +156,8 @@ class ErrorHandler(): params = [type(error), error, error.__traceback__] exception = traceback.format_exception(*params) - exceptionString = "".join(exception) - log_messages = [f"exception in /{ctx.name}", f"{exceptionString}"] + exception_string = "".join(exception) + log_messages = [f"exception in /{ctx.name}", f"{exception_string}"] self.bot.log(log_messages, str(ctx.channel_id), 40) if isinstance(error, discord.errors.NotFound): self.bot.log("Context is non-existant", level=40) @@ -167,12 +166,12 @@ class ErrorHandler(): async def on_error(self, method: str): """Log when there's an error.""" - errorType = sys.exc_info()[0] - if errorType == discord.errors.NotFound: + error_type = sys.exc_info()[0] + if error_type == discord.errors.NotFound: self.bot.log("Deleted message before I could add all reactions") else: exception = traceback.format_exc() - exceptionString = "".join(exception) - log_messages = [f"exception in {method}", f"{exceptionString}"] + exception_string = "".join(exception) + log_messages = [f"exception in {method}", f"{exception_string}"] self.bot.log(log_messages, level=40) diff --git a/gwendolyn/utils/helper_classes.py b/gwendolyn/utils/helper_classes.py new file mode 100644 index 0000000..cf950ed --- /dev/null +++ b/gwendolyn/utils/helper_classes.py @@ -0,0 +1,260 @@ +""" +Contains classes used for utilities. + +*Classes* +--------- + DatabaseFuncs() +""" +import os # Used to test if files exist +import json # Used to read the data about add_movie/add_show +import time # Used to test how long it's been since commands were synced + +import re # Used in get_id +import discord # Used for type hints + + +class DatabaseFuncs(): + """ + Manages database functions. + + *Methods* + --------- + get_name(user_id: str) -> str + get_id(user_name: str) -> str + delete_game(game_type: str, channel: str) + wipe_games() + connect_four_reaction_test(message: discord.Message, + user: discord.User) -> bool + hangman_reaction_test(message: discord.Message, + user: discord.User) -> bool + bedre_netflix_reaction_test(message: discord.Message, + user: discord.User) -> bool, bool, + list + imdb_commands() + """ + + def __init__(self, bot): + """Initialize the class.""" + self.bot = bot + + def get_name(self, user_id: str): + """ + Get the name of a user you have the # id of. + + *Parameters: + ------------ + user_id: str + The id of the user you want the name of. The format is + "#" + str(discord.User.id) + + *Returns* + --------- + user_name: str + The name of the user. If the user couldn't be found, + returns the user_id. + """ + user = self.bot.database["users"].find_one({"_id": user_id}) + + if user_id == f"#{self.bot.user.id}": + return_name = "Gwendolyn" + elif user is not None: + return_name = user["user name"] + else: + self.bot.log(f"Couldn't find user {user_id}") + return_name = user_id + + return return_name + + def get_id(self, user_name: str): + """ + Get the id of a user you have the username of. + + *Parameters: + ------------ + user_name: str + The name of the user you want the id of. + + *Returns* + --------- + user_id: str + The id of the user in the format "#" + + str(discord.User.id). If the user couldn't be found, + returns the user_name. + """ + user_search = {"user name": re.compile(user_name, re.IGNORECASE)} + user = self.bot.database["users"].find_one(user_search) + + if user is not None: + return_id = user["_id"] + else: + self.bot.log("Couldn't find user "+user_name) + return_id = None + + return return_id + + def delete_game(self, game_type: str, channel: str): + """ + Remove a game from the database. + + *Parameters* + ------------ + game_type: str + The name of the collection the game is in, like + "hangman games", "blackjack games" etc. + channel: str + The channel id of the channel the game is on as a + string. + """ + self.bot.database[game_type].delete_one({"_id": channel}) + + def wipe_games(self): + """Delete all running games and pull from git.""" + game_types = [ + "trivia questions", + "blackjack games", + "connect 4 games", + "hangman games", + "hex games" + ] + for game_type in game_types: + self.bot.database[game_type].delete_many({}) + + def connect_four_reaction_test(self, message: discord.Message, + user: discord.User): + """ + Test if the given message is the current connect four game. + + Also tests if the given user is the one who's turn it is. + + *Parameters* + ------------ + message: discord.Message + The message to test. + user: discord.User + The user to test. + *Returns* + --------- + : bool + Whether the given message is the current connect four + game and if the user who reacted is the user who's turn + it is. + """ + channel = message.channel + channel_search = {"_id": str(channel.id)} + game = self.bot.database["connect 4 games"].find_one(channel_search) + + old_images_path = "gwendolyn/resources/games/old_images/" + file_path = old_images_path + f"connect_four{channel.id}" + if os.path.isfile(file_path): + with open(file_path, "r") as file_pointer: + old_image = int(file_pointer.read()) + else: + old_image = 0 + + if message.id == old_image: + self.bot.log("They reacted to the connect_four game") + turn = game["turn"] + if user == game["players"][turn]: + valid_reaction = True + else: + self.bot.log("It wasn't their turn") + valid_reaction = False + else: + valid_reaction = False + + return valid_reaction + + def hangman_reaction_test(self, message: discord.Message, + user: discord.User): + """ + Test if the given message is the current hangman game. + + Also tests if the given user is the one who's playing hangman. + + *Parameters* + ------------ + message: discord.Message + The message to test. + user: discord.User + The user to test. + *Returns* + --------- + : bool + Whether the given message is the current hangman game + and if the user who reacted is the user who's playing + hangman. + """ + channel = message.channel + file_path = f"gwendolyn/resources/games/old_images/hangman{channel.id}" + if os.path.isfile(file_path): + with open(file_path, "r") as file_pointer: + old_messages = file_pointer.read().splitlines() + else: + return False + game_message = False + + for old_message in old_messages: + old_message_id = int(old_message) + if message.id == old_message_id: + database = self.bot.database["hangman games"] + channel_search = {"_id": str(channel.id)} + game = database.find_one(channel_search) + if user == game["player"]: + game_message = True + + break + + return game_message + + def bedre_netflix_reaction_test(self, message: discord.Message): + """ + Test if the given message is the response to a plex request. + + *Parameters* + ------------ + message: discord.Message + The message to test. + + *Returns* + --------- + : bool + Whether the message is the response to a plex request. + : bool + Whether it was a movie request (false for a show + request) + : list + A list of ids or names of the shows or movies that + Gwendolyn presented after the request. + """ + channel = message.channel + old_messages_path = "gwendolyn/resources/bedre_netflix/" + file_path = old_messages_path + f"old_message{str(channel.id)}" + if os.path.isfile(file_path): + with open(file_path, "r") as file_pointer: + data = json.load(file_pointer) + else: + return (False, None, None) + + if data["message_id"] != message.id: + return (False, None, None) + + if "imdb_ids" in data: + return_data = (True, True, data["imdb_ids"]) + else: + return_data = (True, False, data["imdb_names"]) + + return return_data + + async def imdb_commands(self): + """Sync the slash commands with the discord API.""" + collection = self.bot.database["last synced"] + last_synced = collection.find_one() + now = time.time() + if last_synced["last synced"] < now - 86400: + slash_command_list = await self.bot.slash.to_dict() + self.bot.log(f"Updating commands: {slash_command_list}") + await self.bot.slash.sync_all_commands() + id_number = last_synced["_id"] + query_filter = {"_id": id_number} + update = {"$set": {"last synced": now}} + collection.update_one(query_filter, update) diff --git a/gwendolyn/utils/util_functions.py b/gwendolyn/utils/util_functions.py new file mode 100644 index 0000000..cd1d185 --- /dev/null +++ b/gwendolyn/utils/util_functions.py @@ -0,0 +1,340 @@ +""" +Contains utility functions used by parts of the bot. + +*Functions* +----------- + sanitize(data: str, lower_case_value: bool = false) -> dict + get_options() -> dict + get_credentials() -> dict + long_strings() -> dict + get_params() -> dict + log_this(messages: Union[str, list], channel: str = "", + level: int = 20) + cap(s: str) -> str + make_files() + replace_multiple(main_string: str, to_be_replaced: list, + new_string: str) -> str + emoji_to_command(emoji: str) -> str +""" +import json # Used by longString(), get_params() and make_files() +import logging # Used for logging +import os # Used by make_files() to check if files exist +import sys # Used to specify printing for logging +import imdb # Used to disable logging for the module + +# All of this is logging configuration +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") +loggingConfigParams = { + "format": FORMAT, + "datefmt": DATEFORMAT, + "level": logging.INFO, + "filename": "gwendolyn.log" +} +logging.basicConfig(**loggingConfigParams) +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 + +imdb._logging.setLevel("CRITICAL") # pylint: disable=protected-access +# Basically disables imdbpy logging, since it's being printed to the +# terminal. + +def sanitize(data: str, lower_case_value: bool = False): + """ + Sanitize and create a dictionary from a string. + + Each element is created from a line with a : in it. The key is left + of the :, the value is right of it. + + *Parameters* + ------------ + data: str + The string to create a dict from. + lower_case_value: bool = False + Whether the value of each element should be lowercase. + + *Returns* + --------- + dct: dict + The sanitized dictionary of elements. + + """ + data = data.splitlines() + dct = {} + for line in data: + if line[0] != "#" and ":" in line: + line_values = line.split(":") + line_values[0] = line_values[0].lower() + line_values[1] = line_values[1].replace(" ", "") + if lower_case_value: + line_values[1] = line_values[1].lower() + + if line_values[0] in ["testing guild ids", "admins"]: + line_values[1] = line_values[1].split(",") + if all(i.isnumeric() for i in line_values[1]): + line_values[1] = [int(i) for i in line_values[1]] + + if any(i == line_values[1] for i in ["true", "false"]): + line_values[1] = (line_values[1] == "true") + + dct[line_values[0]] = line_values[1] + + return dct + + +def get_options(): + """ + Get the bot options as dict. + + *Returns* + --------- + options: dict + The options of the bot. + """ + with open("options.txt", "r") as file_pointer: + data = sanitize(file_pointer.read(), True) + + options = {} + + options["testing"] = data["testing"] + options["guild_ids"] = data["testing guild ids"] + options["admins"] = data["admins"] + return options + +def get_credentials(): + """ + Returns the credentials used by the bot as a dict. + + *Returns* + --------- + credentials: dict + The credentials used by the bot. + """ + with open("credentials.txt", "r") as file_pointer: + data = sanitize(file_pointer.read()) + + credentials = {} + + credentials["token"] = data["bot token"] + credentials["finnhub_key"] = data["finnhub api key"] + credentials["wordnik_key"] = data["wordnik api key"] + credentials["mongo_db_user"] = data["mongodb user"] + credentials["mongo_db_password"] = data["mongodb password"] + credentials["wolfram_alpha_key"] = data["wolframalpha appid"] + credentials["radarr_key"] = data["radarr api key"] + credentials["sonarr_key"] = data["sonarr api key"] + + return credentials + +def long_strings(): + """ + Get the data from gwendolyn/resources/long_strings.json. + + *Returns* + --------- + data: dict + The long strings and their keys. + """ + with open("gwendolyn/resources/long_strings.json", "r") as file_pointer: + data = json.load(file_pointer) + + return data + + +def get_params(): + """ + Get the slash command parameters. + + *Returns* + --------- + params: dict + The parameters for every slash command. + """ + with open("gwendolyn/resources/slash_parameters.json", "r") as file_pointer: + slash_parameters = json.load(file_pointer) + + options = get_options() + + if options["testing"]: + for parameter in slash_parameters: + slash_parameters[parameter]["guild_ids"] = options["guild_ids"] + + return slash_parameters + + +def log_this(messages, channel: str = "", level: int = 20): + """ + Log something in Gwendolyn's logs. + + *Parameters* + ------------ + messages: Union[str, list] + A string or list of strings to be logged. If there are + multiple strings and the level is PRINT (25) or higher, + only the first string will be printed. + channel: str = "" + The channel the event to be logged occurred in. Will be + logged along with the message(s). + level: int = 20 + The level to log the message(s) at. If PRINT (25) or + higher, the first message will be printed to the console. + """ + channel = channel.replace("Direct Message with ", "") + if isinstance(messages, str): + messages = [messages] + + print_message = messages[0] + + for i, message in enumerate(messages): + if channel != "": + messages[i] = f"{message} - ({channel})" # Adds channel ID + # to log messages + + if len(messages) > 1: # Tells user to check the log if there are + # more messages there + print_message += " (details in log)" + + if level >= 25: + printer.log(level, print_message) + + for log_message in messages: + logger.log(level, log_message) + + +def cap(input_string: str): + """ + Capitalize a string like a movie title. + + That means "of" and "the" are not capitalized. + + *Parameters* + ------------ + input_string: str + The string to capitalized. + + *Returns* + --------- + return_string: str + The capitalized string. + """ + no_caps_list = ["of", "the"] + word_number = 0 + string_list = input_string.split() + return_string = '' + for word in string_list: + word_number += 1 + if word not in no_caps_list or word_number == 1: + word = word.capitalize() + return_string += word+" " + return_string = return_string[:-1] + return return_string + + +def make_files(): + """Create all the files and directories needed by Gwendolyn.""" + def make_json_file(path, content): + """Create json file if it doesn't exist.""" + if not os.path.isfile(path): + log_this(path.split("/")[-1]+" didn't exist. Making it now.") + with open(path, "w") as file_pointer: + json.dump(content, file_pointer, indent=4) + + def make_txt_file(path, content): + """Create txt file if it doesn't exist.""" + if not os.path.isfile(path): + log_this(path.split("/")[-1]+" didn't exist. Making it now.") + with open(path, "w") as file_pointer: + file_pointer.write(content) + + def directory(path): + """Create directory if it doesn't exist.""" + if not os.path.isdir(path): + os.makedirs(path) + log_this("The "+path.split("/")[-1]+" directory didn't exist") + + with open("gwendolyn/resources/starting_files.json") as file_pointer: + data = json.load(file_pointer) + + for path, content in data["json"].items(): + make_json_file(path, content) + + for path, content in data["txt"].items(): + make_txt_file(path, content) + + for path in data["folder"]: + directory(path) + + +def replace_multiple(main_string: str, to_be_replaced: list, new_string: str): + """ + Replace multiple substrings in a string with the same substring. + + *Parameters* + ------------ + main_string: str + The string to replace substrings in. + to_be_replaced: list + The substrings to replace. + new_string: str + The string to replace the substrings with. + + *Returns* + --------- + main_string: str + The string with the substrings replaced. + """ + # Iterate over the strings to be replaced + for elem in to_be_replaced: + # Check if string is in the main string + if elem in main_string: + # Replace the string + main_string = main_string.replace(elem, new_string) + + return main_string + + +def emoji_to_command(emoji: str): + """ + Convert emoji to text. + + *Parameters* + ------------ + emoji: str + The emoji to decipher. + + *Returns* + --------- + : str + The deciphered string. + """ + if emoji == "1️⃣": + return_value = 1 + elif emoji == "2️⃣": + return_value = 2 + elif emoji == "3️⃣": + return_value = 3 + elif emoji == "4️⃣": + return_value = 4 + elif emoji == "5️⃣": + return_value = 5 + elif emoji == "6️⃣": + return_value = 6 + elif emoji == "7️⃣": + return_value = 7 + elif emoji == "🎲": + return_value = "roll" + elif emoji == "❌": + return_value = "none" + elif emoji == "✔️": + return_value = 1 + else: + return_value = "" + + return return_value diff --git a/main.py b/main.py new file mode 100644 index 0000000..ba3dee2 --- /dev/null +++ b/main.py @@ -0,0 +1,30 @@ +"""Runs the Gwendolyn bot.""" + +import platform # Used to test if the bot is running on windows, in + # order to fix a bug with asyncio +import asyncio # used to set change the loop policy if the bot is + # running on windows + +from gwendolyn import Gwendolyn +from gwendolyn.utils import make_files + + +def main(): + """Starts the bot""" + if platform.system() == "Windows": + asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) + + # Creates the required files + make_files() + + # Creates the Bot + bot = Gwendolyn() + + try: + # Runs the whole shabang + bot.run(bot.credentials["token"]) + except Exception as exception: # pylint: disable=broad-except + bot.log(bot.long_strings[f"Can't log in: {repr(exception)}"]) + +if __name__ == "__main__": + main() diff --git a/project-guidelines.md b/project-guidelines.md index 33ef2b4..583932d 100644 --- a/project-guidelines.md +++ b/project-guidelines.md @@ -91,7 +91,7 @@ The `Command` methods in cogs should only exist to perform small tasks or call c ### Folders + `cogs/` contains the command cogs. + `funcs/` contains all functions and classes called on to perform commands. All functions must be accessible through a class, of which the `Gwendolyn` class has an instance as an attribute. -+ `resources/` contains the images, lookup databases, fonts etc. that the rest of the code uses. ++ `gwendolyn/resources/` contains the images, lookup databases, fonts etc. that the rest of the code uses. ### Important files + `Gwendolyn.py` contains the Gwendolyn class and running it starts the bot. It should be lightweight and not more than 100 lines. @@ -108,6 +108,6 @@ Things you should know about the logging: + 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. Never call the `log_this()` 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 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/resources/errorCodes.txt b/resources/errorCodes.txt deleted file mode 100644 index 9234601..0000000 --- a/resources/errorCodes.txt +++ /dev/null @@ -1,128 +0,0 @@ -000 - Unspecified error -001 - Not a command - -1 - Help -100 - Unspecified error -101 - Couldn't find help.txt -102 - Couldn't find help file for specified command - -2 - Stop -200 - Unspecified error -201 - Unauthorized user - -3 - Simple Commands -310 - Hello error -320 - Map error -330 - Name error -340 - Tavern error -350 - Game error - -4 - Roll -400 - Unspecified error - -5 - Spell -500 - Unspecified error -501 - Spell not in database - -6 - Monster -600 - Unspecified error -601 - Monster name too short -602 - Monster not in database - -7 - Image -700 - Unspecified error -701 - Can't connect to Bing -702 - Error picking camera type/image name - -8 - Movie -800 - Unspecified error -801 - Error in function -802 - Can't find movie on imdb -803 - Can't extract data -804 - Can't pick movie -805 - Embed error - -9 - Star Wars -910 - Unspecified swroll error -911 - Obligation roll fucked up -912 - No character swroll -913 - Didn't include heavy/light in ranged or planetary/space in piloting -914 - Swroll invalid input -920 - Unspecified swd error -921 - Didn't specify amount of players -922 - Invalid input -930 - Unspecified swcrit -931 - Swcrit didn't include a number -940 - Unspecified swchar error -941 - No weapons -942 - Can't add to character sheet -943 - No character swchar -944 - Not on character sheet -945 - Problem overwriting data -946 - Problem removing data -947 - Problem adding data -948 - Problem removing spaces -949 - Wrong data type - -10 - Wiki -1000 - Unspecified error -1001 - Something fucked up -1002 - Can't find page - -11 - Trivia -1100 - Unspecified error -1101 - Incorrect input -1102 - Can't find question -1103 - Not an answer -1104 - No question going on -1105 - User has already answered -1106 - There's already a question goin on in the channel - -12 - Money -1210 - Unspecified balance error -1220 - Unspecified give error -1221 - Conversion error -1222 - Incorrect input -1223a - User has no money -1223b - Not enough money - -13 - Blackjack -1300 - Unspecified error -1310 - Unspecified finishing error -1311 - Error calculating winnings -1312 - Error in calcWinnings function -1320 - Unspecified loop error -1321 - Loop interrupted while waiting -1322 - Error with getHandNumber() -1330 - Unspecified continue error -1331 - Error in testIfStanding() -1340 - Error in drawing blackjack table -1341 - Error in drawHand() - -14 - connect four -1400 - Unspecified error -1401 - Error deleting old image -1410 - Unspecified parsing error -1420 - Unspecified AI error - -15 - Hex -1500 - Unspecified -1501 - Error deleting old image -1510 - Unspecified parsing error -1520 - Unspecified AI error -1531 - Invalid position -1532 - Cannot place on existing piece -1533 - Position out of bounds -1541 - Error loading board-image -1542 - Error swapping - -17 - Hangman -1700 - Unspecified error -1701 - Error parsing command -1710 - Error in drawImage() -1711 - Error in drawGallows() -1712 - Error in drawMan() -1713 - Error in drawLetterLines() -1714 - Error in drawMisses() -1720 - Unspecified hangmanGuess() error -1730 - Unspecified hangmanStart() error diff --git a/resources/games/oldImages/blackjack740652054388932682 b/resources/games/oldImages/blackjack740652054388932682 deleted file mode 100644 index a5f44f7..0000000 --- a/resources/games/oldImages/blackjack740652054388932682 +++ /dev/null @@ -1 +0,0 @@ -854063181868695593 \ No newline at end of file diff --git a/resources/games/oldImages/hangman740652054388932682 b/resources/games/oldImages/hangman740652054388932682 deleted file mode 100644 index 5372483..0000000 --- a/resources/games/oldImages/hangman740652054388932682 +++ /dev/null @@ -1,2 +0,0 @@ -854064833909489734 -854064835040641044 \ No newline at end of file diff --git a/resources/games/oldImages/hex740652054388932682 b/resources/games/oldImages/hex740652054388932682 deleted file mode 100644 index 4147771..0000000 --- a/resources/games/oldImages/hex740652054388932682 +++ /dev/null @@ -1 +0,0 @@ -854064794356023346 \ No newline at end of file diff --git a/resources/help/help-addshow.txt b/resources/help/help-addshow.txt deleted file mode 100644 index 9d35d5c..0000000 --- a/resources/help/help-addshow.txt +++ /dev/null @@ -1 +0,0 @@ -Du kan søge efter et show ved at skrive `/addshow [søgning]`. Gwendolyn vil derefter vise dig resultater baseret på din søgning. Du kan derfra reagere på Gwendolyns besked for at downloade et specifikt show. \ No newline at end of file diff --git a/utils/__init__.py b/utils/__init__.py deleted file mode 100644 index b6ad44b..0000000 --- a/utils/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -"""A collections of utilities used by Gwendolyn and her functions.""" - -__all__ = ["Options", "Credentials", "DatabaseFuncs", "EventHandler", - "ErrorHandler", "getParams", "logThis", "cap", "makeFiles", - "replaceMultiple", "emojiToCommand"] - -from .helper_classes import Options, Credentials, DatabaseFuncs -from .event_handlers import EventHandler, ErrorHandler -from .util_functions import (getParams, logThis, cap, makeFiles, - replaceMultiple, emojiToCommand, long_strings) diff --git a/utils/helper_classes.py b/utils/helper_classes.py deleted file mode 100644 index ce86b1f..0000000 --- a/utils/helper_classes.py +++ /dev/null @@ -1,330 +0,0 @@ -""" -Contains classes used for utilities. - -*Functions* ------------ - Sanitize(data: str, lowerCaseValue: bool = false) -> dict - -*Classes* ---------- - Options() - Credentials() - DatabaseFuncs() -""" -import re # Used in getID -import git # Used to pull when stopping -import os # Used to test if files exist -import json # Used to read the data about addmovie/addshow -import time # Used to test how long it's been since commands were synced -import discord # Used for type hints - - -def sanitize(data: str, lowerCaseValue: bool = False): - """ - Sanitize and create a dictionary from a string. - - Each element is created from a line with a : in it. The key is left - of the :, the value is right of it. - - *Parameters* - ------------ - data: str - The string to create a dict from. - lowerCaseValue: bool = False - Whether the value of each element should be lowercase. - - *Returns* - --------- - dct: dict - The sanitized dictionary of elements. - - """ - data = data.splitlines() - dct = {} - for line in data: - if line[0] != "#" and ":" in line: - lineValues = line.split(":") - lineValues[0] = lineValues[0].lower() - lineValues[1] = lineValues[1].replace(" ", "") - if lowerCaseValue: - lineValues[1] = lineValues[1].lower() - - if lineValues[0] in ["testing guild ids", "admins"]: - lineValues[1] = lineValues[1].split(",") - if all(i.isnumeric() for i in lineValues[1]): - lineValues[1] = [int(i) for i in lineValues[1]] - - if any(i == lineValues[1] for i in ["true", "false"]): - lineValues[1] = (lineValues[1] == "true") - - dct[lineValues[0]] = lineValues[1] - - return dct - - -class Options(): - """Contains the options for the bot.""" - - def __init__(self): - """Initialize the options.""" - with open("options.txt", "r") as f: - data = sanitize(f.read(), True) - - self.testing = data["testing"] - self.guildIds = data["testing guild ids"] - self.admins = data["admins"] - - -class Credentials(): - """Contains the credentials for the bot and apis.""" - - def __init__(self): - """Initialize the credentials.""" - with open("credentials.txt", "r") as f: - data = sanitize(f.read()) - - self.token = data["bot token"] - self.finnhub_key = data["finnhub api key"] - self.wordnikKey = data["wordnik api key"] - self.mongoDBUser = data["mongodb user"] - self.mongoDBPassword = data["mongodb password"] - self.wolfKey = data["wolframalpha appid"] - self.radarrKey = data["radarr api key"] - self.sonarrKey = data["sonarr api key"] - - -class DatabaseFuncs(): - """ - Manages database functions. - - *Methods* - --------- - getName(userID: str) -> str - getID(userName: str) -> str - deleteGame(gameType: str, channel: str) - wipeGames() - connect_fourReactionTest(message: discord.Message, - user: discord.User) -> bool - hangmanReactionTest(message: discord.Message, - user: discord.User) -> bool - BedreNetflixReactionTest(message: discord.Message, - user: discord.User) -> bool, bool, - list - syncCommands() - """ - - def __init__(self, bot): - """Initialize the class.""" - self.bot = bot - - def getName(self, userID: str): - """ - Get the name of a user you have the # id of. - - *Parameters: - ------------ - userID: str - The id of the user you want the name of. The format is - "#" + str(discord.User.id) - - *Returns* - --------- - userName: str - The name of the user. If the user couldn't be found, - returns the userID. - """ - user = self.bot.database["users"].find_one({"_id": userID}) - - if userID == f"#{self.bot.user.id}": - return "Gwendolyn" - elif user is not None: - return user["user name"] - else: - self.bot.log(f"Couldn't find user {userID}") - return userID - - def getID(self, userName: str): - """ - Get the id of a user you have the username of. - - *Parameters: - ------------ - userName: str - The name of the user you want the id of. - - *Returns* - --------- - userID: str - The id of the user in the format "#" + - str(discord.User.id). If the user couldn't be found, - returns the userName. - """ - userSearch = {"user name": re.compile(userName, re.IGNORECASE)} - user = self.bot.database["users"].find_one(userSearch) - - if user is not None: - return user["_id"] - else: - self.bot.log("Couldn't find user "+userName) - return None - - def deleteGame(self, gameType: str, channel: str): - """ - Remove a game from the database. - - *Parameters* - ------------ - gameType: str - The name of the collection the game is in, like - "hangman games", "blackjack games" etc. - channel: str - The channel id of the channel the game is on as a - string. - """ - self.bot.database[gameType].delete_one({"_id": channel}) - - def wipeGames(self): - """Delete all running games and pull from git.""" - self.bot.database["trivia questions"].delete_many({}) - 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("") - g.pull() - - def connect_fourReactionTest(self, message: discord.Message, - user: discord.User): - """ - Test if the given message is the current connect four game. - - Also tests if the given user is the one who's turn it is. - - *Parameters* - ------------ - message: discord.Message - The message to test. - user: discord.User - The user to test. - *Returns* - --------- - : bool - Whether the given message is the current connect four - game and if the user who reacted is the user who's turn - it is. - """ - channel = message.channel - channelSearch = {"_id": str(channel.id)} - game = self.bot.database["connect 4 games"].find_one(channelSearch) - - filePath = f"resources/games/old_images/connect_four{channel.id}" - if os.path.isfile(filePath): - with open(filePath, "r") as f: - oldImage = int(f.read()) - else: - oldImage = 0 - - if message.id == oldImage: - self.bot.log("They reacted to the connect_four game") - turn = game["turn"] - if user == game["players"][turn]: - return True - else: - self.bot.log("It wasn't their turn") - return False - else: - return False - - def hangmanReactionTest(self, message: discord.Message, - user: discord.User): - """ - Test if the given message is the current hangman game. - - Also tests if the given user is the one who's playing hangman. - - *Parameters* - ------------ - message: discord.Message - The message to test. - user: discord.User - The user to test. - *Returns* - --------- - : bool - Whether the given message is the current hangman game - and if the user who reacted is the user who's playing - hangman. - """ - channel = message.channel - filePath = f"resources/games/old_images/hangman{channel.id}" - if os.path.isfile(filePath): - with open(filePath, "r") as f: - oldMessages = f.read().splitlines() - else: - return False - gameMessage = False - - for oldMessage in oldMessages: - oldMessageID = int(oldMessage) - if message.id == oldMessageID: - database = self.bot.database["hangman games"] - channelSearch = {"_id": str(channel.id)} - game = database.find_one(channelSearch) - if user == game["player"]: - gameMessage = True - - break - - return gameMessage - - def bedreNetflixReactionTest(self, message: discord.Message): - """ - Test if the given message is the response to a plex request. - - *Parameters* - ------------ - message: discord.Message - The message to test. - - *Returns* - --------- - : bool - Whether the message is the response to a plex request. - : bool - Whether it was a movie request (false for a show - request) - : list - A list of ids or names of the shows or movies that - Gwendolyn presented after the request. - """ - channel = message.channel - filePath = f"resources/bedreNetflix/oldMessage{str(channel.id)}" - if os.path.isfile(filePath): - with open(filePath, "r") as f: - data = json.load(f) - else: - return False, None, None - - if data["messageID"] == message.id: - if "imdbIds" in data: - return True, True, data["imdbIds"] - else: - return True, False, data["imdbNames"] - else: - return False, None, None - - async def syncCommands(self): - """Sync the slash commands with the discord API.""" - collection = self.bot.database["last synced"] - lastSynced = collection.find_one() - now = time.time() - if lastSynced["last synced"] < now - 86400: - slashCommandList = await self.bot.slash.to_dict() - self.bot.log(f"Updating commands: {slashCommandList}") - await self.bot.slash.sync_all_commands() - idNumber = lastSynced["_id"] - queryFilter = {"_id": idNumber} - update = {"$set": {"last synced": now}} - collection.update_one(queryFilter, update) diff --git a/utils/util_functions.py b/utils/util_functions.py deleted file mode 100644 index af27573..0000000 --- a/utils/util_functions.py +++ /dev/null @@ -1,250 +0,0 @@ -""" -Contains utility functions used by parts of the bot. - -*Functions* ------------ - long_strings() -> dict - getParams() -> dict - logThis(messages: Union[str, list], channel: str = "", - level: int = 20) - cap(s: str) -> str - makeFiles() - replaceMultiple(mainString: str, toBeReplaced: list, - newString: str) -> str - emojiToCommand(emoji: str) -> str -""" -import json # Used by longString(), getParams() and makeFiles() -import logging # Used for logging -import os # Used by makeFiles() to check if files exist -import sys # Used to specify printing for logging -import imdb # Used to disable logging for the module -from .helper_classes import Options # Used by getParams() - - -# All of this is logging configuration -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") -loggingConfigParams = { - "format": FORMAT, - "datefmt": DATEFORMAT, - "level": logging.INFO, - "filename": "gwendolyn.log" -} -logging.basicConfig(**loggingConfigParams) -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 - -imdb._logging.setLevel("CRITICAL") # Basically disables imdbpy -# logging, since it's printed to the terminal. - - -def long_strings(): - """ - Get the data from resources/long_strings.json. - - *Returns* - --------- - data: dict - The long strings and their keys. - """ - with open("resources/long_strings.json", "r") as f: - data = json.load(f) - - return data - - -def getParams(): - """ - Get the slash command parameters. - - *Returns* - --------- - params: dict - The parameters for every slash command. - """ - 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 - - return params - - -def logThis(messages, channel: str = "", level: int = 20): - """ - Log something in Gwendolyn's logs. - - *Parameters* - ------------ - messages: Union[str, list] - A string or list of strings to be logged. If there are - multiple strings and the level is PRINT (25) or higher, - only the first string will be printed. - channel: str = "" - The channel the event to be logged occurred in. Will be - logged along with the message(s). - level: int = 20 - The level to log the message(s) at. If PRINT (25) or - higher, the first message will be printed to the console. - """ - 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] = f"{msg} - ({channel})" # Adds channel to log - # messages - - if len(messages) > 1: # Tells user to check the log if there are - # more messages there - printMessage += " (details in log)" - - if level >= 25: - printer.log(level, printMessage) - - for log_message in messages: - logger.log(level, log_message) - - -def cap(s: str): - """ - Capitalize a string like a movie title. - - That means "of" and "the" are not capitalized. - - *Parameters* - ------------ - s: str - The string to capitalized. - - *Returns* - --------- - res: str - The capitalized string. - """ - no_caps_list = ["of", "the"] - word_number = 0 - lst = s.split() - res = '' - for word in lst: - word_number += 1 - if word not in no_caps_list or word_number == 1: - word = word.capitalize() - res += word+" " - res = res[:-1] - return res - - -def makeFiles(): - """Create all the files and directories needed by Gwendolyn.""" - def makeJsonFile(path, content): - """Create json file if it doesn't exist.""" - if not os.path.isfile(path): - logThis(path.split("/")[-1]+" didn't exist. Making it now.") - with open(path, "w") as f: - json.dump(content, f, indent=4) - - def makeTxtFile(path, content): - """Create txt file if it doesn't exist.""" - if not os.path.isfile(path): - logThis(path.split("/")[-1]+" didn't exist. Making it now.") - with open(path, "w") as f: - f.write(content) - - def directory(path): - """Create directory if it doesn't exist.""" - if not os.path.isdir(path): - os.makedirs(path) - logThis("The "+path.split("/")[-1]+" directory didn't exist") - - with open("resources/startingFiles.json") as f: - data = json.load(f) - - for path, content in data["json"].items(): - makeJsonFile(path, content) - - for path, content in data["txt"].items(): - makeTxtFile(path, content) - - for path in data["folder"]: - directory(path) - - -def replaceMultiple(mainString: str, toBeReplaced: list, newString: str): - """ - Replace multiple substrings in a string with the same substring. - - *Parameters* - ------------ - mainString: str - The string to replace substrings in. - toBeReplaced: list - The substrings to replace. - newString: str - The string to replace the substrings with. - - *Returns* - --------- - mainString: str - The string with the substrings replaced. - """ - # Iterate over the strings to be replaced - for elem in toBeReplaced: - # Check if string is in the main string - if elem in mainString: - # Replace the string - mainString = mainString.replace(elem, newString) - - return mainString - - -def emojiToCommand(emoji: str): - """ - Convert emoji to text. - - *Parameters* - ------------ - emoji: str - The emoji to decipher. - - *Returns* - --------- - : str - The deciphered string. - """ - if emoji == "1️⃣": - return 1 - elif emoji == "2️⃣": - return 2 - elif emoji == "3️⃣": - return 3 - elif emoji == "4️⃣": - return 4 - elif emoji == "5️⃣": - return 5 - elif emoji == "6️⃣": - return 6 - elif emoji == "7️⃣": - return 7 - elif emoji == "🎲": - return "roll" - elif emoji == "❌": - return "none" - elif emoji == "✔️": - return 1 - else: - return "" From 573d081734c7b32cc0e7ad87988c9e274790696d Mon Sep 17 00:00:00 2001 From: Nikolaj Date: Tue, 17 Aug 2021 18:05:41 +0200 Subject: [PATCH 23/23] :broom: More refactoring --- .gitignore | 4 +- gwendolyn/cogs/game_cog.py | 2 +- gwendolyn/cogs/misc_cog.py | 8 +- gwendolyn/funcs/games/blackjack.py | 1122 +++++++++-------- gwendolyn/funcs/games/connect_four.py | 719 ++++++----- gwendolyn/funcs/games/hangman.py | 499 ++++---- gwendolyn/funcs/games/hex.py | 136 +- gwendolyn/funcs/games/invest.py | 78 +- gwendolyn/funcs/games/money.py | 22 +- gwendolyn/funcs/games/trivia.py | 12 +- gwendolyn/funcs/lookup/lookup_funcs.py | 12 +- gwendolyn/funcs/other/__init__.py | 2 +- gwendolyn/funcs/other/bedre_netflix.py | 413 ------ gwendolyn/funcs/other/generators.py | 8 +- gwendolyn/funcs/other/nerd_shit.py | 22 +- gwendolyn/funcs/other/other.py | 30 +- gwendolyn/funcs/other/plex.py | 536 ++++++++ .../funcs/star_wars_funcs/star_wars_char.py | 6 +- .../star_wars_funcs/star_wars_destiny.py | 18 +- .../funcs/star_wars_funcs/star_wars_roll.py | 42 +- gwendolyn/resources/help/help-downloading.txt | 2 +- gwendolyn/resources/help/help-movie.txt | 2 +- gwendolyn/resources/help/help.txt | 6 +- gwendolyn/resources/longStrings.json | 21 - gwendolyn/resources/long_strings.json | 8 +- gwendolyn/resources/slash_parameters.json | 8 +- gwendolyn/resources/starting_files.json | 4 +- gwendolyn/utils/event_handlers.py | 6 +- gwendolyn/utils/helper_classes.py | 6 +- gwendolyn/utils/util_functions.py | 2 +- requirements.txt | 2 +- 31 files changed, 1971 insertions(+), 1787 deletions(-) delete mode 100644 gwendolyn/funcs/other/bedre_netflix.py create mode 100644 gwendolyn/funcs/other/plex.py delete mode 100644 gwendolyn/resources/longStrings.json diff --git a/.gitignore b/.gitignore index 59cd0e4..affb8ee 100644 --- a/.gitignore +++ b/.gitignore @@ -154,11 +154,11 @@ token.txt credentials.txt options.txt gwendolyn/resources/star_wars/destinyPoints.txt -gwendolyn/resources/bedre_netflix/ +gwendolyn/resources/plex/ gwendolyn/resources/games/hilo/ gwendolyn/resources/games/blackjack_tables/ gwendolyn/resources/games/old_images/ -gwendolyn/resources/games/connect4Boards/ +gwendolyn/resources/games/connect_four_boards/ gwendolyn/resources/games/hex_boards/ gwendolyn/resources/games/hangman_boards/ gwendolyn/resources/lookup/monsters.json diff --git a/gwendolyn/cogs/game_cog.py b/gwendolyn/cogs/game_cog.py index 0caad15..8b04fd9 100644 --- a/gwendolyn/cogs/game_cog.py +++ b/gwendolyn/cogs/game_cog.py @@ -50,7 +50,7 @@ class BlackjackCog(commands.Cog): @cog_ext.cog_subcommand(**params["blackjack_bet"]) async def blackjack_bet(self, ctx, bet): """Enter the game of blackjack with a bet.""" - await self.bot.games.blackjack.enterGame(ctx, bet) + await self.bot.games.blackjack.enter_game(ctx, bet) @cog_ext.cog_subcommand(**params["blackjack_stand"]) async def blackjack_stand(self, ctx, hand=""): diff --git a/gwendolyn/cogs/misc_cog.py b/gwendolyn/cogs/misc_cog.py index cd2bffe..5042757 100644 --- a/gwendolyn/cogs/misc_cog.py +++ b/gwendolyn/cogs/misc_cog.py @@ -15,7 +15,7 @@ class MiscCog(commands.Cog): self.bot = bot self.bot.remove_command("help") self.generators = bot.other.generators - self.bedre_netflix = bot.other.bedre_netflix + self.plex = bot.other.plex self.nerd_shit = bot.other.nerd_shit @cog_ext.cog_slash(**params["ping"]) @@ -76,17 +76,17 @@ class MiscCog(commands.Cog): @cog_ext.cog_slash(**params["add_movie"]) async def add_movie(self, ctx, movie): """Search for a movie and add it to the Plex server.""" - await self.bedre_netflix.requestMovie(ctx, movie) + await self.plex.request_movie(ctx, movie) @cog_ext.cog_slash(**params["add_show"]) async def add_show(self, ctx, show): """Search for a show and add it to the Plex server.""" - await self.bedre_netflix.requestShow(ctx, show) + await self.plex.request_show(ctx, show) @cog_ext.cog_slash(**params["downloading"]) async def downloading(self, ctx, parameters="-d"): """Get the current downloading torrents.""" - await self.bedre_netflix.downloading(ctx, parameters) + await self.plex.downloading(ctx, parameters) @cog_ext.cog_slash(**params["wolf"]) async def wolf(self, ctx, query): diff --git a/gwendolyn/funcs/games/blackjack.py b/gwendolyn/funcs/games/blackjack.py index 4b5b1be..57c053a 100644 --- a/gwendolyn/funcs/games/blackjack.py +++ b/gwendolyn/funcs/games/blackjack.py @@ -12,12 +12,12 @@ import random # Used to shuffle the blackjack cards import math # Used for flooring decimal numbers import datetime # Used to generate the game id import asyncio # Used for sleeping +from shutil import copyfile + import discord # Used for discord.file import discord_slash # Used for typehints from PIL import Image, ImageDraw, ImageFont -from shutil import copyfile - from gwendolyn.utils import replace_multiple @@ -28,14 +28,14 @@ class Blackjack(): *Methods* --------- hit(ctx: discord_slash.context.SlashContext, - handNumber: int = 0) + hand_number: int = 0) double(ctx: discord_slash.context.SlashContext, - handNumber: int = 0) + hand_number: int = 0) stand(ctx: discord_slash.context.SlashContext, - handNumber: int = 0) + hand_number: int = 0) split(ctx: discord_slash.context.SlashContext, - handNumber: int = 0) - enterGame(ctx: discord_slash.context.SlashContext, bet: int) + hand_number: int = 0) + enter_game(ctx: discord_slash.context.SlashContext, bet: int) start(ctx: discord_slash.context.SlashContext) hilo(ctx: discord_slash.context.SlashContext) shuffle(ctx: discord_slash.context.SlashContext) @@ -47,6 +47,7 @@ class Blackjack(): self.bot = bot self.draw = DrawBlackjack(bot) self.decks = 4 + self.long_strings = self.bot.long_strings def _blackjack_shuffle(self, channel: str): """ @@ -62,25 +63,26 @@ class Blackjack(): """ self.bot.log("Shuffling the blackjack deck") - with open("gwendolyn/resources/games/deck_of_cards.txt", "r") as f: - deck = f.read() + deck_path = "gwendolyn/resources/games/deck_of_cards.txt" + with open(deck_path, "r") as file_pointer: + deck = file_pointer.read() - allDecks = deck.split("\n") * self.decks - random.shuffle(allDecks) + all_decks = deck.split("\n") * self.decks + random.shuffle(all_decks) blackjack_cards = self.bot.database["blackjack cards"] cards = {"_id": channel} - cardUpdater = {"$set": {"_id": channel, "cards": allDecks}} - blackjack_cards.update_one(cards, cardUpdater, upsert=True) + card_updater = {"$set": {"_id": channel, "cards": all_decks}} + blackjack_cards.update_one(cards, card_updater, upsert=True) # Creates hilo file self.bot.log(f"creating hilo doc for {channel}") data = 0 blackjack_hilo = self.bot.database["hilo"] - hiloUpdater = {"$set": {"_id": channel, "hilo": data}} - blackjack_hilo.update_one({"_id": channel}, hiloUpdater, upsert=True) + hilo_updater = {"$set": {"_id": channel, "hilo": data}} + blackjack_hilo.update_one({"_id": channel}, hilo_updater, upsert=True) - def _calcHandValue(self, hand: list): + def _calc_hand_value(self, hand: list): """ Calculate the value of a blackjack hand. @@ -95,36 +97,33 @@ class Blackjack(): *Returns* --------- - handValue: int + hand_value: int The blackjack value of the hand. """ - values = [] - values.append(0) + values = [0] for card in hand: - cardValue = card[0] - cardValue = replace_multiple(cardValue, ["0", "k", "q", "j"], "10") - if cardValue == "a": + card_value = replace_multiple(card[0], ["0", "k", "q", "j"], "10") + if card_value == "a": length = len(values) - for x in range(length): - values.append(values[x] + 11) - values[x] += 1 + for i in range(length): + values.append(values[i] + 11) + values[i] += 1 else: - for x in range(len(values)): - values[x] += int(cardValue) + values = [int(i)+int(card_value) for i in values] values.sort() - handValue = values[0] + hand_value = values[0] for value in values: if value <= 21: - handValue = value + hand_value = value - self.bot.log(f"Calculated the value of {hand} to be {handValue}") + self.bot.log(f"Calculated the value of {hand} to be {hand_value}") - return handValue + return hand_value - def _drawCard(self, channel: str): + def _draw_card(self, channel: str): """ Draw a card from the stack. @@ -136,9 +135,9 @@ class Blackjack(): self.bot.log("drawing a card") blackjack_cards = self.bot.database["blackjack cards"] - drawnCard = blackjack_cards.find_one({"_id": channel})["cards"][0] + drawn_card = blackjack_cards.find_one({"_id": channel})["cards"][0] blackjack_cards.update_one({"_id": channel}, {"$pop": {"cards": -1}}) - value = self._calcHandValue([drawnCard]) + value = self._calc_hand_value([drawn_card]) blackjack_hilo = self.bot.database["hilo"] @@ -147,9 +146,9 @@ class Blackjack(): elif value >= 10: blackjack_hilo.update_one({"_id": channel}, {"$inc": {"hilo": -1}}) - return drawnCard + return drawn_card - def _dealerDraw(self, channel: str): + def _dealer_draw(self, channel: str): """ Draw a card for the dealer. @@ -166,20 +165,20 @@ class Blackjack(): game = self.bot.database["blackjack games"].find_one({"_id": channel}) done = False - dealerHand = game["dealer hand"] + dealer_hand = game["dealer hand"] blackjack_games = self.bot.database["blackjack games"] - if self._calcHandValue(dealerHand) < 17: - dealerHand.append(self._drawCard(channel)) - dealerUpdater = {"$set": {"dealer hand": dealerHand}} - blackjack_games.update_one({"_id": channel}, dealerUpdater) + if self._calc_hand_value(dealer_hand) < 17: + dealer_hand.append(self._draw_card(channel)) + dealer_updater = {"$set": {"dealer hand": dealer_hand}} + blackjack_games.update_one({"_id": channel}, dealer_updater) else: done = True - if self._calcHandValue(dealerHand) > 21: - dealerUpdater = {"$set": {"dealer busted": True}} - blackjack_games.update_one({"_id": channel}, dealerUpdater) + if self._calc_hand_value(dealer_hand) > 21: + dealer_updater = {"$set": {"dealer busted": True}} + blackjack_games.update_one({"_id": channel}, dealer_updater) return done @@ -194,9 +193,9 @@ class Blackjack(): *Returns* --------- - sendMessage: str + send_message: str The message to send to the channel. - allStanding: bool + all_standing: bool If all players are standing. gameDone: bool If the game has finished. @@ -209,53 +208,53 @@ class Blackjack(): blackjack_games = self.bot.database["blackjack games"] blackjack_games.update_one({"_id": channel}, {"$inc": {"round": 1}}) - allStanding = True - preAllStanding = True - message = self.bot.long_strings["Blackjack all players standing"] + all_standing = True + pre_all_standing = True + message = self.long_strings["Blackjack all players standing"] if game["all standing"]: self.bot.log("All are standing") - done = self._dealerDraw(channel) + done = self._dealer_draw(channel) message = "The dealer draws a card." blackjack_games.find_one({"_id": channel}) self.bot.log("Testing if all are standing") for user in game["user hands"]: - userHand = game["user hands"][user] - test_parameters = [userHand, allStanding, preAllStanding, True] - standingTest = (self._testIfStanding(*test_parameters)) - newUser, allStanding, preAllStanding = standingTest - handUpdater = {"$set": {"user hands."+user: newUser}} - blackjack_games.update_one({"_id": channel}, handUpdater) + user_hand = game["user hands"][user] + test_parameters = [user_hand, all_standing, pre_all_standing, True] + standing_test = (self._test_if_standing(*test_parameters)) + new_user, all_standing, pre_all_standing = standing_test + hand_updater = {"$set": {"user hands."+user: new_user}} + blackjack_games.update_one({"_id": channel}, hand_updater) - if allStanding: - gameUpdater = {"$set": {"all standing": True}} - blackjack_games.update_one({"_id": channel}, gameUpdater) + if all_standing: + game_updater = {"$set": {"all standing": True}} + blackjack_games.update_one({"_id": channel}, game_updater) - self.draw.drawImage(channel) + self.draw.draw_image(channel) - if allStanding: + if all_standing: if not done: return message, True, done else: return "The dealer is done drawing cards", True, done - elif preAllStanding: + elif pre_all_standing: return "", True, done else: + send_message = self.long_strings["Blackjack commands"] + if game["round"] == 0: - firstRoundMsg = self.bot.long_strings["Blackjack first round"] + send_message = send_message.format( + self.long_strings["Blackjack first round"]) else: - firstRoundMsg = "" + send_message = send_message.format("") - sendMessage = self.bot.long_strings["Blackjack commands"] - print(firstRoundMsg) - sendMessage = sendMessage.format(firstRoundMsg) - return sendMessage, False, done + return send_message, False, done - def _testIfStanding(self, hand: dict, allStanding: bool, - preAllStanding: bool, topLevel: bool): + def _test_if_standing(self, hand: dict, all_standing: bool, + pre_all_standing: bool, top_level: bool): """ Test if a player is standing on all their hands. @@ -265,13 +264,13 @@ class Blackjack(): ------------ hand: dict The hand to test and reset. - allStanding: bool + all_standing: bool Is set to True at the top level. If it's false, the player is not standing on one of the previously tested hands. - preAllStanding: bool + pre_all_standing: bool Is set to True at the top level. - topLevel: bool + top_level: bool If the input hand is _all_ if the player's hands. If False, it's one of the hands resulting from a split. @@ -279,10 +278,10 @@ class Blackjack(): --------- hand: dict The reset hand. - allStanding: bool + all_standing: bool If the player is standing on all their hands. - preAllStanding: bool - Is true if allStanding is True, or if a player has done + pre_all_standing: bool + Is true if all_standing is True, or if a player has done something equivalent to standing but still needs to see the newly drawn card. """ @@ -291,33 +290,54 @@ class Blackjack(): hand["standing"] = True if not hand["standing"]: - allStanding = False + all_standing = False - if self._calcHandValue(hand["hand"]) >= 21 or hand["doubled"]: + if self._calc_hand_value(hand["hand"]) >= 21 or hand["doubled"]: hand["standing"] = True else: - preAllStanding = False + pre_all_standing = False hand["hit"] = False - if topLevel: + if top_level: if hand["split"] >= 1: - testHand = hand["other hand"] - test_parameters = [testHand, allStanding, preAllStanding, False] - standingTest = (self._testIfStanding(*test_parameters)) - hand["other hand"], allStanding, preAllStanding = standingTest + test_hand = hand["other hand"] + test_parameters = [ + test_hand, + all_standing, + pre_all_standing, + False + ] + standing_test = (self._test_if_standing(*test_parameters)) + hand["other hand"] = standing_test[0] + all_standing = standing_test[1] + pre_all_standing = standing_test[2] if hand["split"] >= 2: - testHand = hand["third hand"] - test_parameters = [testHand, allStanding, preAllStanding, False] - standingTest = (self._testIfStanding(*test_parameters)) - hand["third hand"], allStanding, preAllStanding = standingTest + test_hand = hand["third hand"] + test_parameters = [ + test_hand, + all_standing, + pre_all_standing, + False + ] + standing_test = (self._test_if_standing(*test_parameters)) + hand["third hand"] = standing_test[0] + all_standing = standing_test[1] + pre_all_standing = standing_test[2] if hand["split"] >= 3: - testHand = hand["fourth hand"] - test_parameters = [testHand, allStanding, preAllStanding, False] - standingTest = (self._testIfStanding(*test_parameters)) - hand["fourth hand"], allStanding, preAllStanding = standingTest + test_hand = hand["fourth hand"] + test_parameters = [ + test_hand, + all_standing, + pre_all_standing, + False + ] + standing_test = (self._test_if_standing(*test_parameters)) + hand["fourth hand"] = standing_test[0] + all_standing = standing_test[1] + pre_all_standing = standing_test[2] - return hand, allStanding, preAllStanding + return hand, all_standing, pre_all_standing def _blackjack_finish(self, channel: str): """ @@ -330,54 +350,57 @@ class Blackjack(): *Returns* --------- - finalWinnings: str + final_winnings: str The winnings message. """ - finalWinnings = "*Final Winnings:*\n" + final_winnings = "*Final Winnings:*\n" game = self.bot.database["blackjack games"].find_one({"_id": channel}) - dealerValue = self._calcHandValue(game["dealer hand"]) - dealerBlackjack = game["dealer blackjack"] - dealerBusted = game["dealer busted"] + dealer_value = self._calc_hand_value(game["dealer hand"]) + dealer_blackjack = game["dealer blackjack"] + dealer_busted = game["dealer busted"] for user in game["user hands"]: - _calcWinningsParams = [ + calc_winnings_parameters = [ game["user hands"][user], - dealerValue, + dealer_value, True, - dealerBlackjack, - dealerBusted + dealer_blackjack, + dealer_busted ] - winningCalc = (self._calcWinnings(*_calcWinningsParams)) - winnings, netWinnings, reason = winningCalc + winnings_calc = (self._calc_winning(*calc_winnings_parameters)) + winnings, net_winnings, reason = winnings_calc user_name = self.bot.database_funcs.get_name(user) if winnings < 0: if winnings == -1: - finalWinnings += f"{user_name} lost 1 GwendoBuck {reason}\n" + final_winnings += "{} lost 1 GwendoBuck {}\n".format( + user_name, + reason + ) else: - moneyLost = -1 * winnings - winningText = f"{user_name} lost {moneyLost} GwendoBucks" - winningText += f" {reason}\n" - finalWinnings += winningText + money_lost = -1 * winnings + winnings_text = f"{user_name} lost {money_lost} GwendoBucks" + winnings_text += f" {reason}\n" + final_winnings += winnings_text else: if winnings == 1: - finalWinnings += f"{user_name} won 1 GwendoBuck {reason}\n" + final_winnings += f"{user_name} won 1 GwendoBuck {reason}\n" else: - winningText = f"{user_name} won {winnings} GwendoBucks" - winningText += f" {reason}\n" - finalWinnings += winningText + winnings_text = f"{user_name} won {winnings} GwendoBucks" + winnings_text += f" {reason}\n" + final_winnings += winnings_text - self.bot.money.addMoney(user, netWinnings) + self.bot.money.addMoney(user, net_winnings) self.bot.database["blackjack games"].delete_one({"_id": channel}) - return finalWinnings + return final_winnings - def _calcWinnings(self, hand: dict, dealerValue: int, topLevel: bool, - dealerBlackjack: bool, dealerBusted: bool): + def _calc_winning(self, hand: dict, dealer_value: int, top_level: bool, + dealer_blackjack: bool, dealer_busted: bool): """ Calculate how much a user has won/lost in the blackjack game. @@ -385,21 +408,21 @@ class Blackjack(): ------------ hand: dict The hand to calculate the winnings of. - dealerValue: int + dealer_value: int The dealer's hand value. - topLevel: bool + top_level: bool If the input hand is _all_ if the player's hands. If False, it's one of the hands resulting from a split. - dealerBlackjack: bool + dealer_blackjack: bool If the dealer has a blackjack. - dealerBusted: bool + dealer_busted: bool If the dealer busted. *Returns* --------- winnings: int How much the player has won/lost. - netWinnings: int + net_winnings: int winnings minus the original bet. This is added to the user's account, since the bet was removed from their account when they placed the bet. @@ -410,77 +433,77 @@ class Blackjack(): reason = "" bet = hand["bet"] winnings = -1 * bet - netWinnings = 0 - handValue = self._calcHandValue(hand["hand"]) + net_winnings = 0 + hand_value = self._calc_hand_value(hand["hand"]) - if hand["blackjack"] and not dealerBlackjack: + if hand["blackjack"] and not dealer_blackjack: reason += "(blackjack)" winnings += math.floor(2.5 * bet) - netWinnings += math.floor(2.5 * bet) - elif dealerBlackjack: + net_winnings += math.floor(2.5 * bet) + elif dealer_blackjack: reason += "(dealer blackjack)" elif hand["busted"]: reason += "(busted)" else: - if dealerBusted: + if dealer_busted: reason = "(dealer busted)" winnings += 2 * bet - netWinnings += 2 * bet - elif handValue > dealerValue: + net_winnings += 2 * bet + elif hand_value > dealer_value: winnings += 2 * bet - netWinnings += 2 * bet + net_winnings += 2 * bet reason = "(highest value)" - elif handValue == dealerValue: + elif hand_value == dealer_value: reason = "(pushed)" winnings += bet - netWinnings += bet + net_winnings += bet else: reason = "(highest value)" - if topLevel: + if top_level: if hand["split"] >= 1: - _calcWinningsParams = [ + calc_winnings_parameters = [ hand["other hand"], - dealerValue, + dealer_value, False, - dealerBlackjack, - dealerBusted + dealer_blackjack, + dealer_busted ] - winningsCalc = self._calcWinnings(*_calcWinningsParams) - winningsTemp, netWinningsTemp, reasonTemp = winningsCalc - winnings += winningsTemp - netWinnings += netWinningsTemp - reason += reasonTemp + winnings_calc = self._calc_winning(*calc_winnings_parameters) + winnings_temp, net_winnings_temp, reason_temp = winnings_calc + winnings += winnings_temp + net_winnings += net_winnings_temp + reason += reason_temp if hand["split"] >= 2: - _calcWinningsParams = [ + calc_winnings_parameters = [ hand["third hand"], - dealerValue, + dealer_value, False, - dealerBlackjack, - dealerBusted + dealer_blackjack, + dealer_busted ] - winningsCalc = self._calcWinnings(*_calcWinningsParams) - winningsTemp, netWinningsTemp, reasonTemp = winningsCalc - winnings += winningsTemp - netWinnings += netWinningsTemp - reason += reasonTemp + winnings_calc = self._calc_winning(*calc_winnings_parameters) + winnings_temp, net_winnings_temp, reason_temp = winnings_calc + winnings += winnings_temp + net_winnings += net_winnings_temp + reason += reason_temp if hand["split"] >= 3: - _calcWinningsParams = [ + calc_winnings_parameters = [ hand["fourth hand"], - dealerValue, + dealer_value, False, - dealerBlackjack, - dealerBusted + dealer_blackjack, + dealer_busted ] - winningsCalc = self._calcWinnings(*_calcWinningsParams) - winningsTemp, netWinningsTemp, reasonTemp = winningsCalc - winnings += winningsTemp - netWinnings += netWinningsTemp - reason += reasonTemp + winnings_calc = self._calc_winning(*calc_winnings_parameters) + winnings_temp, net_winnings_temp, reason_temp = winnings_calc + winnings += winnings_temp + net_winnings += net_winnings_temp + reason += reason_temp - return winnings, netWinnings, reason + return winnings, net_winnings, reason - def _getHandNumber(self, user: dict, handNumber: int): + def _get_hand_number(self, user: dict, hand_number: int): """ Get the hand with the given number. @@ -488,36 +511,36 @@ class Blackjack(): ------------ user: dict The full hand dict of the user. - handNumber: int + hand_number: int The number of the hand to get. *Returns* --------- hand: dict The hand. - handNumber: int - The same as handNumber, except if the user hasn't + hand_number: int + The same as hand_number, except if the user hasn't split. If the user hasn't split, returns 0. """ hand = None if user["split"] == 0: hand = user - handNumber = 0 + hand_number = 0 else: - if handNumber != 0: - if handNumber == 1: + if hand_number != 0: + if hand_number == 1: hand = user - elif handNumber == 2: + elif hand_number == 2: hand = user["other hand"] - elif handNumber == 3: + elif hand_number == 3: hand = user["third hand"] - elif handNumber == 4: + elif hand_number == 4: hand = user["fourth hand"] - return hand, handNumber + return hand, hand_number - def _isRoundDone(self, game: dict): + def _is_round_done(self, game: dict): """ Find out if the round is done. @@ -528,33 +551,33 @@ class Blackjack(): *Returns* --------- - roundDone: bool + round_done: bool Whether the round is done. """ - roundDone = True + round_done = True for person in game["user hands"].values(): if (not person["hit"]) and (not person["standing"]): - roundDone = False + round_done = False if person["split"] > 0: if not person["other hand"]["hit"]: if not person["other hand"]["standing"]: - roundDone = False + round_done = False if person["split"] > 1: if not person["third hand"]["hit"]: if not person["third hand"]["standing"]: - roundDone = False + round_done = False if person["split"] > 2: if not person["fourth hand"]["hit"]: if not person["fourth hand"]["standing"]: - roundDone = False + round_done = False - return roundDone + return round_done - async def _blackjack_loop(self, channel, gameRound: int, gameID: str): + async def _blackjack_loop(self, channel, game_round: int, game_id: str): """ Run blackjack logic and continue if enough time passes. @@ -562,32 +585,33 @@ class Blackjack(): ------------ channel: guildChannel or DMChannel The channel the game is happening in. - gameRound: int + game_round: int The round to start. - gameID: str + game_id: str The ID of the game. """ - self.bot.log("Loop "+str(gameRound), str(channel.id)) + self.bot.log("Loop "+str(game_round), str(channel.id)) - old_imagePath = f"gwendolyn/resources/games/old_images/blackjack{channel.id}" - with open(old_imagePath, "r") as f: - old_image = await channel.fetch_message(int(f.read())) + old_images_path = "gwendolyn/resources/games/old_images/" + old_image_path = old_images_path + f"blackjack{channel.id}" + with open(old_image_path, "r") as file_pointer: + old_image = await channel.fetch_message(int(file_pointer.read())) - continueData = (self._blackjack_continue(str(channel.id))) - new_message, allStanding, gamedone = continueData + continue_data = (self._blackjack_continue(str(channel.id))) + new_message, all_standing, game_done = continue_data if new_message != "": self.bot.log(new_message, str(channel.id)) await channel.send(new_message) - if not gamedone: + if not game_done: await old_image.delete() - tablesPath = "gwendolyn/resources/games/blackjack_tables/" - file_path = f"{tablesPath}blackjack_table{channel.id}.png" + tables_path = "gwendolyn/resources/games/blackjack_tables/" + file_path = f"{tables_path}blackjack_table{channel.id}.png" old_image = await channel.send(file=discord.File(file_path)) - with open(old_imagePath, "w") as f: - f.write(str(old_image.id)) + with open(old_image_path, "w") as file_pointer: + file_pointer.write(str(old_image.id)) - if allStanding: + if all_standing: await asyncio.sleep(5) else: await asyncio.sleep(120) @@ -596,26 +620,26 @@ class Blackjack(): game = blackjack_games.find_one({"_id": str(channel.id)}) if game is None: - rightRound = False + right_round = False else: - realRound = game["round"] or -1 - realID = game["gameID"] or -1 - rightRound = gameRound == realRound and gameID == realID + real_round = game["round"] or -1 + real_id = game["game_id"] or -1 + right_round = game_round == real_round and game_id == real_id - if rightRound: - if not gamedone: - log_message = f"Loop {gameRound} calling self._blackjack_loop()" + if right_round: + if not game_done: + log_message = f"Loop {game_round} starting a new blackjack loop" self.bot.log(log_message, str(channel.id)) - await self._blackjack_loop(channel, gameRound+1, gameID) + await self._blackjack_loop(channel, game_round+1, game_id) else: new_message = self._blackjack_finish(str(channel.id)) await channel.send(new_message) else: - log_message = f"Ending loop on round {gameRound}" + log_message = f"Ending loop on round {game_round}" self.bot.log(log_message, str(channel.id)) async def hit(self, ctx: discord_slash.context.SlashContext, - handNumber: int = 0): + hand_number: int = 0): """ Hit on a hand. @@ -623,73 +647,73 @@ class Blackjack(): ------------ ctx: discord_slash.context.SlashContext The context of the command. - handNumber: int = 0 + hand_number: int = 0 The number of the hand to hit. """ await self.bot.defer(ctx) channel = str(ctx.channel_id) user = f"#{ctx.author.id}" - roundDone = False + round_done = False blackjack_games = self.bot.database["blackjack games"] game = blackjack_games.find_one({"_id": channel}) if user in game["user hands"]: - userHands = game["user hands"][user] - hand, handNumber = self._getHandNumber(userHands, handNumber) + user_hands = game["user hands"][user] + hand, hand_number = self._get_hand_number(user_hands, hand_number) if hand is None: log_message = "They didn't specify a hand" - sendMessage = "You need to specify a hand" + send_message = "You need to specify a hand" elif game["round"] <= 0: log_message = "They tried to hit on the 0th round" - sendMessage = "You can't hit before you see your cards" + send_message = "You can't hit before you see your cards" elif hand["hit"]: log_message = "They've already hit this round" - sendMessage = "You've already hit this round" + send_message = "You've already hit this round" elif hand["standing"]: log_message = "They're already standing" - sendMessage = "You can't hit when you're standing" + send_message = "You can't hit when you're standing" else: - hand["hand"].append(self._drawCard(channel)) + hand["hand"].append(self._draw_card(channel)) hand["hit"] = True - handValue = self._calcHandValue(hand["hand"]) + hand_value = self._calc_hand_value(hand["hand"]) - if handValue > 21: + if hand_value > 21: hand["busted"] = True - if handNumber == 2: - handPath = f"user hands.{user}.other hand" - elif handNumber == 3: - handPath = f"user hands.{user}.third hand" - elif handNumber == 4: - handPath = f"user hands.{user}.fourth hand" + if hand_number == 2: + hand_path = f"user hands.{user}.other hand" + elif hand_number == 3: + hand_path = f"user hands.{user}.third hand" + elif hand_number == 4: + hand_path = f"user hands.{user}.fourth hand" else: - handPath = f"user hands.{user}" + hand_path = f"user hands.{user}" - gameUpdater = {"$set": {handPath: hand}} - blackjack_games.update_one({"_id": channel}, gameUpdater) + game_updater = {"$set": {hand_path: hand}} + blackjack_games.update_one({"_id": channel}, game_updater) game = blackjack_games.find_one({"_id": channel}) - roundDone = self._isRoundDone(game) + round_done = self._is_round_done(game) - sendMessage = f"{ctx.author.display_name} hit" + send_message = f"{ctx.author.display_name} hit" log_message = "They succeeded" else: log_message = "They tried to hit without being in the game" - sendMessage = "You have to enter the game before you can hit" + send_message = "You have to enter the game before you can hit" - await ctx.send(sendMessage) + await ctx.send(send_message) self.bot.log(log_message) - if roundDone: - gameID = game["gameID"] + if round_done: + game_id = game["game_id"] self.bot.log("Hit calling self._blackjack_loop()", channel) - await self._blackjack_loop(ctx.channel, game["round"]+1, gameID) + await self._blackjack_loop(ctx.channel, game["round"]+1, game_id) async def double(self, ctx: discord_slash.context.SlashContext, - handNumber: int = 0): + hand_number: int = 0): """ Double a hand. @@ -697,86 +721,86 @@ class Blackjack(): ------------ ctx: discord_slash.context.SlashContext The context of the command. - handNumber: int = 0 + hand_number: int = 0 The number of the hand to double. """ await self.bot.defer(ctx) channel = str(ctx.channel_id) user = f"#{ctx.author.id}" - roundDone = False + round_done = False blackjack_games = self.bot.database["blackjack games"] game = blackjack_games.find_one({"_id": channel}) if user in game["user hands"]: - handParams = [game["user hands"][user], handNumber] - hand, handNumber = self._getHandNumber(*handParams) + hand_parameters = [game["user hands"][user], hand_number] + hand, hand_number = self._get_hand_number(*hand_parameters) if hand is None: log_message = "They didn't specify a hand" - sendMessage = "You need to specify a hand" + send_message = "You need to specify a hand" elif game["round"] <= 0: log_message = "They tried to hit on the 0th round" - sendMessage = "You can't hit before you see your cards" + send_message = "You can't hit before you see your cards" elif hand["hit"]: log_message = "They've already hit this round" - sendMessage = "You've already hit this round" + send_message = "You've already hit this round" elif hand["standing"]: log_message = "They're already standing" - sendMessage = "You can't hit when you're standing" + send_message = "You can't hit when you're standing" elif len(hand["hand"]) != 2: log_message = "They tried to double after round 1" - sendMessage = "You can only double on the first round" + send_message = "You can only double on the first round" elif self.bot.money.checkBalance(user) < hand["bet"]: log_message = "They tried to double without being in the game" - sendMessage = "You can't double when you're not in the game" + send_message = "You can't double when you're not in the game" else: bet = hand["bet"] self.bot.money.addMoney(user, -1 * bet) - hand["hand"].append(self._drawCard(channel)) + hand["hand"].append(self._draw_card(channel)) hand["hit"] = True hand["doubled"] = True hand["bet"] += bet - handValue = self._calcHandValue(hand["hand"]) + hand_value = self._calc_hand_value(hand["hand"]) - if handValue > 21: + if hand_value > 21: hand["busted"] = True - if handNumber == 2: - handPath = f"user hands.{user}.other hand" - elif handNumber == 3: - handPath = f"user hands.{user}.third hand" - elif handNumber == 4: - handPath = f"user hands.{user}.fourth hand" + if hand_number == 2: + hand_path = f"user hands.{user}.other hand" + elif hand_number == 3: + hand_path = f"user hands.{user}.third hand" + elif hand_number == 4: + hand_path = f"user hands.{user}.fourth hand" else: - handPath = f"user hands.{user}" + hand_path = f"user hands.{user}" - gameUpdater = {"$set": {handPath: hand}} - blackjack_games.update_one({"_id": channel}, gameUpdater) + game_updater = {"$set": {hand_path: hand}} + blackjack_games.update_one({"_id": channel}, game_updater) game = blackjack_games.find_one({"_id": channel}) - roundDone = self._isRoundDone(game) + round_done = self._is_round_done(game) - sendMessage = self.bot.long_strings["Blackjack double"] + send_message = self.long_strings["Blackjack double"] user_name = self.bot.database_funcs.get_name(user) - sendMessage = sendMessage.format(bet, user_name) + send_message = send_message.format(bet, user_name) log_message = "They succeeded" else: log_message = "They tried to double without being in the game" - sendMessage = "You can't double when you're not in the game" + send_message = "You can't double when you're not in the game" - await ctx.send(sendMessage) + await ctx.send(send_message) self.bot.log(log_message) - if roundDone: - gameID = game["gameID"] + if round_done: + game_id = game["game_id"] self.bot.log("Double calling self._blackjack_loop()", channel) - await self._blackjack_loop(ctx.channel, game["round"]+1, gameID) + await self._blackjack_loop(ctx.channel, game["round"]+1, game_id) async def stand(self, ctx: discord_slash.context.SlashContext, - handNumber: int = 0): + hand_number: int = 0): """ Stand on a hand. @@ -784,67 +808,67 @@ class Blackjack(): ------------ ctx: discord_slash.context.SlashContext The context of the command. - handNumber: int = 0 + hand_number: int = 0 The number of the hand to stand on. """ await self.bot.defer(ctx) channel = str(ctx.channel_id) user = f"#{ctx.author.id}" - roundDone = False + round_done = False blackjack_games = self.bot.database["blackjack games"] game = blackjack_games.find_one({"_id": channel}) if game is not None and user in game["user hands"]: - handParams = [game["user hands"][user], handNumber] - hand, handNumber = self._getHandNumber(*handParams) + hand_parameters = [game["user hands"][user], hand_number] + hand, hand_number = self._get_hand_number(*hand_parameters) if hand is None: - sendMessage = "You need to specify which hand" + send_message = "You need to specify which hand" log_message = "They didn't specify a hand" elif game["round"] <= 0: - sendMessage = "You can't stand before you see your cards" + send_message = "You can't stand before you see your cards" log_message = "They tried to stand on round 0" elif hand["hit"]: - sendMessage = "You've already hit this round" + send_message = "You've already hit this round" log_message = "They'd already hit this round" elif hand["standing"]: - sendMessage = "You're already standing" + send_message = "You're already standing" log_message = "They're already standing" else: hand["standing"] = True - if handNumber == 2: - handPath = f"user hands.{user}.other hand" - elif handNumber == 3: - handPath = f"user hands.{user}.third hand" - elif handNumber == 4: - handPath = f"user hands.{user}.fourth hand" + if hand_number == 2: + hand_path = f"user hands.{user}.other hand" + elif hand_number == 3: + hand_path = f"user hands.{user}.third hand" + elif hand_number == 4: + hand_path = f"user hands.{user}.fourth hand" else: - handPath = f"user hands.{user}" + hand_path = f"user hands.{user}" - gameUpdater = {"$set": {handPath: hand}} - blackjack_games.update_one({"_id": channel}, gameUpdater) + game_updater = {"$set": {hand_path: hand}} + blackjack_games.update_one({"_id": channel}, game_updater) game = blackjack_games.find_one({"_id": channel}) - roundDone = self._isRoundDone(game) + round_done = self._is_round_done(game) - sendMessage = f"{ctx.author.display_name} is standing" + send_message = f"{ctx.author.display_name} is standing" log_message = "They succeeded" else: log_message = "They tried to stand without being in the game" - sendMessage = "You have to enter the game before you can stand" + send_message = "You have to enter the game before you can stand" - await ctx.send(sendMessage) + await ctx.send(send_message) self.bot.log(log_message) - if roundDone: - gameID = game["gameID"] + if round_done: + game_id = game["game_id"] self.bot.log("Stand calling self._blackjack_loop()", channel) - await self._blackjack_loop(ctx.channel, game["round"]+1, gameID) + await self._blackjack_loop(ctx.channel, game["round"]+1, game_id) async def split(self, ctx: discord_slash.context.SlashContext, - handNumber: int = 0): + hand_number: int = 0): """ Split a hand. @@ -852,141 +876,141 @@ class Blackjack(): ------------ ctx: discord_slash.context.SlashContext The context of the command. - handNumber: int = 0 + hand_number: int = 0 The number of the hand to split. """ await self.bot.defer(ctx) channel = str(ctx.channel_id) user = f"#{ctx.author.id}" - roundDone = False - handNumberError = False + round_done = False + hand_number_error = False blackjack_games = self.bot.database["blackjack games"] game = blackjack_games.find_one({"_id": channel}) if game["user hands"][user]["split"] == 0: hand = game["user hands"][user] - newHand = game["user hands"][user]["other hand"] - handNumber = 0 - otherHand = 2 + new_hand = game["user hands"][user]["other hand"] + hand_number = 0 + other_hand = 2 else: - if handNumber == 1: + if hand_number == 1: hand = game["user hands"][user] - elif handNumber == 2: + elif hand_number == 2: hand = game["user hands"][user]["other hand"] - elif handNumber == 3: + elif hand_number == 3: hand = game["user hands"][user]["third hand"] else: - handNumberError = True + hand_number_error = True if game["user hands"][user]["split"] == 1: - newHand = game["user hands"][user]["third hand"] - otherHand = 3 + new_hand = game["user hands"][user]["third hand"] + other_hand = 3 else: - newHand = game["user hands"][user]["fourth hand"] - otherHand = 4 + new_hand = game["user hands"][user]["fourth hand"] + other_hand = 4 - if handNumberError: + if hand_number_error: log_message = "They didn't specify a hand" - sendMessage = "You have to specify the hand you're hitting with" + send_message = "You have to specify the hand you're hitting with" elif game["round"] == 0: log_message = "They tried to split on round 0" - sendMessage = "You can't split before you see your cards" + send_message = "You can't split before you see your cards" elif game["user hands"][user]["split"] > 3: log_message = "They tried to split more than three times" - sendMessage = "You can only split 3 times" + send_message = "You can only split 3 times" elif hand["hit"]: log_message = "They've already hit" - sendMessage = "You've already hit or split this hand." + send_message = "You've already hit or split this hand." elif hand["standing"]: log_message = "They're already standing" - sendMessage = "You're already standing" + send_message = "You're already standing" elif len(hand["hand"]) != 2: log_message = "They tried to split after the first round" - sendMessage = "You can only split on the first round" + send_message = "You can only split on the first round" else: - firstCard = self._calcHandValue([hand["hand"][0]]) - secondCard = self._calcHandValue([hand["hand"][1]]) - if firstCard != secondCard: + first_card = self._calc_hand_value([hand["hand"][0]]) + second_card = self._calc_hand_value([hand["hand"][1]]) + if first_card != second_card: log_message = "They tried to split two different cards" - sendMessage = self.bot.long_strings["Blackjack different cards"] + send_message = self.long_strings["Blackjack different cards"] else: bet = hand["bet"] if self.bot.money.checkBalance(user) < bet: log_message = "They didn't have enough GwendoBucks" - sendMessage = "You don't have enough GwendoBucks" + send_message = "You don't have enough GwendoBucks" else: self.bot.money.addMoney(user, -1 * bet) hand["hit"] = True - newHand["hit"] = True + new_hand["hit"] = True - newHand = { + new_hand = { "hand": [], "bet": 0, "standing": False, "busted": False, "blackjack": False, "hit": True, "doubled": False } - newHand["bet"] = hand["bet"] + new_hand["bet"] = hand["bet"] - newHand["hand"].append(hand["hand"].pop(1)) - newHand["hand"].append(self._drawCard(channel)) - hand["hand"].append(self._drawCard(channel)) + new_hand["hand"].append(hand["hand"].pop(1)) + new_hand["hand"].append(self._draw_card(channel)) + hand["hand"].append(self._draw_card(channel)) hand["hit"] = True - handValue = self._calcHandValue(hand["hand"]) - otherHandValue = self._calcHandValue(newHand["hand"]) - if handValue > 21: + hand_value = self._calc_hand_value(hand["hand"]) + other_hand_value = self._calc_hand_value(new_hand["hand"]) + if hand_value > 21: hand["busted"] = True - elif handValue == 21: + elif hand_value == 21: hand["blackjack"] = True - if otherHandValue > 21: - newHand["busted"] = True - elif otherHandValue == 21: - newHand["blackjack"] = True + if other_hand_value > 21: + new_hand["busted"] = True + elif other_hand_value == 21: + new_hand["blackjack"] = True - if handNumber == 2: - handPath = f"user hands.{user}.other hand" - elif handNumber == 3: - handPath = f"user hands.{user}.third hand" + if hand_number == 2: + hand_path = f"user hands.{user}.other hand" + elif hand_number == 3: + hand_path = f"user hands.{user}.third hand" else: - handPath = f"user hands.{user}" + hand_path = f"user hands.{user}" - gameUpdater = {"$set": {handPath: hand}} - blackjack_games.update_one({"_id": channel}, gameUpdater) + game_updater = {"$set": {hand_path: hand}} + blackjack_games.update_one({"_id": channel}, game_updater) - if otherHand == 3: - otherHandPath = f"user hands.{user}.third hand" - elif otherHand == 4: - otherHandPath = f"user hands.{user}.fourth hand" + if other_hand == 3: + other_hand_path = f"user hands.{user}.third hand" + elif other_hand == 4: + other_hand_path = f"user hands.{user}.fourth hand" else: - otherHandPath = f"user hands.{user}.other hand" + other_hand_path = f"user hands.{user}.other hand" - gameUpdater = {"$set": {otherHandPath: newHand}} - blackjack_games.update_one({"_id": channel}, gameUpdater) + game_updater = {"$set": {other_hand_path: new_hand}} + blackjack_games.update_one({"_id": channel}, game_updater) - splitUpdater = {"$inc": {"user hands."+user+".split": 1}} - blackjack_games.update_one({"_id": channel}, splitUpdater) + split_updater = {"$inc": {"user hands."+user+".split": 1}} + blackjack_games.update_one({"_id": channel}, split_updater) game = blackjack_games.find_one({"_id": channel}) - roundDone = self._isRoundDone(game) + round_done = self._is_round_done(game) - sendMessage = self.bot.long_strings["Blackjack split"] + send_message = self.long_strings["Blackjack split"] user_name = self.bot.database_funcs.get_name(user) - sendMessage = sendMessage.format(user_name) + send_message = send_message.format(user_name) log_message = "They succeeded" - await ctx.send(sendMessage) + await ctx.send(send_message) self.bot.log(log_message) - if roundDone: - gameID = game["gameID"] + if round_done: + game_id = game["game_id"] self.bot.log("Stand calling self._blackjack_loop()", channel) - await self._blackjack_loop(ctx.channel, game["round"]+1, gameID) + await self._blackjack_loop(ctx.channel, game["round"]+1, game_id) - async def enterGame(self, ctx: discord_slash.context.SlashContext, - bet: int): + async def enter_game(self, ctx: discord_slash.context.SlashContext, + bet: int): """ Enter the blackjack game. @@ -1007,51 +1031,48 @@ class Blackjack(): self.bot.log(f"{user_name} is trying to join the Blackjack game") if game is None: - sendMessage = "There is no game going on in this channel" - log_message = sendMessage + send_message = "There is no game going on in this channel" + log_message = send_message elif user in game["user hands"]: - sendMessage = "You're already in the game!" + send_message = "You're already in the game!" log_message = "They're already in the game" elif len(game["user hands"]) >= 5: - sendMessage = "There can't be more than 5 players in a game" + send_message = "There can't be more than 5 players in a game" log_message = "There were already 5 players in the game" elif game["round"] != 0: - sendMessage = "The table is no longer taking bets" + send_message = "The table is no longer taking bets" log_message = "They tried to join after the game begun" elif bet < 0: - sendMessage = "You can't bet a negative amount" + send_message = "You can't bet a negative amount" log_message = "They tried to bet a negative amount" elif self.bot.money.checkBalance(user) < bet: - sendMessage = "You don't have enough GwendoBucks" + send_message = "You don't have enough GwendoBucks" log_message = "They didn't have enough GwendoBucks" else: self.bot.money.addMoney(user, -1 * bet) - playerHand = [self._drawCard(channel) for _ in range(2)] + player_hand = [self._draw_card(channel) for _ in range(2)] - handValue = self._calcHandValue(playerHand) + hand_value = self._calc_hand_value(player_hand) - if handValue == 21: - blackjack_hand = True - else: - blackjack_hand = False + blackjack_hand = (hand_value == 21) - newHand = { - "hand": playerHand, "bet": bet, "standing": False, + new_hand = { + "hand": player_hand, "bet": bet, "standing": False, "busted": False, "blackjack": blackjack_hand, "hit": True, "doubled": False, "split": 0, "other hand": {}, "third hand": {}, "fourth hand": {} } - function = {"$set": {f"user hands.{user}": newHand}} + function = {"$set": {f"user hands.{user}": new_hand}} collection.update_one({"_id": channel}, function) - enterGameText = "entered the game with a bet of" - betText = f"{bet} GwendoBucks" - sendMessage = f"{user_name} {enterGameText} {betText}" - log_message = sendMessage + enter_game_text = "entered the game with a bet of" + bet_text = f"{bet} GwendoBucks" + send_message = f"{user_name} {enter_game_text} {bet_text}" + log_message = send_message self.bot.log(log_message) - await ctx.send(sendMessage) + await ctx.send(send_message) async def start(self, ctx: discord_slash.context.SlashContext): """ @@ -1064,17 +1085,17 @@ class Blackjack(): """ await self.bot.defer(ctx) channel = str(ctx.channel_id) - blackjack_minCards = 50 + blackjack_min_cards = 50 self.bot.log("Starting blackjack game") await ctx.send("Starting a new game of blackjack") - cardsLeft = 0 + cards_left = 0 cards = self.bot.database["blackjack cards"].find_one({"_id": channel}) if cards is not None: - cardsLeft = len(cards["cards"]) + cards_left = len(cards["cards"]) # Shuffles if not enough cards - if cardsLeft < blackjack_minCards: + if cards_left < blackjack_min_cards: self._blackjack_shuffle(channel) self.bot.log("Shuffling the blackjack deck...", channel) await ctx.channel.send("Shuffling the deck...") @@ -1082,68 +1103,69 @@ class Blackjack(): game = self.bot.database["blackjack games"].find_one({"_id": channel}) self.bot.log("Trying to start a blackjack game in "+channel) - gameStarted = False + game_started = False if game is None: - dealerHand = [self._drawCard(channel), self._drawCard(channel)] - gameID = datetime.datetime.now().strftime('%Y%m%d%H%M%S') + dealer_hand = [self._draw_card(channel), self._draw_card(channel)] + game_id = datetime.datetime.now().strftime('%Y%m%d%H%M%S') - newGame = { - "_id": channel, "dealer hand": dealerHand, + new_game = { + "_id": channel, "dealer hand": dealer_hand, "dealer busted": False, "dealer blackjack": False, "user hands": {}, "all standing": False, "round": 0, - "gameID": gameID + "game_id": game_id } - if self._calcHandValue(dealerHand) == 21: - newGame["dealer blackjack"] = True + if self._calc_hand_value(dealer_hand) == 21: + new_game["dealer blackjack"] = True - self.bot.database["blackjack games"].insert_one(newGame) + self.bot.database["blackjack games"].insert_one(new_game) - tableImagesPath = "gwendolyn/resources/games/blackjack_tables/" - emptyTableImagePath = f"gwendolyn/resources/games/blackjack_table.png" - newTableImagePath = f"{tableImagesPath}blackjack_table{channel}.png" - copyfile(emptyTableImagePath, newTableImagePath) + table_images_path = "gwendolyn/resources/games/blackjack_tables/" + empty_table_image = "gwendolyn/resources/games/blackjack_table.png" + new_table_image = f"{table_images_path}blackjack_table{channel}.png" + copyfile(empty_table_image, new_table_image) - gameStarted = True + game_started = True - if gameStarted: - sendMessage = self.bot.long_strings["Blackjack started"] - await ctx.channel.send(sendMessage) + if game_started: + send_message = self.long_strings["Blackjack started"] + await ctx.channel.send(send_message) - tableImagesPath = "gwendolyn/resources/games/blackjack_tables/" - file_path = f"{tableImagesPath}blackjack_table{channel}.png" + table_images_path = "gwendolyn/resources/games/blackjack_tables/" + file_path = f"{table_images_path}blackjack_table{channel}.png" old_image = await ctx.channel.send(file=discord.File(file_path)) + old_images_path = "gwendolyn/resources/games/old_images/blackjack" - with open("gwendolyn/resources/games/old_images/blackjack"+channel, "w") as f: - f.write(str(old_image.id)) + with open(old_images_path+channel, "w") as file_pointer: + file_pointer.write(str(old_image.id)) await asyncio.sleep(30) - gamedone = False + game_done = False blackjack_games = self.bot.database["blackjack games"] game = blackjack_games.find_one({"_id": channel}) if len(game["user hands"]) == 0: - gamedone = True - sendMessage = "No one entered the game. Ending the game." - await ctx.channel.send(sendMessage) + game_done = True + send_message = "No one entered the game. Ending the game." + await ctx.channel.send(send_message) - gameID = game["gameID"] + game_id = game["game_id"] # Loop of game rounds - if not gamedone: + if not game_done: self.bot.log("start() calling _blackjack_loop()", channel) - await self._blackjack_loop(ctx.channel, 1, gameID) + await self._blackjack_loop(ctx.channel, 1, game_id) else: new_message = self._blackjack_finish(channel) await ctx.channel.send(new_message) else: - sendMessage = self.bot.long_strings["Blackjack going on"] - await ctx.channel.send(sendMessage) + send_message = self.long_strings["Blackjack going on"] + await ctx.channel.send(send_message) self.bot.log("There was already a game going on") async def hilo(self, ctx: discord_slash.context.SlashContext): @@ -1187,15 +1209,15 @@ class Blackjack(): The context of the command. """ channel = ctx.channel_id - cardsLeft = 0 + cards_left = 0 blackjack_games = self.bot.database["blackjack cards"] cards = blackjack_games.find_one({"_id": str(channel)}) if cards is not None: - cardsLeft = len(cards["cards"]) + cards_left = len(cards["cards"]) - decksLeft = round(cardsLeft/52, 1) - sendMessage = f"Cards left:\n{cardsLeft} cards, {decksLeft} decks" - await ctx.send(sendMessage, hidden=True) + decks_left = round(cards_left/52, 1) + send_message = f"Cards left:\n{cards_left} cards, {decks_left} decks" + await ctx.send(send_message, hidden=True) class DrawBlackjack(): @@ -1204,7 +1226,7 @@ class DrawBlackjack(): *Methods* --------- - drawImage(channel: str) + draw_image(channel: str) *Attributes* ------------ @@ -1215,14 +1237,18 @@ class DrawBlackjack(): PLACEMENT: list The order to place the user hands in. """ + # pylint: disable=too-few-public-methods def __init__(self, bot): """Initialize the class.""" self.bot = bot + # pylint: disable=invalid-name self.BORDER = 100 + self.SMALLBORDER = int(self.BORDER/3.5) self.PLACEMENT = [2, 1, 3, 0, 4] + # pylint: enable=invalid-name - def drawImage(self, channel: str): + def draw_image(self, channel: str): """ Draw the table image. @@ -1234,139 +1260,140 @@ class DrawBlackjack(): self.bot.log("Drawing blackjack table", channel) game = self.bot.database["blackjack games"].find_one({"_id": channel}) - font = ImageFont.truetype('gwendolyn/resources/fonts/futura-bold.ttf', 50) - smallFont = ImageFont.truetype('gwendolyn/resources/fonts/futura-bold.ttf', 40) - self.SMALLBORDER = int(self.BORDER/3.5) + fonts_path = "gwendolyn/resources/fonts/" + font = ImageFont.truetype(fonts_path+'futura-bold.ttf', 50) + small_font = ImageFont.truetype(fonts_path+'futura-bold.ttf', 40) table = Image.open("gwendolyn/resources/games/blackjack_table.png") - textImage = ImageDraw.Draw(table) + text_image = ImageDraw.Draw(table) hands = game["user hands"] - dealerBusted = game["dealer busted"] - dealerBlackjack = game["dealer blackjack"] + dealer_busted = game["dealer busted"] + dealer_blackjack = game["dealer blackjack"] if not game["all standing"]: - handParams = [ + hand_parameters = [ game["dealer hand"], True, False, False ] else: - handParams = [ + hand_parameters = [ game["dealer hand"], False, - dealerBusted, - dealerBlackjack + dealer_busted, + dealer_blackjack ] - dealerHand = self._drawHand(*handParams) + dealer_hand = self._draw_hand(*hand_parameters) - pastePosition = (800-self.SMALLBORDER, 20-self.SMALLBORDER) - table.paste(dealerHand, pastePosition, dealerHand) + paste_position = (800-self.SMALLBORDER, 20-self.SMALLBORDER) + table.paste(dealer_hand, paste_position, dealer_hand) - for x in range(len(hands)): - key, value = list(hands.items())[x] + for i in range(len(hands)): + key, value = list(hands.items())[i] key = self.bot.database_funcs.get_name(key) - handParams = [ + hand_parameters = [ value["hand"], False, value["busted"], value["blackjack"] ] - userHand = self._drawHand(*handParams) - positionX = 32-self.SMALLBORDER+(384*self.PLACEMENT[x]) + user_hand = self._draw_hand(*hand_parameters) + position_x = 32-self.SMALLBORDER+(384*self.PLACEMENT[i]) if value["split"] >= 1: - handParamsTwo = [ + hand_parameters_two = [ value["other hand"]["hand"], False, value["other hand"]["busted"], value["other hand"]["blackjack"] ] - userOtherHand = self._drawHand(*handParamsTwo) + user_other_hand = self._draw_hand(*hand_parameters_two) if value["split"] >= 2: - handParamsThree = [ + hand_parameters_three = [ value["third hand"]["hand"], False, value["third hand"]["busted"], value["third hand"]["blackjack"] ] - userThirdHand = self._drawHand(*handParamsThree) + user_third_hand = self._draw_hand(*hand_parameters_three) if value["split"] >= 3: - handParamsFour = [ + hand_parameters_four = [ value["fourth hand"]["hand"], False, value["fourth hand"]["busted"], value["fourth hand"]["blackjack"] ] - userFourthHand = self._drawHand(*handParamsFour) + user_fourth_hand = self._draw_hand(*hand_parameters_four) if value["split"] == 3: - positionOne = (positionX, 280-self.SMALLBORDER) - positionTwo = (positionX, 420-self.SMALLBORDER) - positionThree = (positionX, 560-self.SMALLBORDER) - positionFour = (positionX, 700-self.SMALLBORDER) + position_one = (position_x, 280-self.SMALLBORDER) + position_two = (position_x, 420-self.SMALLBORDER) + position_three = (position_x, 560-self.SMALLBORDER) + position_four = (position_x, 700-self.SMALLBORDER) - table.paste(userHand, positionOne, userHand) - table.paste(userOtherHand, positionTwo, userOtherHand) - table.paste(userThirdHand, positionThree, userThirdHand) - table.paste(userFourthHand, positionFour, userFourthHand) + table.paste(user_hand, position_one, user_hand) + table.paste(user_other_hand, position_two, user_other_hand) + table.paste(user_third_hand, position_three, user_third_hand) + table.paste(user_fourth_hand, position_four, user_fourth_hand) elif value["split"] == 2: - positionOne = (positionX, 420-self.SMALLBORDER) - positionTwo = (positionX, 560-self.SMALLBORDER) - positionThree = (positionX, 700-self.SMALLBORDER) + position_one = (position_x, 420-self.SMALLBORDER) + position_two = (position_x, 560-self.SMALLBORDER) + position_three = (position_x, 700-self.SMALLBORDER) - table.paste(userHand, positionOne, userHand) - table.paste(userOtherHand, positionTwo, userOtherHand) - table.paste(userThirdHand, positionThree, userThirdHand) + table.paste(user_hand, position_one, user_hand) + table.paste(user_other_hand, position_two, user_other_hand) + table.paste(user_third_hand, position_three, user_third_hand) elif value["split"] == 1: - positionOne = (positionX, 560-self.SMALLBORDER) - positionTwo = (positionX, 700-self.SMALLBORDER) + position_one = (position_x, 560-self.SMALLBORDER) + position_two = (position_x, 700-self.SMALLBORDER) - table.paste(userHand, positionOne, userHand) - table.paste(userOtherHand, positionTwo, userOtherHand) + table.paste(user_hand, position_one, user_hand) + table.paste(user_other_hand, position_two, user_other_hand) else: - positionOne = (positionX, 680-self.SMALLBORDER) - table.paste(userHand, positionOne, userHand) + position_one = (position_x, 680-self.SMALLBORDER) + table.paste(user_hand, position_one, user_hand) - textWidth = font.getsize(key)[0] - textX = 32+(384*self.PLACEMENT[x])+117-int(textWidth/2) + text_width = font.getsize(key)[0] + text_x = 32+(384*self.PLACEMENT[i])+117-int(text_width/2) black = (0, 0, 0) white = (255, 255, 255) - if textWidth < 360: + if text_width < 360: # Black shadow behind and slightly below white text - textImage.text((textX-3, 1010-3), key, fill=black, font=font) - textImage.text((textX+3, 1010-3), key, fill=black, font=font) - textImage.text((textX-3, 1010+3), key, fill=black, font=font) - textImage.text((textX+3, 1010+3), key, fill=black, font=font) - textImage.text((textX, 1005), key, fill=white, font=font) + text_image.text((text_x-3, 1010-3), key, fill=black, font=font) + text_image.text((text_x+3, 1010-3), key, fill=black, font=font) + text_image.text((text_x-3, 1010+3), key, fill=black, font=font) + text_image.text((text_x+3, 1010+3), key, fill=black, font=font) + text_image.text((text_x, 1005), key, fill=white, font=font) else: - textWidth = smallFont.getsize(key)[0] - shadows = [ - (textX-2, 1020-2), - (textX+2, 1020-2), - (textX-2, 1020+2), - (textX+2, 1020+2) + text_width = small_font.getsize(key)[0] + positions = [ + (text_x-2, 1020-2), + (text_x+2, 1020-2), + (text_x-2, 1020+2), + (text_x+2, 1020+2), + (text_x, 1015) ] - textImage.text(shadows[0], key, fill=black, font=smallFont) - textImage.text(shadows[1], key, fill=black, font=smallFont) - textImage.text(shadows[2], key, fill=black, font=smallFont) - textImage.text(shadows[3], key, fill=black, font=smallFont) - textImage.text((textX, 1015), key, fill=white, font=smallFont) + text_image.text(positions[0], key, fill=black, font=small_font) + text_image.text(positions[1], key, fill=black, font=small_font) + text_image.text(positions[2], key, fill=black, font=small_font) + text_image.text(positions[3], key, fill=black, font=small_font) + text_image.text(positions[4], key, fill=white, font=small_font) self.bot.log("Saving table image") - tableImagesPath = "gwendolyn/resources/games/blackjack_tables/" - tableImagePath = f"{tableImagesPath}blackjack_table{channel}.png" - table.save(tableImagePath) + table_images_path = "gwendolyn/resources/games/blackjack_tables/" + table_image_path = f"{table_images_path}blackjack_table{channel}.png" + table.save(table_image_path) return - def _drawHand(self, hand: dict, dealer: bool, busted: bool, - blackjack: bool): + def _draw_hand(self, hand: dict, dealer: bool, busted: bool, + blackjack: bool): """ Draw a hand. @@ -1388,82 +1415,85 @@ class DrawBlackjack(): The image of the hand. """ self.bot.log("Drawing hand {hand}, {busted}, {blackjack}") - font = ImageFont.truetype('gwendolyn/resources/fonts/futura-bold.ttf', 200) - font2 = ImageFont.truetype('gwendolyn/resources/fonts/futura-bold.ttf', 120) + fonts_path = "gwendolyn/resources/fonts/" + font = ImageFont.truetype(fonts_path+'futura-bold.ttf', 200) + font_two = ImageFont.truetype(fonts_path+'futura-bold.ttf', 120) length = len(hand) - backgroundWidth = (self.BORDER*2)+691+(125*(length-1)) - backgroundSize = (backgroundWidth, (self.BORDER*2)+1065) - background = Image.new("RGBA", backgroundSize, (0, 0, 0, 0)) - textImage = ImageDraw.Draw(background) - cardY = self.BORDER+self.PLACEMENT[1] + background_width = (self.BORDER*2)+691+(125*(length-1)) + background_size = (background_width, (self.BORDER*2)+1065) + background = Image.new("RGBA", background_size, (0, 0, 0, 0)) + text_image = ImageDraw.Draw(background) + card_y = self.BORDER+self.PLACEMENT[1] + cards_path = "gwendolyn/resources/games/cards/" if dealer: - img = Image.open("gwendolyn/resources/games/cards/"+hand[0].upper()+".png") - cardPosition = (self.BORDER+self.PLACEMENT[0], cardY) - background.paste(img, cardPosition, img) + img = Image.open(cards_path+hand[0].upper()+".png") + card_position = (self.BORDER+self.PLACEMENT[0], card_y) + background.paste(img, card_position, img) img = Image.open("gwendolyn/resources/games/cards/red_back.png") - cardPosition = (125+self.BORDER+self.PLACEMENT[0], cardY) - background.paste(img, cardPosition, img) + card_position = (125+self.BORDER+self.PLACEMENT[0], card_y) + background.paste(img, card_position, img) else: - for x in range(length): - cardPath = f"gwendolyn/resources/games/cards/{hand[x].upper()}.png" - img = Image.open(cardPath) - cardPosition = (self.BORDER+(x*125)+self.PLACEMENT[0], cardY) - background.paste(img, cardPosition, img) + for i in range(length): + card_path = cards_path + f"{hand[i].upper()}.png" + img = Image.open(card_path) + card_position = (self.BORDER+(i*125)+self.PLACEMENT[0], card_y) + background.paste(img, card_position, img) - w, h = background.size - textHeight = 290+self.BORDER + width, height = background.size + text_height = 290+self.BORDER white = (255, 255, 255) black = (0, 0, 0) red = (255, 50, 50) gold = (155, 123, 0) if busted: - textWidth = font.getsize("BUSTED")[0] - textX = int(w/2)-int(textWidth/2) + text_width = font.getsize("BUSTED")[0] + text_x = int(width/2)-int(text_width/2) positions = [ - (textX-10, textHeight+20-10), - (textX+10, textHeight+20-10), - (textX-10, textHeight+20+10), - (textX+10, textHeight+20+10), - (textX-5, textHeight-5), - (textX+5, textHeight-5), - (textX-5, textHeight+5), - (textX+5, textHeight+5), - (textX, textHeight) + (text_x-10, text_height+20-10), + (text_x+10, text_height+20-10), + (text_x-10, text_height+20+10), + (text_x+10, text_height+20+10), + (text_x-5, text_height-5), + (text_x+5, text_height-5), + (text_x-5, text_height+5), + (text_x+5, text_height+5), + (text_x, text_height) ] - textImage.text(positions[0], "BUSTED", fill=black, font=font) - textImage.text(positions[1], "BUSTED", fill=black, font=font) - textImage.text(positions[2], "BUSTED", fill=black, font=font) - textImage.text(positions[3], "BUSTED", fill=black, font=font) - textImage.text(positions[4], "BUSTED", fill=white, font=font) - textImage.text(positions[5], "BUSTED", fill=white, font=font) - textImage.text(positions[6], "BUSTED", fill=white, font=font) - textImage.text(positions[7], "BUSTED", fill=white, font=font) - textImage.text(positions[8], "BUSTED", fill=red, font=font) + text_image.text(positions[0], "BUSTED", fill=black, font=font) + text_image.text(positions[1], "BUSTED", fill=black, font=font) + text_image.text(positions[2], "BUSTED", fill=black, font=font) + text_image.text(positions[3], "BUSTED", fill=black, font=font) + text_image.text(positions[4], "BUSTED", fill=white, font=font) + text_image.text(positions[5], "BUSTED", fill=white, font=font) + text_image.text(positions[6], "BUSTED", fill=white, font=font) + text_image.text(positions[7], "BUSTED", fill=white, font=font) + text_image.text(positions[8], "BUSTED", fill=red, font=font) elif blackjack: - textWidth = font2.getsize("BLACKJACK")[0] - textX = int(w/2)-int(textWidth/2) + text_width = font_two.getsize("BLACKJACK")[0] + text_x = int(width/2)-int(text_width/2) positions = [ - (textX-6, textHeight+20-6), - (textX+6, textHeight+20-6), - (textX-6, textHeight+20+6), - (textX+6, textHeight+20+6), - (textX-3, textHeight-3), - (textX+3, textHeight-3), - (textX-3, textHeight+3), - (textX+3, textHeight+3), - (textX, textHeight) + (text_x-6, text_height+20-6), + (text_x+6, text_height+20-6), + (text_x-6, text_height+20+6), + (text_x+6, text_height+20+6), + (text_x-3, text_height-3), + (text_x+3, text_height-3), + (text_x-3, text_height+3), + (text_x+3, text_height+3), + (text_x, text_height) ] - textImage.text(positions[0], "BLACKJACK", fill=black, font=font2) - textImage.text(positions[1], "BLACKJACK", fill=black, font=font2) - textImage.text(positions[2], "BLACKJACK", fill=black, font=font2) - textImage.text(positions[3], "BLACKJACK", fill=black, font=font2) - textImage.text(positions[4], "BLACKJACK", fill=white, font=font2) - textImage.text(positions[5], "BLACKJACK", fill=white, font=font2) - textImage.text(positions[6], "BLACKJACK", fill=white, font=font2) - textImage.text(positions[7], "BLACKJACK", fill=white, font=font2) - textImage.text(positions[8], "BLACKJACK", fill=gold, font=font2) + text = "BLACKJACK" + text_image.text(positions[0], text, fill=black, font=font_two) + text_image.text(positions[1], text, fill=black, font=font_two) + text_image.text(positions[2], text, fill=black, font=font_two) + text_image.text(positions[3], text, fill=black, font=font_two) + text_image.text(positions[4], text, fill=white, font=font_two) + text_image.text(positions[5], text, fill=white, font=font_two) + text_image.text(positions[6], text, fill=white, font=font_two) + text_image.text(positions[7], text, fill=white, font=font_two) + text_image.text(positions[8], text, fill=gold, font=font_two) - resizedSize = (int(w/3.5), int(h/3.5)) - return background.resize(resizedSize, resample=Image.BILINEAR) + resized_size = (int(width/3.5), int(height/3.5)) + return background.resize(resized_size, resample=Image.BILINEAR) diff --git a/gwendolyn/funcs/games/connect_four.py b/gwendolyn/funcs/games/connect_four.py index 430b38c..7dc0322 100644 --- a/gwendolyn/funcs/games/connect_four.py +++ b/gwendolyn/funcs/games/connect_four.py @@ -9,13 +9,13 @@ Contains the classes that deal with playing and drawing connect four. import random # Used to shuffle players and by the ai to pick from # similar options import copy # Used to deepcopy boards +from typing import Union # Used for typehints import math # Used for math.inf import discord # Used for typehints, discord.file and to check whether # the opponent in ConnectFour.start is a discord.User from PIL import Image, ImageDraw, ImageFont # Used by DrawConnectFour() from discord_slash.context import SlashContext # Used for typehints -from typing import Union # Used for typehints ROWCOUNT = 6 COLUMNCOUNT = 7 @@ -28,7 +28,7 @@ class ConnectFour(): *Methods* --------- start(ctx: SlashContext, opponent: Union[int, Discord.User]) - placePiece(ctx: Union[SlashContext, discord.Message], + place_piece(ctx: Union[SlashContext, discord.Message], user: int, column: int) surrender(ctx: SlashContext) """ @@ -37,6 +37,8 @@ class ConnectFour(): """Initialize the class.""" self.bot = bot self.draw = DrawConnectFour(bot) + self.database_funcs = self.bot.database_funcs + # pylint: disable=invalid-name self.AISCORES = { "middle": 3, "two in a row": 10, @@ -48,6 +50,7 @@ class ConnectFour(): "avoid losing": 100 } self.REACTIONS = ["1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣"] + # pylint: enable=invalid-name async def start(self, ctx: SlashContext, opponent: Union[int, discord.User]): @@ -69,53 +72,52 @@ class ConnectFour(): channel = str(ctx.channel_id) game = self.bot.database["connect 4 games"].find_one({"_id": channel}) - startedGame = False - canStart = True + started_game = False + can_start = True if game is not None: - sendMessage = self.bot.long_strings["Connect 4 going on"] + send_message = self.bot.long_strings["Connect 4 going on"] log_message = "There was already a game going on" - canStart = False - elif type(opponent) == int: + can_start = False + elif isinstance(opponent, int): # Opponent is Gwendolyn if opponent in range(1, 6): difficulty = int(opponent) - diffText = f" with difficulty {difficulty}" + difficulty_text = f" with difficulty {difficulty}" opponent = f"#{self.bot.user.id}" else: - sendMessage = "Difficulty doesn't exist" + send_message = "Difficulty doesn't exist" log_message = "They challenged a difficulty that doesn't exist" - canStart = False - elif type(opponent) == discord.User: + can_start = False + elif isinstance(opponent, discord.User): if opponent.bot: # User has challenged a bot if opponent == self.bot.user: # It was Gwendolyn difficulty = 3 - diffText = f" with difficulty {difficulty}" + difficulty_text = f" with difficulty {difficulty}" opponent = f"#{self.bot.user.id}" else: - sendMessage = "You can't challenge a bot!" + send_message = "You can't challenge a bot!" log_message = "They tried to challenge a bot" - canStart = False + can_start = False else: # Opponent is another player if ctx.author != opponent: opponent = f"#{opponent.id}" difficulty = 5 - diffText = "" + difficulty_text = "" else: - sendMessage = "You can't play against yourself" + send_message = "You can't play against yourself" log_message = "They tried to play against themself" - canStart = False + can_start = False - if canStart: - x, y = COLUMNCOUNT, ROWCOUNT - board = [[0 for _ in range(x)] for _ in range(y)] + if can_start: + board = [[0 for _ in range(COLUMNCOUNT)] for _ in range(ROWCOUNT)] players = [user, opponent] random.shuffle(players) - newGame = { + new_game = { "_id": channel, "board": board, "winner": 0, @@ -126,42 +128,45 @@ class ConnectFour(): "difficulty": difficulty } - self.bot.database["connect 4 games"].insert_one(newGame) + self.bot.database["connect 4 games"].insert_one(new_game) - self.draw.drawImage(channel) + self.draw.draw_image(channel) - gwendoTurn = (players[0] == f"#{self.bot.user.id}") - startedGame = True + gwendolyn_turn = (players[0] == f"#{self.bot.user.id}") + started_game = True - opponentName = self.bot.database_funcs.get_name(opponent) - turnName = self.bot.database_funcs.get_name(players[0]) + opponent_name = self.database_funcs.get_name(opponent) + turn_name = self.database_funcs.get_name(players[0]) - startedText = f"Started game against {opponentName}{diffText}." - turnText = f"It's {turnName}'s turn" - sendMessage = f"{startedText} {turnText}" + started_text = "Started game against {}{}.".format( + opponent_name, + difficulty_text + ) + turn_text = f"It's {turn_name}'s turn" + send_message = f"{started_text} {turn_text}" log_message = "They started a game" self.bot.log(log_message) - await ctx.send(sendMessage) + await ctx.send(send_message) # Sets the whole game in motion - if startedGame: - boardsPath = "gwendolyn/resources/games/connect4Boards/" - file_path = f"{boardsPath}board{ctx.channel_id}.png" + if started_game: + boards_path = "gwendolyn/resources/games/connect_four_boards/" + file_path = f"{boards_path}board{ctx.channel_id}.png" old_image = await ctx.channel.send(file=discord.File(file_path)) - old_imagesPath = "gwendolyn/resources/games/old_images/" - old_imagePath = f"{old_imagesPath}connect_four{ctx.channel_id}" - with open(old_imagePath, "w") as f: - f.write(str(old_image.id)) + old_images_path = "gwendolyn/resources/games/old_images/" + old_image_path = f"{old_images_path}connect_four{ctx.channel_id}" + with open(old_image_path, "w") as file_pointer: + file_pointer.write(str(old_image.id)) - if gwendoTurn: - await self._connect_fourAI(ctx) + if gwendolyn_turn: + await self._connect_four_ai(ctx) else: for reaction in self.REACTIONS: await old_image.add_reaction(reaction) - async def placePiece(self, ctx: Union[SlashContext, discord.Message], + async def place_piece(self, ctx: Union[SlashContext, discord.Message], user: int, column: int): """ Place a piece on the board. @@ -180,90 +185,95 @@ class ConnectFour(): The column the player is placing the piece in. """ channel = str(ctx.channel.id) - connect4Games = self.bot.database["connect 4 games"] - game = connect4Games.find_one({"_id": channel}) - playerNumber = game["players"].index(user)+1 - user_name = self.bot.database_funcs.get_name(user) - placedPiece = False + connect_four_games = self.bot.database["connect 4 games"] + game = connect_four_games.find_one({"_id": channel}) + player_number = game["players"].index(user)+1 + user_name = self.database_funcs.get_name(user) + placed_piece = False if game is None: - sendMessage = "There's no game in this channel" + send_message = "There's no game in this channel" log_message = "There was no game in the channel" else: board = game["board"] - board = self._placeOnBoard(board, playerNumber, column) + board = self._place_on_board(board, player_number, column) if board is None: - sendMessage = "There isn't any room in that column" + send_message = "There isn't any room in that column" log_message = "There wasn't any room in the column" else: updater = {"$set": {"board": board}} - connect4Games.update_one({"_id": channel}, updater) + connect_four_games.update_one({"_id": channel}, updater) turn = (game["turn"]+1) % 2 updater = {"$set": {"turn": turn}} - connect4Games.update_one({"_id": channel}, updater) + connect_four_games.update_one({"_id": channel}, updater) self.bot.log("Checking for win") - won, winDirection, winCoordinates = self._isWon(board) + won, win_direction, win_coordinates = self._is_won(board) if won != 0: - gameWon = True + game_won = True updater = {"$set": {"winner": won}} - connect4Games.update_one({"_id": channel}, updater) - updater = {"$set": {"win direction": winDirection}} - connect4Games.update_one({"_id": channel}, updater) - updater = {"$set": {"win coordinates": winCoordinates}} - connect4Games.update_one({"_id": channel}, updater) + connect_four_games.update_one({"_id": channel}, updater) + updater = {"$set": {"win direction": win_direction}} + connect_four_games.update_one({"_id": channel}, updater) + updater = {"$set": {"win coordinates": win_coordinates}} + connect_four_games.update_one({"_id": channel}, updater) - sendMessage = "{} placed a piece in column {} and won. " - sendMessage = sendMessage.format(user_name, column+1) + send_message = "{} placed a piece in column {} and won. " + send_message = send_message.format(user_name, column+1) log_message = f"{user_name} won" - winAmount = int(game["difficulty"])**2+5 + win_amount = int(game["difficulty"])**2+5 if game["players"][won-1] != f"#{self.bot.user.id}": - sendMessage += "Adding {} GwendoBucks to their account" - sendMessage = sendMessage.format(winAmount) + send_message += "Adding {} GwendoBucks to their account" + send_message = send_message.format(win_amount) elif 0 not in board[0]: - gameWon = True - sendMessage = "It's a draw!" + game_won = True + send_message = "It's a draw!" log_message = "The game ended in a draw" else: - gameWon = False - otherUserId = game["players"][turn] - otherUserName = self.bot.database_funcs.get_name(otherUserId) - sendMessage = self.bot.long_strings["Connect 4 placed"] - formatParams = [user_name, column+1, otherUserName] - sendMessage = sendMessage.format(*formatParams) + game_won = False + other_user_id = game["players"][turn] + other_user_name = self.database_funcs.get_name( + other_user_id) + send_message = self.bot.long_strings["Connect 4 placed"] + format_parameters = [user_name, column+1, other_user_name] + send_message = send_message.format(*format_parameters) log_message = "They placed the piece" - gwendoTurn = (game["players"][turn] == f"#{self.bot.user.id}") + gwendolyn_turn = ( + game["players"][turn] == f"#{self.bot.user.id}") - placedPiece = True + placed_piece = True - await ctx.channel.send(sendMessage) + await ctx.channel.send(send_message) self.bot.log(log_message) - if placedPiece: - self.draw.drawImage(channel) + if placed_piece: + self.draw.draw_image(channel) - old_imagePath = f"gwendolyn/resources/games/old_images/connect_four{channel}" - with open(old_imagePath, "r") as f: - old_image = await ctx.channel.fetch_message(int(f.read())) + old_images_path = "gwendolyn/resources/games/old_images/" + old_image_path = old_images_path + f"connect_four{channel}" + with open(old_image_path, "r") as file_pointer: + old_image_id = int(file_pointer.read()) + old_image = await ctx.channel.fetch_message(old_image_id) if old_image is not None: await old_image.delete() else: self.bot.log("The old image was already deleted") - file_path = f"gwendolyn/resources/games/connect4Boards/board{channel}.png" + boards_path = "gwendolyn/resources/games/connect_four_boards/" + file_path = boards_path + f"board{channel}.png" old_image = await ctx.channel.send(file=discord.File(file_path)) - if gameWon: - self._endGame(channel) + if game_won: + self._end_game(channel) else: - with open(old_imagePath, "w") as f: - f.write(str(old_image.id)) - if gwendoTurn: - await self._connect_fourAI(ctx) + with open(old_image_path, "w") as file_pointer: + file_pointer.write(str(old_image.id)) + if gwendolyn_turn: + await self._connect_four_ai(ctx) else: for reaction in self.REACTIONS: await old_image.add_reaction(reaction) @@ -282,37 +292,39 @@ class ConnectFour(): 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.database_funcs.get_name(winnerID) + loser_index = game["players"].index(f"#{ctx.author.id}") + winner_index = (loser_index+1) % 2 + winner_id = game["players"][winner_index] + winner_name = self.database_funcs.get_name(winner_id) - sendMessage = f"{ctx.author.display_name} surrenders." - sendMessage += f" This means {winnerName} is the winner." - if winnerID != f"#{self.bot.user.id}": + send_message = f"{ctx.author.display_name} surrenders." + send_message += f" This means {winner_name} is the winner." + if winner_id != f"#{self.bot.user.id}": difficulty = int(game["difficulty"]) reward = difficulty**2 + 5 - sendMessage += f" Adding {reward} to their account" + send_message += f" Adding {reward} to their account" - await ctx.send(sendMessage) - old_imagePath = f"gwendolyn/resources/games/old_images/connect_four{channel}" - with open(old_imagePath, "r") as f: - old_image = await ctx.channel.fetch_message(int(f.read())) + await ctx.send(send_message) + old_images_path = "gwendolyn/resources/games/old_images/" + old_image_path = old_images_path + f"connect_four{channel}" + with open(old_image_path, "r") as file_pointer: + old_image_id = int(file_pointer.read()) + old_image = await ctx.channel.fetch_message(old_image_id) if old_image is not None: await old_image.delete() else: self.bot.log("The old image was already deleted") - self._endGame(channel) + self._end_game(channel) else: await ctx.send("You can't surrender when you're not a player") - def _placeOnBoard(self, board: dict, player: int, column: int): + def _place_on_board(self, board: dict, player: int, column: int): """ Place a piece in a given board. - This differs from placePiece() by not having anything to do + This differs from place_piece() by not having anything to do with any actual game. It just places the piece in a game dict. *Parameters* @@ -329,21 +341,21 @@ class ConnectFour(): board: dict The board with the placed piece. """ - placementX, placementY = -1, column + placement_x, placement_y = -1, column - for x, line in list(enumerate(board))[::-1]: + for i, line in list(enumerate(board))[::-1]: if line[column] == 0: - placementX = x + placement_x = i break - board[placementX][placementY] = player + board[placement_x][placement_y] = player - if placementX == -1: + if placement_x == -1: return None else: return board - def _endGame(self, channel: str): + def _end_game(self, channel: str): game = self.bot.database["connect 4 games"].find_one({"_id": channel}) winner = game["winner"] @@ -353,87 +365,87 @@ class ConnectFour(): reward = difficulty**2 + 5 self.bot.money.addMoney(game["players"][winner-1], reward) - self.bot.database_funcs.delete_game("connect 4 games", channel) + self.database_funcs.delete_game("connect 4 games", channel) - def _isWon(self, board: dict): + def _is_won(self, board: dict): won = 0 - winDirection = "" - winCoordinates = [0, 0] + win_direction = "" + win_coordinates = [0, 0] for row in range(ROWCOUNT): for place in range(COLUMNCOUNT): if won == 0: - piecePlayer = board[row][place] - if piecePlayer != 0: + piece_player = board[row][place] + if piece_player != 0: # Checks horizontal if place <= COLUMNCOUNT-4: - pieceOne = board[row][place+1] - pieceTwo = board[row][place+2] - pieceThree = board[row][place+3] + piece_one = board[row][place+1] + piece_two = board[row][place+2] + piece_three = board[row][place+3] - pieces = [pieceOne, pieceTwo, pieceThree] + pieces = [piece_one, piece_two, piece_three] else: pieces = [0] - if all(x == piecePlayer for x in pieces): - won = piecePlayer - winDirection = "h" - winCoordinates = [row, place] + if all(x == piece_player for x in pieces): + won = piece_player + win_direction = "h" + win_coordinates = [row, place] # Checks vertical if row <= ROWCOUNT-4: - pieceOne = board[row+1][place] - pieceTwo = board[row+2][place] - pieceThree = board[row+3][place] + piece_one = board[row+1][place] + piece_two = board[row+2][place] + piece_three = board[row+3][place] - pieces = [pieceOne, pieceTwo, pieceThree] + pieces = [piece_one, piece_two, piece_three] else: pieces = [0] - if all(x == piecePlayer for x in pieces): - won = piecePlayer - winDirection = "v" - winCoordinates = [row, place] + if all(x == piece_player for x in pieces): + won = piece_player + win_direction = "v" + win_coordinates = [row, place] # Checks right diagonal - goodRow = (row <= ROWCOUNT-4) - goodColumn = (place <= COLUMNCOUNT-4) - if goodRow and goodColumn: - pieceOne = board[row+1][place+1] - pieceTwo = board[row+2][place+2] - pieceThree = board[row+3][place+3] + good_row = (row <= ROWCOUNT-4) + good_column = (place <= COLUMNCOUNT-4) + if good_row and good_column: + piece_one = board[row+1][place+1] + piece_two = board[row+2][place+2] + piece_three = board[row+3][place+3] - pieces = [pieceOne, pieceTwo, pieceThree] + pieces = [piece_one, piece_two, piece_three] else: pieces = [0] - if all(x == piecePlayer for x in pieces): - won = piecePlayer - winDirection = "r" - winCoordinates = [row, place] + if all(x == piece_player for x in pieces): + won = piece_player + win_direction = "r" + win_coordinates = [row, place] # Checks left diagonal if row <= ROWCOUNT-4 and place >= 3: - pieceOne = board[row+1][place-1] - pieceTwo = board[row+2][place-2] - pieceThree = board[row+3][place-3] + piece_one = board[row+1][place-1] + piece_two = board[row+2][place-2] + piece_three = board[row+3][place-3] - pieces = [pieceOne, pieceTwo, pieceThree] + pieces = [piece_one, piece_two, piece_three] else: pieces = [0] - if all(x == piecePlayer for x in pieces): - won = piecePlayer - winDirection = "l" - winCoordinates = [row, place] + if all(i == piece_player for i in pieces): + won = piece_player + win_direction = "l" + win_coordinates = [row, place] - return won, winDirection, winCoordinates + return won, win_direction, win_coordinates - async def _connect_fourAI(self, ctx: SlashContext): - def outOfRange(possibleScores: list): - allowedRange = max(possibleScores)*(1-0.1) - moreThanOne = len(possibleScores) != 1 - return (min(possibleScores) <= allowedRange) and moreThanOne + async def _connect_four_ai(self, ctx: SlashContext): + def out_of_range(possible_scores: list): + allowed_range = max(possible_scores)*(1-0.1) + more_than_one = len(possible_scores) != 1 + return (min(possible_scores) <= allowed_range) and more_than_one channel = str(ctx.channel.id) @@ -446,11 +458,11 @@ class ConnectFour(): scores = [-math.inf for _ in range(COLUMNCOUNT)] for column in range(COLUMNCOUNT): - testBoard = copy.deepcopy(board) - testBoard = self._placeOnBoard(testBoard, player, column) - if testBoard is not None: - _minimaxParams = [ - testBoard, + test_board = copy.deepcopy(board) + test_board = self._place_on_board(test_board, player, column) + if test_board is not None: + minimax_parameters = [ + test_board, difficulty, player % 2+1, player, @@ -458,79 +470,79 @@ class ConnectFour(): math.inf, False ] - scores[column] = await self._minimax(*_minimaxParams) + scores[column] = await self._minimax(*minimax_parameters) self.bot.log(f"Best score for column {column} is {scores[column]}") - possibleScores = scores.copy() + possible_scores = scores.copy() - while outOfRange(possibleScores): - possibleScores.remove(min(possibleScores)) + while out_of_range(possible_scores): + possible_scores.remove(min(possible_scores)) - highestScore = random.choice(possibleScores) + highest_score = random.choice(possible_scores) - bestColumns = [i for i, x in enumerate(scores) if x == highestScore] - placement = random.choice(bestColumns) + best_columns = [i for i, x in enumerate(scores) if x == highest_score] + placement = random.choice(best_columns) - await self.placePiece(ctx, f"#{self.bot.user.id}", placement) + await self.place_piece(ctx, f"#{self.bot.user.id}", placement) - def _AICalcPoints(self, board: dict, player: int): + def _ai_calc_points(self, board: dict, player: int): score = 0 - otherPlayer = player % 2+1 + other_player = player % 2+1 # Adds points for middle placement # Checks horizontal for row in range(ROWCOUNT): if board[row][3] == player: score += self.AISCORES["middle"] - rowArray = [int(i) for i in list(board[row])] + row_array = [int(i) for i in list(board[row])] for place in range(COLUMNCOUNT-3): - window = rowArray[place:place+4] - score += self._evaluateWindow(window, player, otherPlayer) + window = row_array[place:place+4] + score += self._evaluate_window(window, player, other_player) # Checks Vertical for column in range(COLUMNCOUNT): - columnArray = [int(i[column]) for i in list(board)] + column_array = [int(i[column]) for i in list(board)] for place in range(ROWCOUNT-3): - window = columnArray[place:place+4] - score += self._evaluateWindow(window, player, otherPlayer) + window = column_array[place:place+4] + score += self._evaluate_window(window, player, other_player) # Checks right diagonal for row in range(ROWCOUNT-3): for place in range(COLUMNCOUNT-3): - pieceOne = board[row][place] - pieceTwo = board[row+1][place+1] - pieceThree = board[row+2][place+2] - pieceFour = board[row+3][place+3] + piece_one = board[row][place] + piece_two = board[row+1][place+1] + piece_three = board[row+2][place+2] + piece_four = board[row+3][place+3] - window = [pieceOne, pieceTwo, pieceThree, pieceFour] - score += self._evaluateWindow(window, player, otherPlayer) + window = [piece_one, piece_two, piece_three, piece_four] + score += self._evaluate_window(window, player, other_player) for place in range(3, COLUMNCOUNT): - pieceOne = board[row][place] - pieceTwo = board[row+1][place-1] - pieceThree = board[row+2][place-2] - pieceFour = board[row+3][place-3] + piece_one = board[row][place] + piece_two = board[row+1][place-1] + piece_three = board[row+2][place-2] + piece_four = board[row+3][place-3] - window = [pieceOne, pieceTwo, pieceThree, pieceFour] - score += self._evaluateWindow(window, player, otherPlayer) + window = [piece_one, piece_two, piece_three, piece_four] + score += self._evaluate_window(window, player, other_player) return score - def _evaluateWindow(self, window: list, player: int, otherPlayer: int): + def _evaluate_window(self, window: list, player: int, other_player: int): if window.count(player) == 4: return self.AISCORES["win"] elif window.count(player) == 3 and window.count(0) == 1: return self.AISCORES["three in a row"] elif window.count(player) == 2 and window.count(0) == 2: return self.AISCORES["two in a row"] - elif window.count(otherPlayer) == 4: + elif window.count(other_player) == 4: return self.AISCORES["enemy win"] else: return 0 async def _minimax(self, board: dict, depth: int, player: int, - originalPlayer: int, alpha: int, beta: int, - maximizingPlayer: bool): + original_player: int, alpha: int, beta: int, + maximizing_player: bool): """ Evaluate the minimax value of a board. @@ -543,12 +555,12 @@ class ConnectFour(): is returned. player: int The player who can place a piece. - originalPlayer: int + original_player: int The AI player. alpha, beta: int Used with alpha/beta pruning, in order to prune options that will never be picked. - maximizingPlayer: bool + maximizing_player: bool Whether the current player is the one trying to maximize their score. @@ -558,27 +570,27 @@ class ConnectFour(): The minimax value of the board. """ terminal = 0 not in board[0] - points = self._AICalcPoints(board, originalPlayer) + points = self._ai_calc_points(board, original_player) # The depth is how many moves ahead the computer checks. This # value is the difficulty. if depth == 0 or terminal or (points > 5000 or points < -6000): return points - if maximizingPlayer: + if maximizing_player: value = -math.inf for column in range(0, COLUMNCOUNT): - testBoard = copy.deepcopy(board) - testBoard = self._placeOnBoard(testBoard, player, column) - if testBoard is not None: - _minimaxParams = [ - testBoard, + test_board = copy.deepcopy(board) + test_board = self._place_on_board(test_board, player, column) + if test_board is not None: + minimax_parameters = [ + test_board, depth-1, (player % 2)+1, - originalPlayer, + original_player, alpha, beta, False ] - evaluation = await self._minimax(*_minimaxParams) + evaluation = await self._minimax(*minimax_parameters) if evaluation < -9000: evaluation += self.AISCORES["avoid losing"] value = max(value, evaluation) @@ -589,19 +601,19 @@ class ConnectFour(): else: value = math.inf for column in range(0, COLUMNCOUNT): - testBoard = copy.deepcopy(board) - testBoard = self._placeOnBoard(testBoard, player, column) - if testBoard is not None: - _minimaxParams = [ - testBoard, + test_board = copy.deepcopy(board) + test_board = self._place_on_board(test_board, player, column) + if test_board is not None: + minimax_parameters = [ + test_board, depth-1, (player % 2)+1, - originalPlayer, + original_player, alpha, beta, True ] - evaluation = await self._minimax(*_minimaxParams) + evaluation = await self._minimax(*minimax_parameters) if evaluation < -9000: evaluation += self.AISCORES["avoid losing"] value = min(value, evaluation) @@ -617,7 +629,7 @@ class DrawConnectFour(): *Methods* --------- - drawImage(channel: str) + draw_image(channel: str) *Attributes* ------------ @@ -665,13 +677,17 @@ class DrawConnectFour(): PIECESTARTX, PIECESTARTY: int Where the top left piece should be placed. WINBARWHITE: RGBA tuple - White, but with the alpha set to winbarAlpha. + White, but with the alpha set to win_bar_alpha. """ def __init__(self, bot): """Initialize the class.""" self.bot = bot + self.database_funcs = self.bot.database_funcs + win_bar_alpha = 160 + font_path = "gwendolyn/resources/fonts/futura-bold.ttf" + # pylint: disable=invalid-name self.BORDER = 40 self.GRIDBORDER = 40 self.CORNERSIZE = 300 @@ -691,29 +707,29 @@ class DrawConnectFour(): self.PLAYER1COLOR = (254, 74, 73) self.PLAYER2COLOR = (254, 215, 102) self.BOARDCOLOR = (42, 183, 202) - winbarAlpha = 160 self.WINBARCOLOR = (250, 250, 250, 255) self.PIECESIZE = 300 - fontPath = "gwendolyn/resources/fonts/futura-bold.ttf" + # pylint: enable=invalid-name - self.FONT = ImageFont.truetype(fontPath, self.TEXTSIZE) + board_width = self.WIDTH-(2*(self.BORDER+self.GRIDBORDER)) + board_height = self.HEIGHT-(2*(self.BORDER+self.GRIDBORDER)) + board_size = [board_width, board_height] + piece_width = board_size[0]//COLUMNCOUNT + piece_height = board_size[1]//ROWCOUNT + piece_start = (self.BORDER+self.GRIDBORDER)-(self.PIECESIZE//2) - boardWidth = self.WIDTH-(2*(self.BORDER+self.GRIDBORDER)) - boardHeight = self.HEIGHT-(2*(self.BORDER+self.GRIDBORDER)) - boardSize = [boardWidth, boardHeight] + # pylint: disable=invalid-name + self.FONT = ImageFont.truetype(font_path, self.TEXTSIZE) + self.PIECEGRIDSIZE = [piece_width, piece_height] - pieceWidth = boardSize[0]//COLUMNCOUNT - pieceHeight = boardSize[1]//ROWCOUNT - self.PIECEGRIDSIZE = [pieceWidth, pieceHeight] + self.PIECESTARTX = piece_start+(self.PIECEGRIDSIZE[0]//2) + self.PIECESTARTY = piece_start+(self.PIECEGRIDSIZE[1]//2) - pieceStart = (self.BORDER+self.GRIDBORDER)-(self.PIECESIZE//2) - self.PIECESTARTX = pieceStart+(self.PIECEGRIDSIZE[0]//2) - self.PIECESTARTY = pieceStart+(self.PIECEGRIDSIZE[1]//2) - - self.WINBARWHITE = (255, 255, 255, winbarAlpha) + self.WINBARWHITE = (255, 255, 255, win_bar_alpha) + # pylint: enable=invalid-name # Draws the whole thing - def drawImage(self, channel: str): + def draw_image(self, channel: str): """ Draw an image of the connect four board. @@ -727,32 +743,33 @@ class DrawConnectFour(): board = game["board"] - imageSize = (self.WIDTH, self.HEIGHT+self.BOTTOMBORDER) - background = Image.new("RGB", imageSize, self.BACKGROUNDCOLOR) - d = ImageDraw.Draw(background, "RGBA") + image_size = (self.WIDTH, self.HEIGHT+self.BOTTOMBORDER) + background = Image.new("RGB", image_size, self.BACKGROUNDCOLOR) + drawer = ImageDraw.Draw(background, "RGBA") - self._drawBoard(d) + self._draw_board(drawer) - self._drawPieces(d, board) + self._draw_pieces(drawer, board) if game["winner"] != 0: - self._drawWin(background, game) + self._draw_win(background, game) - self._drawFooter(d, game) + self._draw_footer(drawer, game) - background.save(f"gwendolyn/resources/games/connect4Boards/board{channel}.png") + boards_path = "gwendolyn/resources/games/connect_four_boards/" + background.save(boards_path + f"board{channel}.png") - def _drawBoard(self, d: ImageDraw): + def _draw_board(self, drawer: ImageDraw): # This whole part was the easiest way to make a rectangle with # rounded corners and an outline - drawParams = { + draw_parameters = { "fill": self.BOARDCOLOR, "outline": self.BOARDOUTLINECOLOR, "width": self.BOARDOUTLINESIZE } # - Corners -: - cornerPositions = [ + corner_positions = [ [ ( self.BORDER, @@ -788,13 +805,13 @@ class DrawConnectFour(): ] ] - d.ellipse(cornerPositions[0], **drawParams) - d.ellipse(cornerPositions[1], **drawParams) - d.ellipse(cornerPositions[2], **drawParams) - d.ellipse(cornerPositions[3], **drawParams) + drawer.ellipse(corner_positions[0], **draw_parameters) + drawer.ellipse(corner_positions[1], **draw_parameters) + drawer.ellipse(corner_positions[2], **draw_parameters) + drawer.ellipse(corner_positions[3], **draw_parameters) # - Rectangle -: - rectanglePositions = [ + rectangle_positions = [ [ ( self.BORDER+(self.CORNERSIZE//2), @@ -813,11 +830,11 @@ class DrawConnectFour(): ) ] ] - d.rectangle(rectanglePositions[0], **drawParams) - d.rectangle(rectanglePositions[1], **drawParams) + drawer.rectangle(rectangle_positions[0], **draw_parameters) + drawer.rectangle(rectangle_positions[1], **draw_parameters) # - Removing outline on the inside -: - cleanUpPositions = [ + clean_up_positions = [ [ ( self.BORDER+(self.CORNERSIZE//2), @@ -844,43 +861,43 @@ class DrawConnectFour(): ) ] ] - d.rectangle(cleanUpPositions[0], fill=self.BOARDCOLOR) - d.rectangle(cleanUpPositions[1], fill=self.BOARDCOLOR) - d.rectangle(cleanUpPositions[2], fill=self.BOARDCOLOR) + drawer.rectangle(clean_up_positions[0], fill=self.BOARDCOLOR) + drawer.rectangle(clean_up_positions[1], fill=self.BOARDCOLOR) + drawer.rectangle(clean_up_positions[2], fill=self.BOARDCOLOR) - def _drawPieces(self, d: ImageDraw, board: list): - for x, line in enumerate(board): - for y, piece in enumerate(line): + def _draw_pieces(self, drawer: ImageDraw, board: list): + for piece_x, line in enumerate(board): + for piece_y, piece in enumerate(line): if piece == 1: - pieceColor = self.PLAYER1COLOR - outlineWidth = self.PIECEOUTLINESIZE - outlineColor = self.PIECEOUTLINECOLOR + piece_color = self.PLAYER1COLOR + outline_width = self.PIECEOUTLINESIZE + outline_color = self.PIECEOUTLINECOLOR elif piece == 2: - pieceColor = self.PLAYER2COLOR - outlineWidth = self.PIECEOUTLINESIZE - outlineColor = self.PIECEOUTLINECOLOR + piece_color = self.PLAYER2COLOR + outline_width = self.PIECEOUTLINESIZE + outline_color = self.PIECEOUTLINECOLOR else: - pieceColor = self.BACKGROUNDCOLOR - outlineWidth = self.EMPTYOUTLINESIZE - outlineColor = self.EMPTYOUTLINECOLOR + piece_color = self.BACKGROUNDCOLOR + outline_width = self.EMPTYOUTLINESIZE + outline_color = self.EMPTYOUTLINECOLOR - startX = self.PIECESTARTX + self.PIECEGRIDSIZE[0]*y - startY = self.PIECESTARTY + self.PIECEGRIDSIZE[1]*x + start_x = self.PIECESTARTX + self.PIECEGRIDSIZE[0]*piece_y + start_y = self.PIECESTARTY + self.PIECEGRIDSIZE[1]*piece_x position = [ - (startX, startY), - (startX+self.PIECESIZE, startY+self.PIECESIZE) + (start_x, start_y), + (start_x+self.PIECESIZE, start_y+self.PIECESIZE) ] - pieceParams = { - "fill": pieceColor, - "outline": outlineColor, - "width": outlineWidth + piece_parameters = { + "fill": piece_color, + "outline": outline_color, + "width": outline_width } - d.ellipse(position, **pieceParams) + drawer.ellipse(position, **piece_parameters) - def _drawWin(self, background: Image, game: dict): + def _draw_win(self, background: Image, game: dict): """ Draw the bar that shows the winning pieces. @@ -893,19 +910,23 @@ class DrawConnectFour(): """ coordinates = game["win coordinates"] start = self.BORDER + self.GRIDBORDER - startX = start + self.PIECEGRIDSIZE[0]*coordinates[1] - startY = start + self.PIECEGRIDSIZE[1]*coordinates[0] - a = (self.PIECEGRIDSIZE[0]*4-self.GRIDBORDER-self.BORDER)**2 - b = (self.PIECEGRIDSIZE[1]*4-self.GRIDBORDER-self.BORDER)**2 - diagonalLength = (math.sqrt(a+b))/self.PIECEGRIDSIZE[0] - sizeRatio = self.PIECEGRIDSIZE[1]/self.PIECEGRIDSIZE[0] - diagonalAngle = math.degrees(math.atan(sizeRatio)) + start_x = start + self.PIECEGRIDSIZE[0]*coordinates[1] + start_y = start + self.PIECEGRIDSIZE[1]*coordinates[0] + diagonal_length = ( + (math.sqrt( + (self.PIECEGRIDSIZE[0]*4-self.GRIDBORDER-self.BORDER)**2+ + (self.PIECEGRIDSIZE[1]*4-self.GRIDBORDER-self.BORDER)**2 + ))/ + self.PIECEGRIDSIZE[0] + ) + size_ratio = self.PIECEGRIDSIZE[1]/self.PIECEGRIDSIZE[0] + diagonal_angle = math.degrees(math.atan(size_ratio)) if game["win direction"] == "h": - imageSize = (self.PIECEGRIDSIZE[0]*4, self.PIECEGRIDSIZE[1]) - winBar = Image.new("RGBA", imageSize, (0, 0, 0, 0)) - winD = ImageDraw.Draw(winBar) - drawPositions = [ + image_size = (self.PIECEGRIDSIZE[0]*4, self.PIECEGRIDSIZE[1]) + win_bar = Image.new("RGBA", image_size, (0, 0, 0, 0)) + win_drawer = ImageDraw.Draw(win_bar) + draw_position = [ [ (0, 0), (self.PIECEGRIDSIZE[0], self.PIECEGRIDSIZE[1]) @@ -917,15 +938,15 @@ class DrawConnectFour(): (int(self.PIECEGRIDSIZE[0]*3.5), self.PIECEGRIDSIZE[1]) ] ] - winD.ellipse(drawPositions[0], fill=self.WINBARWHITE) - winD.ellipse(drawPositions[1], fill=self.WINBARWHITE) - winD.rectangle(drawPositions[2], fill=self.WINBARWHITE) + win_drawer.ellipse(draw_position[0], fill=self.WINBARWHITE) + win_drawer.ellipse(draw_position[1], fill=self.WINBARWHITE) + win_drawer.rectangle(draw_position[2], fill=self.WINBARWHITE) elif game["win direction"] == "v": - imageSize = (self.PIECEGRIDSIZE[0], self.PIECEGRIDSIZE[1]*4) - winBar = Image.new("RGBA", imageSize, (0, 0, 0, 0)) - winD = ImageDraw.Draw(winBar) - drawPositions = [ + image_size = (self.PIECEGRIDSIZE[0], self.PIECEGRIDSIZE[1]*4) + win_bar = Image.new("RGBA", image_size, (0, 0, 0, 0)) + win_drawer = ImageDraw.Draw(win_bar) + draw_position = [ [ (0, 0), (self.PIECEGRIDSIZE[0], self.PIECEGRIDSIZE[1]) @@ -937,16 +958,16 @@ class DrawConnectFour(): (self.PIECEGRIDSIZE[0], int(self.PIECEGRIDSIZE[1]*3.5)) ] ] - winD.ellipse(drawPositions[0], fill=self.WINBARWHITE) - winD.ellipse(drawPositions[1], fill=self.WINBARWHITE) - winD.rectangle(drawPositions[2], fill=self.WINBARWHITE) + win_drawer.ellipse(draw_position[0], fill=self.WINBARWHITE) + win_drawer.ellipse(draw_position[1], fill=self.WINBARWHITE) + win_drawer.rectangle(draw_position[2], fill=self.WINBARWHITE) elif game["win direction"] == "r": - imageWidth = int(self.PIECEGRIDSIZE[0]*diagonalLength) - imageSize = (imageWidth, self.PIECEGRIDSIZE[1]) - winBar = Image.new("RGBA", imageSize, (0, 0, 0, 0)) - winD = ImageDraw.Draw(winBar) - drawPositions = [ + image_width = int(self.PIECEGRIDSIZE[0]*diagonal_length) + image_size = (image_width, self.PIECEGRIDSIZE[1]) + win_bar = Image.new("RGBA", image_size, (0, 0, 0, 0)) + win_drawer = ImageDraw.Draw(win_bar) + draw_position = [ [ ( 0, @@ -957,10 +978,10 @@ class DrawConnectFour(): ) ], [ ( - (self.PIECEGRIDSIZE[0]*(diagonalLength-1)), + (self.PIECEGRIDSIZE[0]*(diagonal_length-1)), 0 ), ( - self.PIECEGRIDSIZE[0]*diagonalLength, + self.PIECEGRIDSIZE[0]*diagonal_length, self.PIECEGRIDSIZE[1] ) ], [ @@ -968,33 +989,33 @@ class DrawConnectFour(): int(self.PIECEGRIDSIZE[0]*0.5), 0 ), ( - int(self.PIECEGRIDSIZE[0]*(diagonalLength-0.5)), + int(self.PIECEGRIDSIZE[0]*(diagonal_length-0.5)), self.PIECEGRIDSIZE[1] ) ] ] - winD.ellipse(drawPositions[0], fill=self.WINBARWHITE) - winD.ellipse(drawPositions[1], fill=self.WINBARWHITE) - winD.rectangle(drawPositions[2], fill=self.WINBARWHITE) - winBar = winBar.rotate(-diagonalAngle, expand=1) - startX -= 90 - startY -= 100 + win_drawer.ellipse(draw_position[0], fill=self.WINBARWHITE) + win_drawer.ellipse(draw_position[1], fill=self.WINBARWHITE) + win_drawer.rectangle(draw_position[2], fill=self.WINBARWHITE) + win_bar = win_bar.rotate(-diagonal_angle, expand=1) + start_x -= 90 + start_y -= 100 elif game["win direction"] == "l": - imageWidth = int(self.PIECEGRIDSIZE[0]*diagonalLength) - imageSize = (imageWidth, self.PIECEGRIDSIZE[1]) - winBar = Image.new("RGBA", imageSize, (0, 0, 0, 0)) - winD = ImageDraw.Draw(winBar) - drawPositions = [ + image_width = int(self.PIECEGRIDSIZE[0]*diagonal_length) + image_size = (image_width, self.PIECEGRIDSIZE[1]) + win_bar = Image.new("RGBA", image_size, (0, 0, 0, 0)) + win_drawer = ImageDraw.Draw(win_bar) + draw_position = [ [ (0, 0), (self.PIECEGRIDSIZE[0], self.PIECEGRIDSIZE[1]) ], [ ( - (self.PIECEGRIDSIZE[0]*(diagonalLength-1)), + (self.PIECEGRIDSIZE[0]*(diagonal_length-1)), 0 ), ( - self.PIECEGRIDSIZE[0]*diagonalLength, + self.PIECEGRIDSIZE[0]*diagonal_length, self.PIECEGRIDSIZE[1] ) ], [ @@ -1002,67 +1023,77 @@ class DrawConnectFour(): int(self.PIECEGRIDSIZE[0]*0.5), 0 ), ( - int(self.PIECEGRIDSIZE[0]*(diagonalLength-0.5)), + int(self.PIECEGRIDSIZE[0]*(diagonal_length-0.5)), self.PIECEGRIDSIZE[1] ) ] ] - winD.ellipse(drawPositions[0], fill=self.WINBARWHITE) - winD.ellipse(drawPositions[1], fill=self.WINBARWHITE) - winD.rectangle(drawPositions[2], fill=self.WINBARWHITE) - winBar = winBar.rotate(diagonalAngle, expand=1) - startX -= self.PIECEGRIDSIZE[0]*3 + 90 - startY -= self.GRIDBORDER + 60 + win_drawer.ellipse(draw_position[0], fill=self.WINBARWHITE) + win_drawer.ellipse(draw_position[1], fill=self.WINBARWHITE) + win_drawer.rectangle(draw_position[2], fill=self.WINBARWHITE) + win_bar = win_bar.rotate(diagonal_angle, expand=1) + start_x -= self.PIECEGRIDSIZE[0]*3 + 90 + start_y -= self.GRIDBORDER + 60 - mask = winBar.copy() + mask = win_bar.copy() - winBarImage = Image.new("RGBA", mask.size, color=self.WINBARCOLOR) - background.paste(winBarImage, (startX, startY), mask) + win_bar_image = Image.new("RGBA", mask.size, color=self.WINBARCOLOR) + background.paste(win_bar_image, (start_x, start_y), mask) - def _drawFooter(self, d: ImageDraw, game: dict): + def _draw_footer(self, drawer: ImageDraw, game: dict): if game["players"][0] == "Gwendolyn": player1 = "Gwendolyn" else: - player1 = self.bot.database_funcs.get_name(game["players"][0]) + player1 = self.database_funcs.get_name(game["players"][0]) if game["players"][1] == "Gwendolyn": player2 = "Gwendolyn" else: - player2 = self.bot.database_funcs.get_name(game["players"][1]) + player2 = self.database_funcs.get_name(game["players"][1]) - exampleHeight = self.HEIGHT - self.BORDER - exampleHeight += (self.BOTTOMBORDER+self.BORDER)//2 - self.TEXTSIZE//2 - textWidth = self.FONT.getsize(player2)[0] - exampleRPadding = self.BORDER+self.TEXTSIZE+textWidth+self.TEXTPADDING - circlePositions = [ + circle_height = self.HEIGHT - self.BORDER + circle_height += (self.BOTTOMBORDER+self.BORDER)//2 - self.TEXTSIZE//2 + text_width = self.FONT.getsize(player2)[0] + circle_right_padding = ( + self.BORDER+self.TEXTSIZE+text_width+self.TEXTPADDING + ) + circle_positions = [ [ ( self.BORDER, - exampleHeight + circle_height ), ( self.BORDER+self.TEXTSIZE, - exampleHeight+self.TEXTSIZE + circle_height+self.TEXTSIZE ) ], [ ( - self.WIDTH-exampleRPadding, - exampleHeight + self.WIDTH-circle_right_padding, + circle_height ), ( - self.WIDTH-self.BORDER-textWidth-self.TEXTPADDING, - exampleHeight+self.TEXTSIZE + self.WIDTH-self.BORDER-text_width-self.TEXTPADDING, + circle_height+self.TEXTSIZE ) ] ] - circleParams = { + circle_parameters = { "outline": self.BOARDOUTLINECOLOR, "width": 3 } - d.ellipse(circlePositions[0], fill=self.PLAYER1COLOR, **circleParams) - d.ellipse(circlePositions[1], fill=self.PLAYER2COLOR, **circleParams) + drawer.ellipse( + circle_positions[0], + fill=self.PLAYER1COLOR, + **circle_parameters + ) + drawer.ellipse( + circle_positions[1], + fill=self.PLAYER2COLOR, + **circle_parameters + ) - textPositions = [ - (self.BORDER+self.TEXTSIZE+self.TEXTPADDING, exampleHeight), - (self.WIDTH-self.BORDER-textWidth, exampleHeight) + text_positions = [ + (self.BORDER+self.TEXTSIZE+self.TEXTPADDING, circle_height), + (self.WIDTH-self.BORDER-text_width, circle_height) ] - d.text(textPositions[0], player1, font=self.FONT, fill=(0, 0, 0)) - d.text(textPositions[1], player2, font=self.FONT, fill=(0, 0, 0)) + drawer.text(text_positions[0], player1, font=self.FONT, fill=(0, 0, 0)) + drawer.text(text_positions[1], player2, font=self.FONT, fill=(0, 0, 0)) diff --git a/gwendolyn/funcs/games/hangman.py b/gwendolyn/funcs/games/hangman.py index 46e01da..0f247e4 100644 --- a/gwendolyn/funcs/games/hangman.py +++ b/gwendolyn/funcs/games/hangman.py @@ -8,12 +8,12 @@ Deals with commands and logic for hangman games. DrawHangman() Draws the image shown to the player. """ -import requests # Used for getting the word in Hangman.start() import datetime # Used for generating the game id import string # string.ascii_uppercase used -import discord # Used for discord.file and type hints import math # Used by DrawHangman(), mainly for drawing circles import random # Used to draw poorly +import requests # Used for getting the word in Hangman.start() +import discord # Used for discord.file and type hints from discord_slash.context import SlashContext # Used for typehints from PIL import ImageDraw, Image, ImageFont # Used to draw the image @@ -38,16 +38,16 @@ class Hangman(): ------------ draw: DrawHangman The DrawHangman used to draw the hangman image. - APIURL: str + API_url: str The url to get the words from. APIPARAMS: dict The parameters to pass to every api call. """ self.__bot = bot self.__draw = DrawHangman(bot) - self.__APIURL = "https://api.wordnik.com/v4/words.json/randomWords?" - apiKey = self.__bot.credentials["wordnik_key"] - self.__APIPARAMS = { + self.__API_url = "https://api.wordnik.com/v4/words.json/randomWords?" # pylint: disable=invalid-name + api_key = self.__bot.credentials["wordnik_key"] + self.__APIPARAMS = { # pylint: disable=invalid-name "hasDictionaryDef": True, "minCorpusCount": 5000, "maxCorpusCount": -1, @@ -56,7 +56,7 @@ class Hangman(): "minLength": 3, "maxLength": 11, "limit": 1, - "api_key": apiKey + "api_key": api_key } async def start(self, ctx: SlashContext): @@ -73,59 +73,60 @@ class Hangman(): user = f"#{ctx.author.id}" game = self.__bot.database["hangman games"].find_one({"_id": channel}) user_name = self.__bot.database_funcs.get_name(user) - startedGame = False + started_game = False if game is None: word = "-" while "-" in word or "." in word: - response = requests.get(self.__APIURL, params=self.__APIPARAMS) + response = requests.get(self.__API_url, params=self.__APIPARAMS) word = list(response.json()[0]["word"].upper()) self.__bot.log("Found the word \""+"".join(word)+"\"") guessed = [False] * len(word) - gameID = datetime.datetime.now().strftime("%Y%m%d%H%M%S") - newGame = { + game_id = datetime.datetime.now().strftime("%Y%m%d%H%M%S") + new_game = { "_id": channel, "player": user, "guessed letters": [], "word": word, - "game ID": gameID, + "game ID": game_id, "misses": 0, "guessed": guessed } - self.__bot.database["hangman games"].insert_one(newGame) + self.__bot.database["hangman games"].insert_one(new_game) - remainingLetters = list(string.ascii_uppercase) + remaining_letters = list(string.ascii_uppercase) - self.__draw.drawImage(channel) + self.__draw.draw_image(channel) log_message = "Game started" - sendMessage = f"{user_name} started game of hangman." - startedGame = True + send_message = f"{user_name} started game of hangman." + started_game = True else: log_message = "There was already a game going on" - sendMessage = self.__bot.long_strings["Hangman going on"] + send_message = self.__bot.long_strings["Hangman going on"] self.__bot.log(log_message) - await ctx.send(sendMessage) + await ctx.send(send_message) - if startedGame: - boardsPath = "gwendolyn/resources/games/hangman_boards/" - file_path = f"{boardsPath}hangman_board{channel}.png" - newImage = await ctx.channel.send(file=discord.File(file_path)) + if started_game: + boards_path = "gwendolyn/resources/games/hangman_boards/" + file_path = f"{boards_path}hangman_board{channel}.png" + new_image = await ctx.channel.send(file=discord.File(file_path)) - blankMessage = await ctx.channel.send("_ _") - reactionMessages = { - newImage: remainingLetters[:15], - blankMessage: remainingLetters[15:] + blank_message = await ctx.channel.send("_ _") + reaction_messages = { + new_image: remaining_letters[:15], + blank_message: remaining_letters[15:] } - old_messages = f"{newImage.id}\n{blankMessage.id}" + old_messages = f"{new_image.id}\n{blank_message.id}" + old_images_path = "gwendolyn/resources/games/old_images/" - with open(f"gwendolyn/resources/games/old_images/hangman{channel}", "w") as f: - f.write(old_messages) + with open(old_images_path+f"hangman{channel}", "w") as file_pointer: + file_pointer.write(old_messages) - for message, letters in reactionMessages.items(): + for message, letters in reaction_messages.items(): for letter in letters: emoji = chr(ord(letter)+127397) await message.add_reaction(emoji) @@ -148,9 +149,10 @@ class Hangman(): await ctx.send("You can't end a game you're not in") else: self.__bot.database["hangman games"].delete_one({"_id": channel}) + old_images_path = "gwendolyn/resources/games/old_images/" - with open(f"gwendolyn/resources/games/old_images/hangman{channel}", "r") as f: - messages = f.read().splitlines() + with open(old_images_path+f"hangman{channel}", "r") as file_pointer: + messages = file_pointer.read().splitlines() for message in messages: old_message = await ctx.channel.fetch_message(int(message)) @@ -176,89 +178,91 @@ class Hangman(): hangman_games = self.__bot.database["hangman games"] game = hangman_games.find_one({"_id": channel}) - gameExists = (game is not None) - singleLetter = (len(guess) == 1 and guess.isalpha()) - newGuess = (guess not in game["guessed letters"]) - validGuess = (gameExists and singleLetter and newGuess) + game_exists = (game is not None) + single_letter = (len(guess) == 1 and guess.isalpha()) + new_guess = (guess not in game["guessed letters"]) + valid_guess = (game_exists and single_letter and new_guess) - if validGuess: + if valid_guess: self.__bot.log("Guessed the letter") - correctGuess = 0 + correct_guess = 0 - for x, letter in enumerate(game["word"]): + for i, letter in enumerate(game["word"]): if guess == letter: - correctGuess += 1 - updater = {"$set": {f"guessed.{x}": True}} + correct_guess += 1 + updater = {"$set": {f"guessed.{i}": True}} hangman_games.update_one({"_id": channel}, updater) - if correctGuess == 0: + if correct_guess == 0: updater = {"$inc": {"misses": 1}} hangman_games.update_one({"_id": channel}, updater) updater = {"$push": {"guessed letters": guess}} hangman_games.update_one({"_id": channel}, updater) - remainingLetters = list(string.ascii_uppercase) + remaining_letters = list(string.ascii_uppercase) game = hangman_games.find_one({"_id": channel}) for letter in game["guessed letters"]: - remainingLetters.remove(letter) + remaining_letters.remove(letter) - if correctGuess == 1: - sendMessage = "Guessed {}. There was 1 {} in the word." - sendMessage = sendMessage.format(guess, guess) + if correct_guess == 1: + send_message = "Guessed {}. There was 1 {} in the word." + send_message = send_message.format(guess, guess) else: - sendMessage = "Guessed {}. There were {} {}s in the word." - sendMessage = sendMessage.format(guess, correctGuess, guess) + send_message = "Guessed {}. There were {} {}s in the word." + send_message = send_message.format(guess, correct_guess, guess) - self.__draw.drawImage(channel) + self.__draw.draw_image(channel) if game["misses"] == 6: hangman_games.delete_one({"_id": channel}) - sendMessage += self.__bot.long_strings["Hangman lost game"] - remainingLetters = [] + send_message += self.__bot.long_strings["Hangman lost game"] + remaining_letters = [] elif all(game["guessed"]): hangman_games.delete_one({"_id": channel}) self.__bot.money.addMoney(user, 15) - sendMessage += self.__bot.long_strings["Hangman guessed word"] - remainingLetters = [] + send_message += self.__bot.long_strings["Hangman guessed word"] + remaining_letters = [] - await message.channel.send(sendMessage) + await message.channel.send(send_message) + old_images_path = "gwendolyn/resources/games/old_images/" - with open(f"gwendolyn/resources/games/old_images/hangman{channel}", "r") as f: - old_message_ids = f.read().splitlines() + with open(old_images_path+f"hangman{channel}", "r") as file_pointer: + old_message_ids = file_pointer.read().splitlines() - for oldID in old_message_ids: - old_message = await message.channel.fetch_message(int(oldID)) + for old_id in old_message_ids: + old_message = await message.channel.fetch_message(int(old_id)) self.__bot.log("Deleting old message") await old_message.delete() - boardsPath = "gwendolyn/resources/games/hangman_boards/" - file_path = f"{boardsPath}hangman_board{channel}.png" - newImage = await message.channel.send(file=discord.File(file_path)) + boards_path = "gwendolyn/resources/games/hangman_boards/" + file_path = f"{boards_path}hangman_board{channel}.png" + new_image = await message.channel.send(file=discord.File(file_path)) - if len(remainingLetters) > 0: - if len(remainingLetters) > 15: - blankMessage = await message.channel.send("_ _") - reactionMessages = { - newImage: remainingLetters[:15], - blankMessage: remainingLetters[15:] + if len(remaining_letters) > 0: + if len(remaining_letters) > 15: + blank_message = await message.channel.send("_ _") + reaction_messages = { + new_image: remaining_letters[:15], + blank_message: remaining_letters[15:] } else: - blankMessage = "" - reactionMessages = {newImage: remainingLetters} + blank_message = "" + reaction_messages = {new_image: remaining_letters} - if blankMessage != "": - old_messages = f"{newImage.id}\n{blankMessage.id}" + if blank_message != "": + old_messages = f"{new_image.id}\n{blank_message.id}" else: - old_messages = str(newImage.id) + old_messages = str(new_image.id) - old_imagePath = f"gwendolyn/resources/games/old_images/hangman{channel}" - with open(old_imagePath, "w") as f: - f.write(old_messages) + old_images_path = "gwendolyn/resources/games/old_images/" + old_image_path = old_images_path + f"hangman{channel}" + with open(old_image_path, "w") as file_pointer: + file_pointer.write(old_messages) - for message, letters in reactionMessages.items(): + for message, letters in reaction_messages.items(): for letter in letters: emoji = chr(ord(letter)+127397) await message.add_reaction(emoji) @@ -270,7 +274,7 @@ class DrawHangman(): *Methods* --------- - drawImage(channel: str) + draw_image(channel: str) """ def __init__(self, bot): @@ -293,6 +297,7 @@ class DrawHangman(): SMALLFONT """ self.__bot = bot + # pylint: disable=invalid-name self.__CIRCLESIZE = 120 self.__LINEWIDTH = 12 @@ -318,129 +323,132 @@ class DrawHangman(): FONTPATH = "gwendolyn/resources/fonts/comic-sans-bold.ttf" self.__FONT = ImageFont.truetype(FONTPATH, LETTERSIZE) self.__SMALLFONT = ImageFont.truetype(FONTPATH, WORDSIZE) + # pylint: enable=invalid-name - def __deviate(self, preDeviance: int, preDevianceAccuracy: int, - positionChange: float, maxmin: int, - maxAcceleration: float): - randomDeviance = random.uniform(-positionChange, positionChange) - devianceAccuracy = preDevianceAccuracy + randomDeviance - if devianceAccuracy > maxmin * maxAcceleration: - devianceAccuracy = maxmin * maxAcceleration - elif devianceAccuracy < -maxmin * maxAcceleration: - devianceAccuracy = -maxmin * maxAcceleration + def __deviate(self, pre_deviance: int, pre_deviance_accuracy: int, + position_change: float, maxmin: int, + max_acceleration: float): + random_deviance = random.uniform(-position_change, position_change) + deviance_accuracy = pre_deviance_accuracy + random_deviance + if deviance_accuracy > maxmin * max_acceleration: + deviance_accuracy = maxmin * max_acceleration + elif deviance_accuracy < -maxmin * max_acceleration: + deviance_accuracy = -maxmin * max_acceleration - deviance = preDeviance + devianceAccuracy + deviance = pre_deviance + deviance_accuracy if deviance > maxmin: deviance = maxmin elif deviance < -maxmin: deviance = -maxmin - return deviance, devianceAccuracy + return deviance, deviance_accuracy - def __badCircle(self): - circlePadding = (self.__LINEWIDTH*3) - imageWidth = self.__CIRCLESIZE+circlePadding - imageSize = (imageWidth, imageWidth) - background = Image.new("RGBA", imageSize, color=(0, 0, 0, 0)) + def __bad_circle(self): + circle_padding = (self.__LINEWIDTH*3) + image_width = self.__CIRCLESIZE+circle_padding + image_size = (image_width, image_width) + background = Image.new("RGBA", image_size, color=(0, 0, 0, 0)) - d = ImageDraw.Draw(background, "RGBA") + drawer = ImageDraw.Draw(background, "RGBA") middle = (self.__CIRCLESIZE+(self.__LINEWIDTH*3))/2 - devianceX = 0 - devianceY = 0 - devianceAccuracyX = 0 - devianceAccuracyY = 0 + deviance_x = 0 + deviance_y = 0 + deviance_accuracy_x = 0 + deviance_accuracy_y = 0 start = random.randint(-100, -80) - degreesAmount = 360 + random.randint(-10, 30) + degreess_amount = 360 + random.randint(-10, 30) - for degree in range(degreesAmount): - devianceXParams = [ - devianceX, - devianceAccuracyX, + for degree in range(degreess_amount): + deviance_x, deviance_accuracy_x = self.__deviate( + deviance_x, + deviance_accuracy_x, self.__LINEWIDTH/100, self.__LINEWIDTH, 0.03 - ] - devianceYParams = [ - devianceY, - devianceAccuracyY, + ) + deviance_y, deviance_accuracy_y = self.__deviate( + deviance_y, + deviance_accuracy_y, self.__LINEWIDTH/100, self.__LINEWIDTH, 0.03 - ] - devianceX, devianceAccuracyX = self.__deviate(*devianceXParams) - devianceY, devianceAccuracyY = self.__deviate(*devianceYParams) + ) radians = math.radians(degree+start) - circleX = (math.cos(radians) * (self.__CIRCLESIZE/2)) - circleY = (math.sin(radians) * (self.__CIRCLESIZE/2)) + circle_x = (math.cos(radians) * (self.__CIRCLESIZE/2)) + circle_y = (math.sin(radians) * (self.__CIRCLESIZE/2)) - x = middle + circleX - (self.__LINEWIDTH/2) + devianceX - y = middle + circleY - (self.__LINEWIDTH/2) + devianceY + position_x = middle + circle_x - (self.__LINEWIDTH/2) + deviance_x + position_y = middle + circle_y - (self.__LINEWIDTH/2) + deviance_y - circlePosition = [(x, y), (x+self.__LINEWIDTH, y+self.__LINEWIDTH)] - d.ellipse(circlePosition, fill=(0, 0, 0, 255)) + circle_position = [ + (position_x, position_y), + (position_x+self.__LINEWIDTH, position_y+self.__LINEWIDTH) + ] + drawer.ellipse(circle_position, fill=(0, 0, 0, 255)) return background - def __badLine(self, length: int, rotated: bool = False): + def __bad_line(self, length: int, rotated: bool = False): if rotated: - w, h = length+self.__LINEWIDTH*3, self.__LINEWIDTH*3 + width, height = length+self.__LINEWIDTH*3, self.__LINEWIDTH*3 else: - w, h = self.__LINEWIDTH*3, length+self.__LINEWIDTH*3 - background = Image.new("RGBA", (w, h), color=(0, 0, 0, 0)) + width, height = self.__LINEWIDTH*3, length+self.__LINEWIDTH*3 + background = Image.new("RGBA", (width, height), color=(0, 0, 0, 0)) - d = ImageDraw.Draw(background, "RGBA") + drawer = ImageDraw.Draw(background, "RGBA") - possibleDeviance = int(self.__LINEWIDTH/3) - devianceX = random.randint(-possibleDeviance, possibleDeviance) - devianceY = 0 - devianceAccuracyX = 0 - devianceAccuracyY = 0 + possible_deviance = int(self.__LINEWIDTH/3) + deviance_x = random.randint(-possible_deviance, possible_deviance) + deviance_y = 0 + deviance_accuracy_x = 0 + deviance_accuracy_y = 0 for pixel in range(length): - devianceParamsX = [ - devianceX, - devianceAccuracyX, + deviance_x, deviance_accuracy_x = self.__deviate( + deviance_x, + deviance_accuracy_x, self.__LINEWIDTH/1000, self.__LINEWIDTH, 0.004 - ] - devianceParamsY = [ - devianceY, - devianceAccuracyY, + ) + deviance_y, deviance_accuracy_y = self.__deviate( + deviance_y, + deviance_accuracy_y, self.__LINEWIDTH/1000, self.__LINEWIDTH, 0.004 - ] - devianceX, devianceAccuracyX = self.__deviate(*devianceParamsX) - devianceY, devianceAccuracyY = self.__deviate(*devianceParamsY) + ) if rotated: - x = self.__LINEWIDTH + pixel + devianceX - y = self.__LINEWIDTH + devianceY + position_x = self.__LINEWIDTH + pixel + deviance_x + position_y = self.__LINEWIDTH + deviance_y else: - x = self.__LINEWIDTH + devianceX - y = self.__LINEWIDTH + pixel + devianceY + position_x = self.__LINEWIDTH + deviance_x + position_y = self.__LINEWIDTH + pixel + deviance_y - circlePosition = [(x, y), (x+self.__LINEWIDTH, y+self.__LINEWIDTH)] - d.ellipse(circlePosition, fill=(0, 0, 0, 255)) + circle_position = [ + (position_x, position_y), + (position_x+self.__LINEWIDTH, position_y+self.__LINEWIDTH) + ] + drawer.ellipse(circle_position, fill=(0, 0, 0, 255)) return background - def __drawMan(self, misses: int, seed: str): + def __draw_man(self, misses: int, seed: str): random.seed(seed) - manSize = (self.__MANX, self.__MANY) - background = Image.new("RGBA", manSize, color=(0, 0, 0, 0)) + man_size = (self.__MANX, self.__MANY) + background = Image.new("RGBA", man_size, color=(0, 0, 0, 0)) if misses >= 1: - head = self.__badCircle() - pasteX = (self.__MANX-(self.__CIRCLESIZE+(self.__LINEWIDTH*3)))//2 - pastePosition = (pasteX, 0) - background.paste(head, pastePosition, head) + head = self.__bad_circle() + paste_x = (self.__MANX-(self.__CIRCLESIZE+(self.__LINEWIDTH*3)))//2 + paste_position = (paste_x, 0) + background.paste(head, paste_position, head) if misses >= 2: - body = self.__badLine(self.__BODYSIZE) - pasteX = (self.__MANX-(self.__LINEWIDTH*3))//2 - pastePosition = (pasteX, self.__CIRCLESIZE) - background.paste(body, pastePosition, body) + body = self.__bad_line(self.__BODYSIZE) + paste_x = (self.__MANX-(self.__LINEWIDTH*3))//2 + paste_position = (paste_x, self.__CIRCLESIZE) + background.paste(body, paste_position, body) if misses >= 3: limbs = random.sample(["rl", "ll", "ra", "la"], min(misses-2, 4)) @@ -450,131 +458,138 @@ class DrawHangman(): random.seed(seed) for limb in limbs: - limbDrawing = self.__badLine(self.__LIMBSIZE, True) - xPosition = (self.__MANX-(self.__LINEWIDTH*3))//2 + limb_drawing = self.__bad_line(self.__LIMBSIZE, True) + x_position = (self.__MANX-(self.__LINEWIDTH*3))//2 if limb[1] == "a": rotation = random.randint(-45, 45) shift = math.sin(math.radians(rotation)) - lineLength = self.__LIMBSIZE+(self.__LINEWIDTH*3) - compensation = int(shift*lineLength) - limbDrawing = limbDrawing.rotate(rotation, expand=1) - yPosition = self.__CIRCLESIZE + self.__ARMPOSITION + line_length = self.__LIMBSIZE+(self.__LINEWIDTH*3) + compensation = int(shift*line_length) + limb_drawing = limb_drawing.rotate(rotation, expand=1) + y_position = self.__CIRCLESIZE + self.__ARMPOSITION if limb == "ra": compensation = min(-compensation, 0) else: - xPosition -= self.__LIMBSIZE + x_position -= self.__LIMBSIZE compensation = min(compensation, 0) - yPosition += compensation + y_position += compensation else: rotation = random.randint(-15, 15) - yPosition = self.__CIRCLESIZE+self.__BODYSIZE-self.__LINEWIDTH + y_position = self.__CIRCLESIZE+self.__BODYSIZE-self.__LINEWIDTH if limb == "rl": - limbDrawing = limbDrawing.rotate(rotation-45, expand=1) + limb_drawing = limb_drawing.rotate(rotation-45, expand=1) else: - xPosition += -limbDrawing.size[0]+self.__LINEWIDTH*3 - limbDrawing = limbDrawing.rotate(rotation+45, expand=1) + x_position += -limb_drawing.size[0]+self.__LINEWIDTH*3 + limb_drawing = limb_drawing.rotate(rotation+45, expand=1) - pastePosition = (xPosition, yPosition) - background.paste(limbDrawing, pastePosition, limbDrawing) + paste_position = (x_position, y_position) + background.paste(limb_drawing, paste_position, limb_drawing) return background - def __badText(self, text: str, big: bool, color: tuple = (0, 0, 0, 255)): + def __bad_text(self, text: str, big: bool, color: tuple = (0, 0, 0, 255)): if big: font = self.__FONT else: font = self.__SMALLFONT - w, h = font.getsize(text) - img = Image.new("RGBA", (w, h), color=(0, 0, 0, 0)) - d = ImageDraw.Draw(img, "RGBA") + width, height = font.getsize(text) + img = Image.new("RGBA", (width, height), color=(0, 0, 0, 0)) + drawer = ImageDraw.Draw(img, "RGBA") - d.text((0, 0), text, font=font, fill=color) + drawer.text((0, 0), text, font=font, fill=color) return img - def __drawGallows(self): - gallowSize = (self.__GALLOWX, self.__GALLOWY) - background = Image.new("RGBA", gallowSize, color=(0, 0, 0, 0)) + def __draw_gallows(self): + gallow_size = (self.__GALLOWX, self.__GALLOWY) + background = Image.new("RGBA", gallow_size, color=(0, 0, 0, 0)) - bottomLine = self.__badLine(int(self.__GALLOWX * 0.75), True) - bottomLineX = int(self.__GALLOWX * 0.125) - bottomLineY = self.__GALLOWY-(self.__LINEWIDTH*4) - pastePosition = (bottomLineX, bottomLineY) - background.paste(bottomLine, pastePosition, bottomLine) + bottom_line = self.__bad_line(int(self.__GALLOWX * 0.75), True) + bottom_line_x = int(self.__GALLOWX * 0.125) + bottom_line_y = self.__GALLOWY-(self.__LINEWIDTH*4) + paste_position = (bottom_line_x, bottom_line_y) + background.paste(bottom_line, paste_position, bottom_line) - lineTwo = self.__badLine(self.__GALLOWY-self.__LINEWIDTH*6) - lineTwoX = int(self.__GALLOWX*(0.75*self.__PHI)) - lineTwoY = self.__LINEWIDTH*2 - pastePosition = (lineTwoX, lineTwoY) - background.paste(lineTwo, pastePosition, lineTwo) + line_two = self.__bad_line(self.__GALLOWY-self.__LINEWIDTH*6) + line_two_x = int(self.__GALLOWX*(0.75*self.__PHI)) + line_two_y = self.__LINEWIDTH*2 + paste_position = (line_two_x, line_two_y) + background.paste(line_two, paste_position, line_two) - topLine = self.__badLine(int(self.__GALLOWY*0.30), True) - pasteX = int(self.__GALLOWX*(0.75*self.__PHI))-self.__LINEWIDTH - pastePosition = (pasteX, self.__LINEWIDTH*3) - background.paste(topLine, pastePosition, topLine) + top_line = self.__bad_line(int(self.__GALLOWY*0.30), True) + paste_x = int(self.__GALLOWX*(0.75*self.__PHI))-self.__LINEWIDTH + paste_position = (paste_x, self.__LINEWIDTH*3) + background.paste(top_line, paste_position, top_line) - lastLine = self.__badLine(int(self.__GALLOWY*0.125)) - pasteX += int(self.__GALLOWY*0.30) - background.paste(lastLine, (pasteX, self.__LINEWIDTH*3), lastLine) + last_line = self.__bad_line(int(self.__GALLOWY*0.125)) + paste_x += int(self.__GALLOWY*0.30) + background.paste(last_line, (paste_x, self.__LINEWIDTH*3), last_line) return background - def __drawLetterLines(self, word: str, guessed: list, misses: int): - letterWidth = self.__LETTERLINELENGTH+self.__LETTERLINEDISTANCE - imageWidth = letterWidth*len(word) - imageSize = (imageWidth, self.__LETTERLINELENGTH+self.__LINEWIDTH*3) - letterLines = Image.new("RGBA", imageSize, color=(0, 0, 0, 0)) - for x, letter in enumerate(word): - line = self.__badLine(self.__LETTERLINELENGTH, True) - pasteX = x*(self.__LETTERLINELENGTH+self.__LETTERLINEDISTANCE) - pastePosition = (pasteX, self.__LETTERLINELENGTH) - letterLines.paste(line, pastePosition, line) - if guessed[x]: - letterDrawing = self.__badText(letter, True) - letterWidth = self.__FONT.getsize(letter)[0] - letterX = x*(self.__LETTERLINELENGTH+self.__LETTERLINEDISTANCE) - letterX -= (letterWidth//2) - letterX += (self.__LETTERLINELENGTH//2)+(self.__LINEWIDTH*2) - letterLines.paste(letterDrawing, (letterX, 0), letterDrawing) + def __draw_letter_lines(self, word: str, guessed: list, misses: int): + letter_width = self.__LETTERLINELENGTH+self.__LETTERLINEDISTANCE + image_width = letter_width*len(word) + image_size = (image_width, self.__LETTERLINELENGTH+self.__LINEWIDTH*3) + letter_lines = Image.new("RGBA", image_size, color=(0, 0, 0, 0)) + for i, letter in enumerate(word): + line = self.__bad_line(self.__LETTERLINELENGTH, True) + paste_x = i*(self.__LETTERLINELENGTH+self.__LETTERLINEDISTANCE) + paste_position = (paste_x, self.__LETTERLINELENGTH) + letter_lines.paste(line, paste_position, line) + if guessed[i]: + letter_drawing = self.__bad_text(letter, True) + letter_width = self.__FONT.getsize(letter)[0] + letter_x = i*(self.__LETTERLINELENGTH+self.__LETTERLINEDISTANCE) + letter_x -= (letter_width//2) + letter_x += (self.__LETTERLINELENGTH//2)+(self.__LINEWIDTH*2) + letter_lines.paste( + letter_drawing, + (letter_x, 0), + letter_drawing + ) elif misses == 6: - letterDrawing = self.__badText(letter, True, (242, 66, 54)) - letterWidth = self.__FONT.getsize(letter)[0] - letterX = x*(self.__LETTERLINELENGTH+self.__LETTERLINEDISTANCE) - letterX -= (letterWidth//2) - letterX += (self.__LETTERLINELENGTH//2)+(self.__LINEWIDTH*2) - letterLines.paste(letterDrawing, (letterX, 0), letterDrawing) + letter_drawing = self.__bad_text(letter, True, (242, 66, 54)) + letter_width = self.__FONT.getsize(letter)[0] + letter_x = i*(self.__LETTERLINELENGTH+self.__LETTERLINEDISTANCE) + letter_x -= (letter_width//2) + letter_x += (self.__LETTERLINELENGTH//2)+(self.__LINEWIDTH*2) + letter_lines.paste( + letter_drawing, + (letter_x, 0), + letter_drawing + ) - return letterLines + return letter_lines - def __shortestDist(self, positions: list, newPosition: tuple): - __shortestDist = math.inf - x, y = newPosition + def __shortest_dist(self, positions: list, new_position: tuple): + shortest_dist = math.inf for i, j in positions: - xDistance = abs(i-x) - yDistance = abs(j-y) - dist = math.sqrt(xDistance**2+yDistance**2) - if __shortestDist > dist: - __shortestDist = dist - return __shortestDist + x_distance = abs(i-new_position[0]) + y_distance = abs(j-new_position[1]) + dist = math.sqrt(x_distance**2+y_distance**2) + if shortest_dist > dist: + shortest_dist = dist + return shortest_dist - def __drawMisses(self, guesses: list, word: str): + def __draw_misses(self, guesses: list, word: str): background = Image.new("RGBA", (600, 400), color=(0, 0, 0, 0)) pos = [] for guess in guesses: if guess not in word: placed = False while not placed: - letter = self.__badText(guess, True) + letter = self.__bad_text(guess, True) w, h = self.__FONT.getsize(guess) x = random.randint(0, 600-w) y = random.randint(0, 400-h) - if self.__shortestDist(pos, (x, y)) > 70: + if self.__shortest_dist(pos, (x, y)) > 70: pos.append((x, y)) background.paste(letter, (x, y), letter) placed = True return background - def drawImage(self, channel: str): + def draw_image(self, channel: str): """ Draw a hangman Image. @@ -589,24 +604,24 @@ class DrawHangman(): random.seed(game["game ID"]) background = Image.open("gwendolyn/resources/paper.jpg") - gallow = self.__drawGallows() - man = self.__drawMan(game["misses"], game["game ID"]) + gallow = self.__draw_gallows() + man = self.__draw_man(game["misses"], game["game ID"]) random.seed(game["game ID"]) - letterLineParams = [game["word"], game["guessed"], game["misses"]] - letterLines = self.__drawLetterLines(*letterLineParams) + letter_line_parameters = [game["word"], game["guessed"], game["misses"]] + letter_lines = self.__draw_letter_lines(*letter_line_parameters) random.seed(game["game ID"]) - misses = self.__drawMisses(game["guessed letters"], game["word"]) + misses = self.__draw_misses(game["guessed letters"], game["word"]) background.paste(gallow, (100, 100), gallow) background.paste(man, (300, 210), man) - background.paste(letterLines, (120, 840), letterLines) + background.paste(letter_lines, (120, 840), letter_lines) background.paste(misses, (600, 150), misses) - missesText = self.__badText("MISSES", False) - missesTextWidth = missesText.size[0] - background.paste(missesText, (850-missesTextWidth//2, 50), missesText) + misses_text = self.__bad_text("MISSES", False) + misses_text_width = misses_text.size[0] + background.paste(misses_text, (850-misses_text_width//2, 50), misses_text) - boardPath = f"gwendolyn/resources/games/hangman_boards/hangman_board{channel}.png" - background.save(boardPath) + board_path = f"gwendolyn/resources/games/hangman_boards/hangman_board{channel}.png" + background.save(board_path) diff --git a/gwendolyn/funcs/games/hex.py b/gwendolyn/funcs/games/hex.py index 5700bd8..1f33f26 100644 --- a/gwendolyn/funcs/games/hex.py +++ b/gwendolyn/funcs/games/hex.py @@ -28,7 +28,7 @@ class HexGame(): await ctx.send("You can't surrender when you're not a player.") else: opponent = (players.index(user) + 1) % 2 - opponentName = self.bot.database_funcs.get_name(players[opponent]) + opponent_name = self.bot.database_funcs.get_name(players[opponent]) self.bot.database["hex games"].update_one({"_id":channel},{"$set":{"winner":opponent + 1}}) await ctx.send(f"{ctx.author.display_name} surrendered") @@ -71,9 +71,9 @@ class HexGame(): self.draw.drawSwap(channel) opponent = game["players"][::-1][game["turn"]-1] - gwendoTurn = (opponent == f"#{self.bot.user.id}") - opponentName = self.bot.database_funcs.get_name(opponent) - await ctx.send(f"The color of the players were swapped. It is now {opponentName}'s turn") + gwendolyn_turn = (opponent == f"#{self.bot.user.id}") + opponent_name = self.bot.database_funcs.get_name(opponent) + await ctx.send(f"The color of the players were swapped. It is now {opponent_name}'s turn") with open(f"gwendolyn/resources/games/old_images/hex{channel}", "r") as f: old_image = await ctx.channel.fetch_message(int(f.read())) @@ -90,7 +90,7 @@ class HexGame(): with open(f"gwendolyn/resources/games/old_images/hex{channel}", "w") as f: f.write(str(old_image.id)) - if gwendoTurn: + if gwendolyn_turn: await self.hexAI(ctx) # Starts the game @@ -100,109 +100,109 @@ class HexGame(): channel = str(ctx.channel_id) game = self.bot.database["hex games"].find_one({"_id":channel}) - startedGame = False - canStart = True + started_game = False + can_start = True if game != None: - sendMessage = "There's already a hex game going on in this channel" + send_message = "There's already a hex game going on in this channel" log_message = "There was already a game going on" - canStart = False + can_start = False else: if type(opponent) == int: # Opponent is Gwendolyn if opponent in range(1, 6): - opponentName = "Gwendolyn" + opponent_name = "Gwendolyn" difficulty = int(opponent) - diffText = f" with difficulty {difficulty}" + difficulty_text = f" with difficulty {difficulty}" opponent = f"#{self.bot.user.id}" else: - sendMessage = "Difficulty doesn't exist" + send_message = "Difficulty doesn't exist" log_message = "They tried to play against a difficulty that doesn't exist" - canStart = False + can_start = 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" + opponent_name = "Gwendolyn" difficulty = 2 - diffText = f" with difficulty {difficulty}" + difficulty_text = f" with difficulty {difficulty}" opponent = f"#{self.bot.user.id}" else: - sendMessage = "You can't challenge a bot!" + send_message = "You can't challenge a bot!" log_message = "They tried to challenge a bot" - canStart = False + can_start = False else: # Opponent is another player if ctx.author != opponent: - opponentName = opponent.display_name + opponent_name = opponent.display_name opponent = f"#{opponent.id}" difficulty = 5 - diffText = "" + difficulty_text = "" else: - sendMessage = "You can't play against yourself" + send_message = "You can't play against yourself" log_message = "They tried to play against themself" - canStart = False + can_start = False else: - canStart = False + can_start = False log_message = f"Opponent was neither int or member. It was {type(opponent)}" - sendMessage = "Something went wrong" + send_message = "Something went wrong" - if canStart: + if can_start: # board is 11x11 board = [[0 for i in range(self.BOARDWIDTH)] for j in range(self.BOARDWIDTH)] players = [user, opponent] random.shuffle(players) # random starting player gameHistory = [] - newGame = {"_id":channel,"board":board, "winner":0, + new_game = {"_id":channel,"board":board, "winner":0, "players":players, "turn":1, "difficulty":difficulty, "gameHistory":gameHistory} - self.bot.database["hex games"].insert_one(newGame) + self.bot.database["hex games"].insert_one(new_game) # draw the board self.draw.drawBoard(channel) - gwendoTurn = (players[0] == f"#{self.bot.user.id}") - startedGame = True + gwendolyn_turn = (players[0] == f"#{self.bot.user.id}") + started_game = True - turnName = self.bot.database_funcs.get_name(players[0]) - sendMessage = f"Started Hex game against {opponentName}{diffText}. It's {turnName}'s turn" + turn_name = self.bot.database_funcs.get_name(players[0]) + send_message = f"Started Hex game against {opponent_name}{difficulty_text}. It's {turn_name}'s turn" log_message = "Game started" - await ctx.send(sendMessage) + await ctx.send(send_message) self.bot.log(log_message) - if startedGame: + if started_game: file_path = f"gwendolyn/resources/games/hex_boards/board{ctx.channel_id}.png" - newImage = await ctx.channel.send(file = discord.File(file_path)) + new_image = await ctx.channel.send(file = discord.File(file_path)) with open(f"gwendolyn/resources/games/old_images/hex{ctx.channel_id}", "w") as f: - f.write(str(newImage.id)) + f.write(str(new_image.id)) - if gwendoTurn: + if gwendolyn_turn: await self.hexAI(ctx) # Places a piece at the given location and checks things afterwards 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 + placed_piece = False if game == None: - sendMessage = "There's no game in this channel" + send_message = "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." + send_message = "The position must be a letter followed by a number." self.bot.log(f"The position was not valid, {position}") else: 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.database_funcs.get_name(game['players'][0])} and {self.bot.database_funcs.get_name(game['players'][1])}." + send_message = f"You can't place when you're not in the game. The game's players are: {self.bot.database_funcs.get_name(game['players'][0])} and {self.bot.database_funcs.get_name(game['players'][1])}." self.bot.log("They aren't in the game") elif players[game["turn"]-1] != user: - sendMessage = "It's not your turn" + send_message = "It's not your turn" self.bot.log("It wasn't their turn") else: player = game["turn"] @@ -215,7 +215,7 @@ class HexGame(): 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.") + send_message = ("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}}) @@ -227,34 +227,34 @@ class HexGame(): winner = self.evaluateBoard(game["board"])[1] if winner == 0: # Continue with the game. - gameWon = False - sendMessage = self.bot.database_funcs.get_name(game["players"][player-1])+" placed at "+position.upper()+". It's now "+self.bot.database_funcs.get_name(game["players"][turn-1])+"'s turn."# The score is "+str(score) + game_won = False + send_message = self.bot.database_funcs.get_name(game["players"][player-1])+" placed at "+position.upper()+". It's now "+self.bot.database_funcs.get_name(game["players"][turn-1])+"'s turn."# The score is "+str(score) else: # Congratulations! - gameWon = True + game_won = True self.bot.database["hex games"].update_one({"_id":channel},{"$set":{"winner":winner}}) - sendMessage = self.bot.database_funcs.get_name(game["players"][player-1])+" placed at "+position.upper()+" and won!" + send_message = self.bot.database_funcs.get_name(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." + win_amount = game["difficulty"]*10 + send_message += " Adding "+str(win_amount)+" 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 + gwendolyn_turn = False if game["players"][turn-1] == f"#{self.bot.user.id}": self.bot.log("It's Gwendolyn's turn") - gwendoTurn = True + gwendolyn_turn = True - placedPiece = True + placed_piece = True if user == f"#{self.bot.user.id}": - await ctx.channel.send(sendMessage) + await ctx.channel.send(send_message) else: - await ctx.send(sendMessage) + await ctx.send(send_message) - if placedPiece: + if placed_piece: # Update the board self.draw.drawHexPlacement(channel,player, position) @@ -270,7 +270,7 @@ class HexGame(): file_path = f"gwendolyn/resources/games/hex_boards/board{channel}.png" old_image = await ctx.channel.send(file = discord.File(file_path)) - if gameWon: + if game_won: self.bot.log("Dealing with the winning player") game = self.bot.database["hex games"].find_one({"_id":channel}) @@ -284,7 +284,7 @@ class HexGame(): with open(f"gwendolyn/resources/games/old_images/hex{channel}", "w") as f: f.write(str(old_image.id)) - if gwendoTurn: + if gwendolyn_turn: await self.hexAI(ctx) # Returns a board where the placement has ocurred @@ -314,11 +314,11 @@ class HexGame(): game = self.bot.database["hex games"].find_one({"_id":channel}) if user not in game["players"]: - sendMessage = "You're not a player in the game" + send_message = "You're not a player in the game" elif len(game["gameHistory"]) == 0: - sendMessage = "You can't undo nothing" + send_message = "You can't undo nothing" elif user != game["players"][(game["turn"] % 2)]: # If it's not your turn - sendMessage = "It's not your turn" + send_message = "It's not your turn" else: turn = game["turn"] self.bot.log("Undoing {}'s last move".format(self.bot.database_funcs.get_name(user))) @@ -332,10 +332,10 @@ class HexGame(): # 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}" + send_message = f"You undid your last move at {lastMove}" undid = True - await ctx.send(sendMessage) + await ctx.send(send_message) if undid: with open(f"gwendolyn/resources/games/old_images/hex{channel}", "r") as f: old_image = await ctx.channel.fetch_message(int(f.read())) @@ -417,20 +417,20 @@ class HexGame(): return scores[2]-scores[1], winner - def minimaxHex(self, board, depth, alpha, beta, maximizingPlayer): + def minimaxHex(self, board, depth, alpha, beta, maximizing_player): # The depth is how many moves ahead the computer checks. This value is the difficulty. if depth == 0 or 0 not in sum(board,[]): score = self.evaluateBoard(board)[0] return score # if final depth is not reached, look another move ahead: - if maximizingPlayer: # red player predicts next move + if maximizing_player: # red player predicts next move maxEval = -math.inf possiblePlaces = [i for i,v in enumerate(sum(board,[])) if v == 0] #self.bot.log("Judging a red move at depth {}".format(depth)) for i in possiblePlaces: - testBoard = copy.deepcopy(board) - testBoard[i // self.BOARDWIDTH][i % self.BOARDWIDTH] = 1 # because maximizingPlayer is Red which is number 1 - evaluation = self.minimaxHex(testBoard,depth-1,alpha,beta,False) + test_board = copy.deepcopy(board) + test_board[i // self.BOARDWIDTH][i % self.BOARDWIDTH] = 1 # because maximizing_player is Red which is number 1 + evaluation = self.minimaxHex(test_board,depth-1,alpha,beta,False) maxEval = max(maxEval, evaluation) alpha = max(alpha, evaluation) if beta <= alpha: @@ -442,9 +442,9 @@ class HexGame(): possiblePlaces = [i for i,v in enumerate(sum(board,[])) if v == 0] #self.bot.log("Judging a blue move at depth {}".format(depth)) for i in possiblePlaces: - testBoard = copy.deepcopy(board) - testBoard[i // self.BOARDWIDTH][i % self.BOARDWIDTH] = 2 # because minimizingPlayer is Blue which is number 2 - evaluation = self.minimaxHex(testBoard,depth-1,alpha,beta,True) + test_board = copy.deepcopy(board) + test_board[i // self.BOARDWIDTH][i % self.BOARDWIDTH] = 2 # because minimizingPlayer is Blue which is number 2 + evaluation = self.minimaxHex(test_board,depth-1,alpha,beta,True) minEval = min(minEval, evaluation) beta = min(beta, evaluation) if beta <= alpha: diff --git a/gwendolyn/funcs/games/invest.py b/gwendolyn/funcs/games/invest.py index 8977503..eaac8f0 100644 --- a/gwendolyn/funcs/games/invest.py +++ b/gwendolyn/funcs/games/invest.py @@ -62,8 +62,8 @@ class Invest(): portfolio: str The portfolio. """ - investmentsDatabase = self.bot.database["investments"] - userInvestments = investmentsDatabase.find_one({"_id": user}) + investments_database = self.bot.database["investments"] + userInvestments = investments_database.find_one({"_id": user}) user_name = self.bot.database_funcs.get_name(user) @@ -101,7 +101,7 @@ class Invest(): *Returns* --------- - sendMessage: str + send_message: str The message to return to the user. """ if buyAmount < 100: @@ -111,9 +111,9 @@ class Invest(): elif self.getPrice(stock) <= 0: return f"{stock} is not traded on the american market." else: - investmentsDatabase = self.bot.database["investments"] + investments_database = self.bot.database["investments"] stockPrice = self.getPrice(stock) - userInvestments = investmentsDatabase.find_one({"_id": user}) + userInvestments = investments_database.find_one({"_id": user}) self.bot.money.addMoney(user, -1*buyAmount) stock = stock.upper() @@ -126,18 +126,18 @@ class Invest(): currentValue = int(valueChange * value["purchased"]) newAmount = currentValue + buyAmount - valuePath = f"investments.{stock}.value at purchase" - updater = {"$set": {valuePath: stockPrice}} - investmentsDatabase.update_one({"_id": user}, updater) + value_path = f"investments.{stock}.value at purchase" + updater = {"$set": {value_path: stockPrice}} + investments_database.update_one({"_id": user}, updater) - purchasedPath = f"investments.{stock}.purchased" - updater = {"$set": {purchasedPath: newAmount}} - investmentsDatabase.update_one({"_id": user}, updater) + purchased_path = f"investments.{stock}.purchased" + updater = {"$set": {purchased_path: newAmount}} + investments_database.update_one({"_id": user}, updater) if value["purchased for"] != "?": - purchasedForPath = f"investments.{stock}.purchased for" - updater = {"$set": {purchasedForPath: buyAmount}} - investmentsDatabase.update_one({"_id": user}, updater) + purchasedFor_path = f"investments.{stock}.purchased for" + updater = {"$set": {purchasedFor_path: buyAmount}} + investments_database.update_one({"_id": user}, updater) else: updater = { "$set": { @@ -148,9 +148,9 @@ class Invest(): } } } - investmentsDatabase.update_one({"_id": user}, updater) + investments_database.update_one({"_id": user}, updater) else: - newUser = { + new_user = { "_id": user, "investments": { stock: { @@ -160,12 +160,12 @@ class Invest(): } } } - investmentsDatabase.insert_one(newUser) + investments_database.insert_one(new_user) user_name = self.bot.database_funcs.get_name(user) - sendMessage = "{} bought {} GwendoBucks worth of {} stock" - sendMessage = sendMessage.format(user_name, buyAmount, stock) - return sendMessage + send_message = "{} bought {} GwendoBucks worth of {} stock" + send_message = send_message.format(user_name, buyAmount, stock) + return send_message def sellStock(self, user: str, stock: str, sellAmount: int): """ @@ -182,15 +182,15 @@ class Invest(): *Returns* --------- - sendMessage: str + send_message: str The message to return to the user. """ if sellAmount <= 0: return "no" else: - investmentsDatabase = self.bot.database["investments"] - userData = investmentsDatabase.find_one({"_id": user}) - userInvestments = userData["investments"] + investments_database = self.bot.database["investments"] + user_data = investments_database.find_one({"_id": user}) + userInvestments = user_data["investments"] stock = stock.upper() @@ -199,29 +199,29 @@ class Invest(): stockPrice = self.getPrice(stock) priceChange = (stockPrice / value["value at purchase"]) purchasedAmount = int(priceChange * value["purchased"]) - purchasedPath = f"investments.{stock}.purchased" - updater = {"$set": {purchasedPath: purchasedAmount}} - investmentsDatabase.update_one({"_id": user}, updater) - valueAtPurchasePath = f"investments.{stock}.value at purchase" - updater = {"$set": {valueAtPurchasePath: stockPrice}} - investmentsDatabase.update_one({"_id": user}, updater) + purchased_path = f"investments.{stock}.purchased" + updater = {"$set": {purchased_path: purchasedAmount}} + investments_database.update_one({"_id": user}, updater) + valueAtPurchase_path = f"investments.{stock}.value at purchase" + updater = {"$set": {valueAtPurchase_path: stockPrice}} + investments_database.update_one({"_id": user}, updater) if value["purchased"] >= sellAmount: self.bot.money.addMoney(user, sellAmount) if sellAmount < value["purchased"]: - purchasedPath = f"investments.{stock}.purchased" - updater = {"$inc": {purchasedPath: -sellAmount}} - investmentsDatabase.update_one({"_id": user}, updater) + purchased_path = f"investments.{stock}.purchased" + updater = {"$inc": {purchased_path: -sellAmount}} + investments_database.update_one({"_id": user}, updater) - purchasedForPath = f"investments.{stock}.purchased for" - updater = {"$set": {purchasedForPath: "?"}} - investmentsDatabase.update_one({"_id": user}, updater) + purchasedFor_path = f"investments.{stock}.purchased for" + updater = {"$set": {purchasedFor_path: "?"}} + investments_database.update_one({"_id": user}, updater) else: updater = {"$unset": {f"investments.{stock}": ""}} - investmentsDatabase.update_one({"_id": user}, updater) + investments_database.update_one({"_id": user}, updater) user_name = self.bot.database_funcs.get_name(user) - sendMessage = "{} sold {} GwendoBucks worth of {} stock" - return sendMessage.format(user_name, sellAmount, stock) + send_message = "{} sold {} GwendoBucks worth of {} stock" + return send_message.format(user_name, sellAmount, stock) else: return f"You don't have enough {stock} stocks to do that" else: diff --git a/gwendolyn/funcs/games/money.py b/gwendolyn/funcs/games/money.py index 51d179a..e46583d 100644 --- a/gwendolyn/funcs/games/money.py +++ b/gwendolyn/funcs/games/money.py @@ -51,10 +51,10 @@ class Money(): """ self.bot.log("checking "+user+"'s account balance") - userData = self.database["users"].find_one({"_id": user}) + user_data = self.database["users"].find_one({"_id": user}) - if userData is not None: - return userData["money"] + if user_data is not None: + return user_data["money"] else: return 0 @@ -90,18 +90,18 @@ class Money(): """ self.bot.log("adding "+str(amount)+" to "+user+"'s account") - userData = self.database["users"].find_one({"_id": user}) + user_data = self.database["users"].find_one({"_id": user}) - if userData is not None: + if user_data is not None: updater = {"$inc": {"money": amount}} self.database["users"].update_one({"_id": user}, updater) else: - newUser = { + new_user = { "_id": user, "user name": self.bot.database_funcs.get_name(user), "money": amount } - self.database["users"].insert_one(newUser) + self.database["users"].insert_one(new_user) # Transfers money from one user to another async def giveMoney(self, ctx: discord_slash.context.SlashContext, @@ -125,15 +125,15 @@ class Money(): if member.display_name.lower() == username.lower(): username = member.display_name user_id = f"#{member.id}" - newUser = { + new_user = { "_id": user_id, "user name": username, "money": 0 } - self.bot.database["users"].insert_one(newUser) + self.bot.database["users"].insert_one(new_user) userid = f"#{ctx.author.id}" - userData = self.database["users"].find_one({"_id": userid}) + user_data = self.database["users"].find_one({"_id": userid}) targetUser = self.bot.database_funcs.get_id(username) if amount <= 0: @@ -142,7 +142,7 @@ class Money(): elif targetUser is None: self.bot.log("They weren't in the system") await ctx.send("The target doesn't exist") - elif userData is None or userData["money"] < amount: + elif user_data is None or user_data["money"] < amount: self.bot.log("They didn't have enough GwendoBucks") await ctx.send("You don't have that many GwendoBuck") else: diff --git a/gwendolyn/funcs/games/trivia.py b/gwendolyn/funcs/games/trivia.py index d03c2be..e0d2284 100644 --- a/gwendolyn/funcs/games/trivia.py +++ b/gwendolyn/funcs/games/trivia.py @@ -45,7 +45,7 @@ class Trivia(): *Returns* --------- - sendMessage: str + send_message: str The message to return to the user. """ triviaQuestions = self.bot.database["trivia questions"] @@ -110,7 +110,7 @@ class Trivia(): *Returns* --------- - sendMessage: str + send_message: str The message to send if the function failed. """ triviaQuestions = self.bot.database["trivia questions"] @@ -185,10 +185,10 @@ class Trivia(): self.bot.database_funcs.delete_game(*delete_gameParams) self.bot.log("Time's up for the trivia question", channelId) - sendMessage = self.bot.long_strings["Trivia time up"] - formatParams = [chr(correctAnswer), options[correctAnswer-97]] - sendMessage = sendMessage.format(*formatParams) - await ctx.send(sendMessage) + send_message = self.bot.long_strings["Trivia time up"] + format_parameters = [chr(correctAnswer), options[correctAnswer-97]] + send_message = send_message.format(*format_parameters) + await ctx.send(send_message) else: await ctx.send(question, hidden=True) diff --git a/gwendolyn/funcs/lookup/lookup_funcs.py b/gwendolyn/funcs/lookup/lookup_funcs.py index efe24ca..418ed32 100644 --- a/gwendolyn/funcs/lookup/lookup_funcs.py +++ b/gwendolyn/funcs/lookup/lookup_funcs.py @@ -152,13 +152,13 @@ class LookupFuncs(): data = json.load(open('gwendolyn/resources/lookup/spells.json', encoding = "utf8")) if query in data: self.bot.log("Returning spell information") - 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']}") + send_message = (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)") - sendMessage = "I don't think that's a spell (error code 501)" + send_message = "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:]) + if len(send_message) > 2000: + await ctx.send(send_message[:2000]) + await ctx.send(send_message[2000:]) else: - await ctx.send(sendMessage) + await ctx.send(send_message) diff --git a/gwendolyn/funcs/other/__init__.py b/gwendolyn/funcs/other/__init__.py index 8612103..9cef1df 100644 --- a/gwendolyn/funcs/other/__init__.py +++ b/gwendolyn/funcs/other/__init__.py @@ -2,4 +2,4 @@ __all__ = ["Other"] -from .other import Other \ No newline at end of file +from .other import Other diff --git a/gwendolyn/funcs/other/bedre_netflix.py b/gwendolyn/funcs/other/bedre_netflix.py deleted file mode 100644 index f8226ec..0000000 --- a/gwendolyn/funcs/other/bedre_netflix.py +++ /dev/null @@ -1,413 +0,0 @@ -import requests, imdb, discord, json, math, time, asyncio - -class BedreNetflix(): - def __init__(self,bot): - self.bot = bot - ip = ["localhost", "192.168.0.40"][self.bot.options["testing"]] - - self.radarrURL = "http://"+ip+":7878/api/v3/" - self.sonarrURL = "http://"+ip+":8989/api/" - self.qbittorrentURL = "http://"+ip+":8080/api/v2/" - self.moviePath = "/media/plex/Server/movies/" - self.showPath = "/media/plex/Server/Shows/" - - #Returns a list of no more than 5 options when user requests a movie - async def requestMovie(self, ctx, movieName): - await self.bot.defer(ctx) - - self.bot.log("Searching for "+movieName) - movieList = imdb.IMDb().search_movie(movieName) - movies = [] - for movie in movieList: - if movie["kind"] == "movie": - movies.append(movie) - if len(movies) > 5: - movies = movies[:5] - - if len(movies) == 1: - messageTitle = "**Is it this movie?**" - else: - messageTitle = "**Is it any of these movies?**" - - messageText = "" - imdb_ids = [] - - for x, movie in enumerate(movies): - try: - messageText += "\n"+str(x+1)+") "+movie["title"]+" ("+str(movie["year"])+")" - except: - try: - messageText += "\n"+str(x+1)+") "+movie["title"] - except: - messageText += "Error" - imdb_ids.append(movie.movieID) - - self.bot.log("Returning a list of "+str(len(movies))+" possible movies: "+str(imdb_ids)) - - em = discord.Embed(title=messageTitle,description=messageText,colour=0x00FF00) - - message = await ctx.send(embed=em) - - messageData = {"message_id":message.id,"imdb_ids":imdb_ids} - - with open("gwendolyn/resources/bedre_netflix/old_message"+str(ctx.channel.id),"w") as f: - json.dump(messageData,f) - - if len(movies) == 1: - await message.add_reaction("✔️") - else: - for x in range(len(movies)): - await message.add_reaction(["1️⃣","2️⃣","3️⃣","4️⃣","5️⃣"][x]) - - await message.add_reaction("❌") - - message = await ctx.channel.fetch_message(message.id) - if message.content != "" and not isinstance(ctx.channel, discord.DMChannel): - await message.clear_reactions() - - #Adds the requested movie to Bedre Netflix - async def add_movie(self, message, imdbId, editMessage = True): - if imdbId == None: - self.bot.log("Did not find what the user was searching for") - if editMessage: - await message.edit(embed = None, content = "Try searching for the IMDB id") - else: - await message.channel.send("Try searching for the IMDB id") - else: - self.bot.log("Trying to add movie "+str(imdbId)) - apiKey = self.bot.credentials["radarr_key"] - response = requests.get(self.radarrURL+"movie/lookup/imdb?imdbId=tt"+imdbId+"&apiKey="+apiKey) - lookupData = response.json() - postData = {"qualityProfileId": 1, - "rootFolderPath" : self.moviePath, - "monitored" : True, - "addOptions": {"searchForMovie": True}} - for key in ["tmdbId","title","titleSlug","images","year"]: - postData.update({key : lookupData[key]}) - - r = requests.post(url= self.radarrURL+"movie?apikey="+apiKey,json = postData) - - if r.status_code == 201: - if editMessage: - await message.edit(embed = None, content = postData["title"]+" successfully added to Bedre Netflix") - else: - await message.channel.send(postData["title"]+" successfully added to Bedre Netflix") - - self.bot.log("Added "+postData["title"]+" to Bedre Netflix") - elif r.status_code == 400: - text = f"{postData['title']} is either already on Bedre Netflix, downloading, or not available" - if editMessage: - await message.edit(embed = None, content = text) - else: - await message.channel.send(text) - else: - if editMessage: - await message.edit(embed = None, content = "Something went wrong") - else: - await message.channel.send("Something went wrong") - self.bot.log(str(r.status_code)+" "+r.reason) - - #Returns a list of no more than 5 options when user requests a show - async def requestShow(self, ctx, showName): - await self.bot.defer(ctx) - - self.bot.log("Searching for "+showName) - movies = imdb.IMDb().search_movie(showName) #Replace with tvdb - shows = [] - for movie in movies: - if movie["kind"] in ["tv series","tv miniseries"]: - shows.append(movie) - if len(shows) > 5: - shows = shows[:5] - - if len(shows) == 1: - messageTitle = "**Is it this show?**" - else: - messageTitle = "**Is it any of these shows?**" - - messageText = "" - imdb_names = [] - - for x, show in enumerate(shows): - try: - messageText += "\n"+str(x+1)+") "+show["title"]+" ("+str(show["year"])+")" - except: - try: - messageText += "\n"+str(x+1)+") "+show["title"] - except: - messageText += "Error" - imdb_names.append(show["title"]) - - self.bot.log("Returning a list of "+str(len(shows))+" possible shows: "+str(imdb_names)) - - em = discord.Embed(title=messageTitle,description=messageText,colour=0x00FF00) - - message = await ctx.send(embed=em) - - messageData = {"message_id":message.id,"imdb_names":imdb_names} - - with open("gwendolyn/resources/bedre_netflix/old_message"+str(ctx.channel.id),"w") as f: - json.dump(messageData,f) - - if len(shows) == 1: - await message.add_reaction("✔️") - else: - for x in range(len(shows)): - await message.add_reaction(["1️⃣","2️⃣","3️⃣","4️⃣","5️⃣"][x]) - - await message.add_reaction("❌") - - message = await ctx.channel.fetch_message(message.id) - if message.content != "" and not isinstance(ctx.channel, discord.DMChannel): - await message.clear_reactions() - - #Adds the requested show to Bedre Netflix - async def add_show(self, message, imdb_name): - if imdb_name == None: - self.bot.log("Did not find what the user was searching for") - await message.edit(embed = None, content = "Try searching for the IMDB id") - else: - self.bot.log("Trying to add show "+str(imdb_name)) - apiKey = self.bot.credentials["sonarr_key"] - response = requests.get(self.sonarrURL+"series/lookup?term="+imdb_name.replace(" ","%20")+"&apiKey="+apiKey) - lookupData = response.json()[0] - postData = {"ProfileId" : 1, - "rootFolderPath" : self.showPath, - "monitored" : True, - "addOptions" : {"searchForMissingEpisodes" : True}} - for key in ["tvdbId","title","titleSlug","images","seasons"]: - postData.update({key : lookupData[key]}) - - r = requests.post(url= self.sonarrURL+"series?apikey="+apiKey,json = postData) - - if r.status_code == 201: - await message.edit(embed = None, content = postData["title"]+" successfully added to Bedre Netflix") - self.bot.log("Added a "+postData["title"]+" to Bedre Netflix") - elif r.status_code == 400: - text = f"{postData['title']} is either already on Bedre Netflix, downloading, or not available" - await message.edit(embed = None, content = text) - else: - await message.edit(embed = None, content = "Something went wrong") - self.bot.log(str(r.status_code)+" "+r.reason) - - #Generates a list of all torrents and returns formatted list and whether all torrents are downloaded - async def genDownloadList(self, showDM, showMovies, showShows, episodes): - self.bot.log("Generating torrent list") - titleWidth = 100 - message = [] - allDownloaded = True - - if showDM: - message.append("") - DMSectionTitle = "*Torrent Downloads*" - DMSectionTitleLine = "-"*((titleWidth-len(DMSectionTitle))//2) - message.append(DMSectionTitleLine+DMSectionTitle+DMSectionTitleLine) - response = requests.get(self.qbittorrentURL+"torrents/info") - torrentList = response.json() - - if len(torrentList) > 0: - for torrent in torrentList: - torrentName = torrent["name"] - if len(torrentName) > 30: - if torrentName[26] == " ": - torrentName = torrentName[:26]+"...." - else: - torrentName = torrentName[:27]+"..." - while len(torrentName) < 30: - torrentName += " " - - if torrent["size"] == 0: - downloadedRatio = 0 - elif torrent["amount_left"] == 0: - downloadedRatio = 1 - else: - downloadedRatio = min(torrent["downloaded"]/torrent["size"],1) - progressBar = "|"+("█"*math.floor(downloadedRatio*20)) - while len(progressBar) < 21: - progressBar += " " - - progressBar += "| "+str(math.floor(downloadedRatio*100))+"%" - - while len(progressBar) < 27: - progressBar += " " - - etaInSeconds = torrent["eta"] - - if etaInSeconds >= 8640000: - eta = "∞" - else: - eta = "" - if etaInSeconds >= 86400: - eta += str(math.floor(etaInSeconds/86400))+"d " - if etaInSeconds >= 3600: - eta += str(math.floor((etaInSeconds%86400)/3600))+"h " - if etaInSeconds >= 60: - eta += str(math.floor((etaInSeconds%3600)/60))+"m " - - eta += str(etaInSeconds%60)+"s" - - torrentInfo = torrentName+" "+progressBar+" (Eta: "+eta+")" - - if torrent["state"] == "stalledDL": - torrentInfo += " (Stalled)" - - if not (downloadedRatio == 1 and torrent["last_activity"] < time.time()-7200): - message.append(torrentInfo) - - if downloadedRatio < 1 and torrent["state"] != "stalledDL": - allDownloaded = False - else: - message.append("No torrents currently downloading") - - if showMovies: - message.append("") - movieSectionTitle = "*Missing movies not downloading*" - movieSectionTitleLine = "-"*((titleWidth-len(movieSectionTitle))//2) - message.append(movieSectionTitleLine+movieSectionTitle+movieSectionTitleLine) - movieList = requests.get(self.radarrURL+"movie?apiKey="+self.bot.credentials["radarr_key"]).json() - movieQueue = requests.get(self.radarrURL+"queue?apiKey="+self.bot.credentials["radarr_key"]).json() - movieQueueIDs = [] - - for queueItem in movieQueue["records"]: - movieQueueIDs.append(queueItem["movieId"]) - - for movie in movieList: - if not movie["hasFile"]: - if movie["id"] not in movieQueueIDs: - movieName = movie["title"] - if len(movieName) > 40: - if movieName[36] == " ": - movieName = movieName[:36]+"...." - else: - movieName = movieName[:37]+"..." - - while len(movieName) < 41: - movieName += " " - - if movie["monitored"]: - movieInfo = movieName+"Could not find a torrent" - else: - movieInfo = movieName+"No torrent exists. Likely because the movie is not yet released on DVD" - - message.append(movieInfo) - - if showShows: - message.append("") - showSectionTitle = "*Missing shows not downloading*" - showSectionTitleLine = "-"*((titleWidth-len(showSectionTitle))//2) - message.append(showSectionTitleLine+showSectionTitle+showSectionTitleLine) - - showList = requests.get(self.sonarrURL+"series?apiKey="+self.bot.credentials["sonarr_key"]).json() - - for show in showList: - if show["seasons"][0]["seasonNumber"] == 0: - seasons = show["seasons"][1:] - else: - seasons = show["seasons"] - if any(i["statistics"]["episodeCount"] != i["statistics"]["totalEpisodeCount"] for i in seasons): - if all(i["statistics"]["episodeCount"] == 0 for i in seasons): - message.append(show["title"] + " (all episodes)") - else: - if episodes: - missingEpisodes = sum(i["statistics"]["totalEpisodeCount"] - i["statistics"]["episodeCount"] for i in seasons) - message.append(show["title"] + f" ({missingEpisodes} episodes)") - - message.append("-"*titleWidth) - - messageText = "```"+"\n".join(message[1:])+"```" - if messageText == "``````": - messageText = "There are no torrents downloading right. If the torrent you're looking for was added more than 24 hours ago, it might already be on Bedre Netflix." - return messageText, allDownloaded - - async def downloading(self, ctx, content): - async def SendLongMessage(ctx,messageText): - if len(messageText) <= 1994: - await ctx.send("```"+messageText+"```") - else: - cutOffIndex = messageText[:1994].rfind("\n") - await ctx.send("```"+messageText[:cutOffIndex]+"```") - await SendLongMessage(ctx,messageText[cutOffIndex+1:]) - - await self.bot.defer(ctx) - - # showDM, showMovies, showShows, episodes - params = [False, False, False, False] - showDMArgs = ["d", "dm", "downloading", "downloadmanager"] - showMoviesArgs = ["m", "movies"] - showShowsArgs = ["s", "shows", "series"] - episodesArgs = ["e", "episodes"] - argList = [showDMArgs, showMoviesArgs, showShowsArgs, episodesArgs] - inputArgs = [] - validArguments = True - - while content != "" and validArguments: - if content[0] == " ": - content = content[1:] - elif content[0] == "-": - if content[1] == "-": - argStart = 2 - if " " in content: - argStop = content.find(" ") - else: - argStop = None - else: - argStart = 1 - argStop = 2 - - inputArgs.append(content[argStart:argStop]) - if argStop is None: - content = "" - else: - content = content[argStop:] - else: - validArguments = False - - if validArguments: - for x, argAliases in enumerate(argList): - argInInput = [i in inputArgs for i in argAliases] - if any(argInInput): - inputArgs.remove(argAliases[argInInput.index(True)]) - params[x] = True - - if len(inputArgs) != 0 or (params[2] == False and params[3] == True): - validArguments = False - - showAnything = any(i for i in params) - if validArguments and showAnything: - messageText, allDownloaded = await self.genDownloadList(*params) - if messageText.startswith("```"): - - if len(messageText) <= 2000: - if not allDownloaded: - updatesLeft = 60 - messageText = messageText[:-3]+"\nThis message will update every 10 seconds for "+str(math.ceil(updatesLeft/6))+" more minutes\n```" - old_message = await ctx.send(messageText) - - while ((not allDownloaded) and updatesLeft > 0): - await asyncio.sleep(10) - updatesLeft -= 1 - messageText, allDownloaded = await self.genDownloadList(*params) - messageText = messageText[:-3]+"\nThis message will update every 10 seconds for "+str(math.ceil(updatesLeft/6))+" more minutes\n```" - await old_message.edit(content = messageText) - - messageText, allDownloaded = await self.genDownloadList(*params) - - if messageText.startswith("```"): - if allDownloaded: - self.bot.log("All torrents are downloaded") - else: - messageText = messageText[:-3]+"\nThis message will not update anymore\n```" - self.bot.log("The message updated 20 times") - - await old_message.edit(content = messageText) - - else: - await ctx.send(messageText) - else: - messageText = messageText[3:-3] - await SendLongMessage(ctx,messageText) - else: - await ctx.send(messageText) - else: - await ctx.send("Invalid or repeated parameters. Use '/help downloading' to see valid parameters.") - diff --git a/gwendolyn/funcs/other/generators.py b/gwendolyn/funcs/other/generators.py index 8b8b5d9..c3a99a6 100644 --- a/gwendolyn/funcs/other/generators.py +++ b/gwendolyn/funcs/other/generators.py @@ -72,15 +72,15 @@ class Generators(): # Ends name if the name ends if new_letter == "\n": done = True - genName = "".join(chain) - self.bot.log("Generated "+genName[:-1]) + gen_name = "".join(chain) + self.bot.log("Generated "+gen_name[:-1]) # Returns the name - await ctx.send(genName) + await ctx.send(gen_name) # Generates a random tavern name async def tavernGen(self, ctx): - # Lists first parts, second parts and third parts of tavern names + # _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"] tp = [" Tavern"," Inn","","","","","","","","",""] diff --git a/gwendolyn/funcs/other/nerd_shit.py b/gwendolyn/funcs/other/nerd_shit.py index 1e8274a..22b7159 100644 --- a/gwendolyn/funcs/other/nerd_shit.py +++ b/gwendolyn/funcs/other/nerd_shit.py @@ -38,10 +38,10 @@ class NerdShit(): heights += [height] width = max(width,int(pod.img['@width'])) if titleChucks[x][count] == "": - placeForText = 0 + placeFor_text = 0 else: - placeForText = 30 - height += int(pod.img["@height"]) + 10 + placeForText + placeFor_text = 30 + height += int(pod.img["@height"]) + 10 + placeFor_text width += 10 height += 5 @@ -55,18 +55,18 @@ class NerdShit(): old_image = Image.open("gwendolyn/resources/wolfTemp.png") oldSize = old_image.size if titleChucks[x][count] == "": - placeForText = 0 + placeFor_text = 0 else: - placeForText = 30 - newSize = (width,int(oldSize[1]+10+placeForText)) - newImage = Image.new("RGB",newSize,color=(255,255,255)) - newImage.paste(old_image, (int((int(oldSize[0]+10)-oldSize[0])/2),int(((newSize[1]-placeForText)-oldSize[1])/2)+placeForText)) + placeFor_text = 30 + newSize = (width,int(oldSize[1]+10+placeFor_text)) + new_image = Image.new("RGB",newSize,color=(255,255,255)) + new_image.paste(old_image, (int((int(oldSize[0]+10)-oldSize[0])/2),int(((newSize[1]-placeFor_text)-oldSize[1])/2)+placeFor_text)) if titleChucks[x][count] != "": - d = ImageDraw.Draw(newImage,"RGB") + d = ImageDraw.Draw(new_image,"RGB") d.text((5,7),titleChucks[x][count],font=fnt,fill=(150,150,150)) - wolfImage.paste(newImage,(0,heights[count])) - newImage.close() + wolfImage.paste(new_image,(0,heights[count])) + new_image.close() old_image.close() count += 1 diff --git a/gwendolyn/funcs/other/other.py b/gwendolyn/funcs/other/other.py index f901e1d..f560a78 100644 --- a/gwendolyn/funcs/other/other.py +++ b/gwendolyn/funcs/other/other.py @@ -7,7 +7,7 @@ import lxml # Used in imageFunc import fandom # Used in findWikiPage import d20 # Used in rollDice import ast -from .bedre_netflix import BedreNetflix +from .plex import Plex from .nerd_shit import NerdShit from .generators import Generators @@ -19,16 +19,16 @@ fandom.set_wiki("senkulpa") class MyStringifier(d20.MarkdownStringifier): def _str_expression(self, node): if node.comment == None: - resultText = "Result" + result_text = "Result" else: - resultText = node.comment.capitalize() + result_text = node.comment.capitalize() - return f"**{resultText}**: {self._stringify(node.roll)}\n**Total**: {int(node.total)}" + return f"**{result_text}**: {self._stringify(node.roll)}\n**Total**: {int(node.total)}" class Other(): def __init__(self, bot): self.bot = bot - self.bedre_netflix = BedreNetflix(self.bot) + self.plex = Plex(self.bot) self.nerd_shit = NerdShit(self.bot) self.generators = Generators(self.bot) @@ -41,11 +41,11 @@ class Other(): self.bot.log("Picking a movie") with open("gwendolyn/resources/movies.txt", "r") as f: - movieList = f.read().split("\n") - movieName = random.choice(movieList) + movie_list = f.read().split("\n") + movie_name = random.choice(movie_list) - self.bot.log(f"Searching for {movieName}") - searchResult = imdbClient.search_movie(movieName) + self.bot.log(f"Searching for {movie_name}") + searchResult = imdbClient.search_movie(movie_name) self.bot.log("Getting the data") movie = searchResult[0] @@ -74,17 +74,17 @@ class Other(): 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): - sendMessage = "Good morning, "+str(author) + send_message = "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): - sendMessage = "Good afternoon, "+str(author) + send_message = "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): - sendMessage = "Good evening, "+str(author) + send_message = "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): - sendMessage = "Good night, "+str(author) + send_message = "Good night, "+str(author) else: - sendMessage = "Hello, "+str(author) + send_message = "Hello, "+str(author) - await ctx.send(sendMessage) + await ctx.send(send_message) # Finds a random picture online async def imageFunc(self, ctx): diff --git a/gwendolyn/funcs/other/plex.py b/gwendolyn/funcs/other/plex.py new file mode 100644 index 0000000..953911d --- /dev/null +++ b/gwendolyn/funcs/other/plex.py @@ -0,0 +1,536 @@ +"""Plex integration with the bot.""" +from math import floor, ceil +import time +import json +import asyncio +import requests +import imdb +import discord + +class Plex(): + """Container for Plex functions and commands.""" + def __init__(self,bot): + self.bot = bot + self.credentials = self.bot.credentials + self.long_strings = self.bot.long_strings + server_ip = ["localhost", "192.168.0.40"][self.bot.options["testing"]] + + self.radarr_url = "http://"+server_ip+":7878/api/v3/" + self.sonarr_url = "http://"+server_ip+":8989/api/" + self.qbittorrent_url = "http://"+server_ip+":8080/api/v2/" + self.movie_path = "/media/plex/Server/movies/" + self.show_path = "/media/plex/Server/Shows/" + + + async def request_movie(self, ctx, movie_name): + """Request a movie for the Plex Server""" + await self.bot.defer(ctx) + + self.bot.log("Searching for "+movie_name) + movie_list = imdb.IMDb().search_movie(movie_name) + movies = [] + for movie in movie_list: + if movie["kind"] == "movie": + movies.append(movie) + if len(movies) > 5: + movies = movies[:5] + + if len(movies) == 1: + message_title = "**Is it this movie?**" + else: + message_title = "**Is it any of these movies?**" + + message_text = "" + imdb_ids = [] + + for i, movie in enumerate(movies): + try: + message_text += "\n"+str(i+1)+") "+movie["title"] + try: + message_text += " ("+str(movie["year"])+")" + except KeyError: + self.bot.log(f"{movie['title']} has no year.") + except KeyError: + message_text += "Error" + imdb_ids.append(movie.movieID) + + self.bot.log( + f"Returning a list of {len(movies)} possible movies: {imdb_ids}" + ) + + embed = discord.Embed( + title=message_title, + description=message_text, + colour=0x00FF00 + ) + + message = await ctx.send(embed=embed) + + message_data = {"message_id":message.id,"imdb_ids":imdb_ids} + + file_path = f"gwendolyn/resources/plex/old_message{ctx.channel.id}" + with open(file_path,"w") as file_pointer: + json.dump(message_data, file_pointer) + + if len(movies) == 1: + await message.add_reaction("✔️") + else: + for i in range(len(movies)): + await message.add_reaction(["1️⃣","2️⃣","3️⃣","4️⃣","5️⃣"][i]) + + await message.add_reaction("❌") + + message = await ctx.channel.fetch_message(message.id) + if (message.content != "" and + not isinstance(ctx.channel, discord.DMChannel)): + await message.clear_reactions() + + + async def add_movie(self, message, imdb_id, edit_message = True): + """Add a movie to Plex server.""" + if imdb_id is None: + self.bot.log("Did not find what the user was searching for") + if edit_message: + await message.edit( + embed = None, + content = "Try searching for the IMDB id" + ) + else: + await message.channel.send("Try searching for the IMDB id") + else: + self.bot.log("Trying to add movie "+str(imdb_id)) + api_key = self.credentials["radarr_key"] + request_url = self.radarr_url+"movie/lookup/imdb?imdbId=tt"+imdb_id + request_url += "&apiKey="+api_key + response = requests.get(request_url) + lookup_data = response.json() + post_data = {"qualityProfileId": 1, + "rootFolder_path" : self.movie_path, + "monitored" : True, + "addOptions": {"searchForMovie": True}} + for key in ["tmdbId","title","titleSlug","images","year"]: + post_data.update({key : lookup_data[key]}) + + response = requests.post( + url= self.radarr_url+"movie?apikey="+api_key, + json = post_data + ) + + if response.status_code == 201: + success_message = "{} successfully added to Plex".format( + post_data["title"] + ) + if edit_message: + await message.edit( + embed = None, + content = success_message + ) + else: + await message.channel.send(success_message) + + self.bot.log("Added "+post_data["title"]+" to Plex") + elif response.status_code == 400: + fail_text = self.long_strings["Already on Plex"].format( + post_data['title'] + ) + if edit_message: + await message.edit(embed = None, content = fail_text) + else: + await message.channel.send(fail_text) + else: + if edit_message: + await message.edit( + embed = None, + content = "Something went wrong" + ) + else: + await message.channel.send("Something went wrong") + self.bot.log(str(response.status_code)+" "+response.reason) + + async def request_show(self, ctx, show_name): + """Request a show for the Plex server.""" + await self.bot.defer(ctx) + + self.bot.log("Searching for "+show_name) + movies = imdb.IMDb().search_movie(show_name) # Replace with tvdb + shows = [] + for movie in movies: + if movie["kind"] in ["tv series","tv miniseries"]: + shows.append(movie) + if len(shows) > 5: + shows = shows[:5] + + if len(shows) == 1: + message_title = "**Is it this show?**" + else: + message_title = "**Is it any of these shows?**" + + message_text = "" + imdb_names = [] + + for i, show in enumerate(shows): + try: + message_text += f"\n{i+1}) {show['title']} ({show['year']})" + except KeyError: + try: + message_text += "\n"+str(i+1)+") "+show["title"] + except KeyError: + message_text += "Error" + imdb_names.append(show["title"]) + + self.bot.log( + f"Returning a list of {len(shows)} possible shows: {imdb_names}" + ) + + embed = discord.Embed( + title=message_title, + description=message_text, + colour=0x00FF00 + ) + + message = await ctx.send(embed=embed) + + message_data = {"message_id":message.id,"imdb_names":imdb_names} + + file_path = "gwendolyn/resources/plex/old_message"+str(ctx.channel.id) + with open(file_path,"w") as file_pointer: + json.dump(message_data, file_pointer) + + if len(shows) == 1: + await message.add_reaction("✔️") + else: + for i in range(len(shows)): + await message.add_reaction(["1️⃣","2️⃣","3️⃣","4️⃣","5️⃣"][i]) + + await message.add_reaction("❌") + + message = await ctx.channel.fetch_message(message.id) + if message.content != "": + if not isinstance(ctx.channel, discord.DMChannel): + await message.clear_reactions() + + async def add_show(self, message, imdb_name): + """Add the requested show to Plex.""" + if imdb_name is None: + self.bot.log("Did not find what the user was searching for") + await message.edit( + embed = None, + content = "Try searching for the IMDB id" + ) + else: + self.bot.log("Trying to add show "+str(imdb_name)) + api_key = self.credentials["sonarr_key"] + request_url = self.sonarr_url+"series/lookup?term=" + request_url += imdb_name.replace(" ","%20") + request_url += "&apiKey="+api_key + response = requests.get(request_url) + lookup_data = response.json()[0] + post_data = { + "ProfileId" : 1, + "rootFolder_path" : self.show_path, + "monitored" : True, + "addOptions" : {"searchForMissingEpisodes" : True} + } + for key in ["tvdbId","title","titleSlug","images","seasons"]: + post_data.update({key : lookup_data[key]}) + + + response = requests.post( + url= self.sonarr_url+"series?apikey="+api_key, + json = post_data + ) + + if response.status_code == 201: + await message.edit( + embed = None, + content = post_data["title"]+" successfully added to Plex" + ) + self.bot.log("Added a "+post_data["title"]+" to Plex") + elif response.status_code == 400: + text = self.long_strings["Already on Plex"].format( + post_data['title'] + ) + await message.edit(embed = None, content = text) + else: + await message.edit( + embed = None, + content = "Something went wrong" + ) + self.bot.log(str(response.status_code)+" "+response.reason) + + async def __generate_download_list(self, show_dm, show_movies, show_shows, + episodes): + """Generate a list of all torrents. + + *Returns* + message_text: str + A formatted list of all torrents + + all_downloaded: bool + Whether all torrents are downloaded + """ + self.bot.log("Generating torrent list") + title_width = 100 + message = [] + all_downloaded = True + + if show_dm: + message.append("") + dm_section_title = "*Torrent Downloads*" + dm_section_title_line = "-"*((title_width-len(dm_section_title))//2) + message.append( + dm_section_title_line+dm_section_title+dm_section_title_line + ) + response = requests.get(self.qbittorrent_url+"torrents/info") + torrent_list = response.json() + + if len(torrent_list) > 0: + for torrent in torrent_list: + torrent_name = torrent["name"] + if len(torrent_name) > 30: + if torrent_name[26] == " ": + torrent_name = torrent_name[:26]+"...." + else: + torrent_name = torrent_name[:27]+"..." + while len(torrent_name) < 30: + torrent_name += " " + + if torrent["size"] == 0: + download_ratio = 0 + elif torrent["amount_left"] == 0: + download_ratio = 1 + else: + download_ratio = min( + torrent["downloaded"]/torrent["size"], + 1 + ) + progress_bar = "|"+("█"*floor(download_ratio*20)) + while len(progress_bar) < 21: + progress_bar += " " + + progress_bar += "| "+str(floor(download_ratio*100))+"%" + + while len(progress_bar) < 27: + progress_bar += " " + + eta_in_seconds = torrent["eta"] + + if eta_in_seconds >= 8640000: + eta = "∞" + else: + eta = "" + if eta_in_seconds >= 86400: + eta += str(floor(eta_in_seconds/86400))+"d " + if eta_in_seconds >= 3600: + eta += str(floor((eta_in_seconds%86400)/3600))+"h " + if eta_in_seconds >= 60: + eta += str(floor((eta_in_seconds%3600)/60))+"m " + + eta += str(eta_in_seconds%60)+"s" + + torrent_info = f"{torrent_name} {progress_bar} " + torrent_info += f"(Eta: {eta})" + + if torrent["state"] == "stalledDL": + torrent_info += " (Stalled)" + + if not (download_ratio == 1 and + torrent["last_activity"] < time.time()-7200): + message.append(torrent_info) + + if download_ratio < 1 and torrent["state"] != "stalledDL": + all_downloaded = False + else: + message.append("No torrents currently downloading") + + if show_movies: + message.append("") + movies_section_title = "*Missing movies not downloading*" + movies_section_line = ( + "-"*((title_width-len(movies_section_title))//2) + ) + message.append( + movies_section_line+movies_section_title+movies_section_line + ) + movie_list = requests.get( + self.radarr_url+"movie?api_key="+self.credentials["radarr_key"] + ).json() + movie_queue = requests.get( + self.radarr_url+"queue?api_key="+self.credentials["radarr_key"] + ).json() + movie_queue_ids = [] + + for queue_item in movie_queue["records"]: + movie_queue_ids.append(queue_item["movieId"]) + + for movie in movie_list: + if (not movie["hasFile"] and + movie["id"] not in movie_queue_ids): + movie_name = movie["title"] + if len(movie_name) > 40: + if movie_name[36] == " ": + movie_name = movie_name[:36]+"...." + else: + movie_name = movie_name[:37]+"..." + + while len(movie_name) < 41: + movie_name += " " + + if movie["monitored"]: + movie_info = movie_name+"Could not find a torrent" + else: + movie_info = self.long_strings["No torrent"].format( + movie_name + ) + + message.append(movie_info) + + if show_shows: + message.append("") + show_section_title = "*Missing shows not downloading*" + show_section_line = "-"*((title_width-len(show_section_title))//2) + message.append( + show_section_line+show_section_title+show_section_line + ) + + show_list = requests.get( + self.sonarr_url+"series?api_key="+self.credentials["sonarr_key"] + ).json() + + for show in show_list: + if show["seasons"][0]["seasonNumber"] == 0: + seasons = show["seasons"][1:] + else: + seasons = show["seasons"] + if any( + ( + i["statistics"]["episodeCount"] != + i["statistics"]["totalEpisodeCount"] + ) for i in seasons): + if all( + i["statistics"]["episodeCount"] == 0 for i in seasons + ): + message.append(show["title"] + " (all episodes)") + else: + if episodes: + missing_episodes = sum( + (i["statistics"]["totalEpisodeCount"] - + i["statistics"]["episodeCount"]) + for i in seasons) + message.append( + f"{show['title']} ({missing_episodes} episodes)" + ) + + message.append("-"*title_width) + + message_text = "```"+"\n".join(message[1:])+"```" + if message_text == "``````": + message_text = self.long_strings["No torrents downloading"] + return message_text, all_downloaded + + async def downloading(self, ctx, content): + """Send message with list of all downloading torrents.""" + async def send_long_message(ctx,message_text): + if len(message_text) <= 1994: + await ctx.send("```"+message_text+"```") + else: + cut_off_index = message_text[:1994].rfind("\n") + await ctx.send("```"+message_text[:cut_off_index]+"```") + await send_long_message(ctx,message_text[cut_off_index+1:]) + + await self.bot.defer(ctx) + + # showDM, showMovies, showShows, episodes + parameters = [False, False, False, False] + show_dm_args = ["d", "dm", "downloading", "downloadmanager"] + show_movies_args = ["m", "movies"] + show_shows_args = ["s", "shows", "series"] + show_episode_args = ["e", "episodes"] + arg_list = [ + show_dm_args, show_movies_args, show_shows_args, show_episode_args + ] + input_args = [] + valid_arguments = True + + while content != "" and valid_arguments: + if content[0] == " ": + content = content[1:] + elif content[0] == "-": + if content[1] == "-": + arg_start = 2 + if " " in content: + arg_stop = content.find(" ") + else: + arg_stop = None + else: + arg_start = 1 + arg_stop = 2 + + input_args.append(content[arg_start:arg_stop]) + if arg_stop is None: + content = "" + else: + content = content[arg_stop:] + else: + valid_arguments = False + + if valid_arguments: + for arg_index, arg_aliases in enumerate(arg_list): + arg_in_input = [i in input_args for i in arg_aliases] + if any(arg_in_input): + input_args.remove(arg_aliases[arg_in_input.index(True)]) + parameters[arg_index] = True + + if len(input_args) != 0 or (not parameters[2] and parameters[3]): + valid_arguments = False + + show_anything = any(i for i in parameters) + if not (valid_arguments and show_anything): + await ctx.send(self.long_strings["Invalid parameters"]) + else: + message_text, all_downloaded = await self.__generate_download_list( + *parameters + ) + if not message_text.startswith("```"): + await ctx.send(message_text) + + elif len(message_text) > 2000: + message_text = message_text[3:-3] + await send_long_message(ctx,message_text) + + elif all_downloaded: + await ctx.send(message_text) + + else: + updates_left = 60 + message_text = self.long_strings["Update"].format( + message_text[:-3], ceil(updates_left/6) + ) + old_message = await ctx.send(message_text) + + while ((not all_downloaded) and updates_left > 0): + await asyncio.sleep(10) + updates_left -= 1 + message_text, all_downloaded = await ( + self.__generate_download_list(*parameters) + ) + message_text = self.long_strings["Update"].format( + message_text[:-3], + ceil(updates_left/6) + ) + await old_message.edit(content = message_text) + + message_text, all_downloaded = await ( + self.__generate_download_list(*parameters) + ) + + if message_text.startswith("```"): + if all_downloaded: + self.bot.log("All torrents are downloaded") + else: + message_text = self.long_strings["No updates"].format( + message_text[:-3] + ) + self.bot.log("The message updated 20 times") + + await old_message.edit(content = message_text) diff --git a/gwendolyn/funcs/star_wars_funcs/star_wars_char.py b/gwendolyn/funcs/star_wars_funcs/star_wars_char.py index fd749f8..ebfa53a 100644 --- a/gwendolyn/funcs/star_wars_funcs/star_wars_char.py +++ b/gwendolyn/funcs/star_wars_funcs/star_wars_char.py @@ -6,7 +6,7 @@ class StarWarsChar(): def __init__(self, bot): self.bot = bot - def getCharName(self, user : str): + def getChar_name(self, user : str): self.bot.log("Getting name for "+self.bot.database_funcs.get_name(user)+"'s character") userCharacter = self.bot.database["starwars characters"].find_one({"_id":user}) @@ -239,7 +239,7 @@ class StarWarsChar(): return name, text1+text2+"\n\n"+text3+divider+text4+"\n"+divider+text5+text6+text7+text8 - def charData(self,user : str,cmd : str): + def char_data(self,user : str,cmd : str): userCharacter = self.bot.database["starwars characters"].find_one({"_id":user}) key = string.capwords(cmd.split(" ")[0]) @@ -508,7 +508,7 @@ class StarWarsChar(): self.bot.database["starwars characters"].delete_one({"_id":user}) await ctx.send("Character for " + self.bot.database_funcs.get_name(user) + " deleted") else: - await ctx.send(self.replaceWithSpaces(str(self.charData(user,cmd)))) + await ctx.send(self.replaceWithSpaces(str(self.char_data(user,cmd)))) if returnEmbed: em = discord.Embed(title = title, description = text, colour=0xDEADBF) diff --git a/gwendolyn/funcs/star_wars_funcs/star_wars_destiny.py b/gwendolyn/funcs/star_wars_funcs/star_wars_destiny.py index acb2cc6..1f5a7c2 100644 --- a/gwendolyn/funcs/star_wars_funcs/star_wars_destiny.py +++ b/gwendolyn/funcs/star_wars_funcs/star_wars_destiny.py @@ -52,21 +52,21 @@ class StarWarsDestiny(): if cmd == "": self.bot.log("Retrieving destiny pool info") with open("gwendolyn/resources/star_wars/destinyPoints.txt","rt") as f: - sendMessage = self.bot.star_wars.roll.resultToEmoji(f.read()) + send_message = self.bot.star_wars.roll.resultToEmoji(f.read()) else: commands = cmd.upper().split(" ") if commands[0] == "N": if len(commands) > 1: - sendMessage = self.destinyNew(int(commands[1])) + send_message = self.destinyNew(int(commands[1])) else: - sendMessage = "You need to give an amount of players (error code 921)" + send_message = "You need to give an amount of players (error code 921)" elif commands[0] == "U": - sendMessage = self.destinyUse(user) + send_message = self.destinyUse(user) else: - sendMessage = "I didn't quite understand that (error code 922)" + send_message = "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:]: + message_list = send_message.split("\n") + await ctx.send(message_list[0]) + if len(message_list) > 1: + for messageItem in message_list[1:]: await ctx.channel.send(messageItem) diff --git a/gwendolyn/funcs/star_wars_funcs/star_wars_roll.py b/gwendolyn/funcs/star_wars_funcs/star_wars_roll.py index bd17710..c923a5c 100644 --- a/gwendolyn/funcs/star_wars_funcs/star_wars_roll.py +++ b/gwendolyn/funcs/star_wars_funcs/star_wars_roll.py @@ -4,7 +4,7 @@ import string import json with open("gwendolyn/resources/star_wars/starwarsskills.json", "r") as f: - skillData = json.load(f) + skill_data = json.load(f) class StarWarsRoll(): def __init__(self, bot): @@ -288,12 +288,12 @@ 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 - sendMessage = "Roll: "+str(roll)+"\nInjury:\n"+results + send_message = "Roll: "+str(roll)+"\nInjury:\n"+results - messageList = sendMessage.split("\n") - await ctx.send(messageList[0]) - if len(messageList) > 1: - for messageItem in messageList[1:]: + message_list = send_message.split("\n") + await ctx.send(message_list[0]) + if len(message_list) > 1: + for messageItem in message_list[1:]: await ctx.channel.send(messageItem) # Parses the command into something the other functions understand @@ -312,19 +312,19 @@ class StarWarsRoll(): rollParameters = [0,0,0,0,0,0,0] if string.capwords(commands[0]) == "Obligations": - sendMessage = self.obligationRoll() + send_message = self.obligationRoll() - elif string.capwords(commands[0]) in skillData: + elif string.capwords(commands[0]) in skill_data: self.bot.log("Oh look! This guy has skills!") if self.bot.star_wars.character.userHasChar(user): self.bot.log("They have a character. That much we know") - skillLevel = self.bot.star_wars.character.charData(user,"Skills " + string.capwords(commands[0])) + skillLevel = self.bot.star_wars.character.char_data(user,"Skills " + string.capwords(commands[0])) if string.capwords(commands[0]) == "Lightsaber": self.bot.log("The skill is lightsaber") - charLevel = self.bot.star_wars.character.charData(user,"Characteristics " + self.bot.star_wars.character.lightsaberChar(user)) + charLevel = self.bot.star_wars.character.char_data(user,"Characteristics " + self.bot.star_wars.character.lightsaberChar(user)) else: - charLevel = self.bot.star_wars.character.charData(user,"Characteristics " + skillData[string.capwords(commands[0])]) + charLevel = self.bot.star_wars.character.char_data(user,"Characteristics " + skill_data[string.capwords(commands[0])]) abilityDice = abs(charLevel-skillLevel) proficiencyDice = min(skillLevel,charLevel) @@ -334,14 +334,14 @@ class StarWarsRoll(): validCommand = True else: self.bot.log("Okay, no they don't i guess") - sendMessage = "You don't have a user. You can make one with /starwarscharacter" + send_message = "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": - sendMessage = "Did you mean \"Ranged - Heavy\" or \"Ranged - Light\" (error code 913)" + send_message = "Did you mean \"Ranged - Heavy\" or \"Ranged - Light\" (error code 913)" else: - sendMessage = "Did you mean \"Piloting - Planetary\" or \"Piloting - Space\" (error code 913)" + send_message = "Did you mean \"Piloting - Planetary\" or \"Piloting - Space\" (error code 913)" else: validCommand = True @@ -372,19 +372,19 @@ class StarWarsRoll(): simplified = self.simplify(rollResults) - name = self.bot.star_wars.character.getCharName(user) + name = self.bot.star_wars.character.getChar_name(user) self.bot.log("Returns results and simplified results") if simplified == "": - sendMessage = name + " rolls: " + "\n" + self.diceResultToEmoji(diceResults) + "\nEverything cancels out!" + send_message = name + " rolls: " + "\n" + self.diceResultToEmoji(diceResults) + "\nEverything cancels out!" else: - sendMessage = name + " rolls: " + "\n" + self.diceResultToEmoji(diceResults) + "\n" + self.resultToEmoji(simplified) + send_message = 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:]: + message_list = send_message.split("\n") + await ctx.send(message_list[0]) + if len(message_list) > 1: + for messageItem in message_list[1:]: if messageItem == "": self.bot.log("Tried to send empty message") else: diff --git a/gwendolyn/resources/help/help-downloading.txt b/gwendolyn/resources/help/help-downloading.txt index 879ce89..b62f3dd 100644 --- a/gwendolyn/resources/help/help-downloading.txt +++ b/gwendolyn/resources/help/help-downloading.txt @@ -1,4 +1,4 @@ -Viser dig de film og serier der er "requested" men ikke endnu på Bedre Netflix. Kommandoen kan tage imod op til 4 parametre: +Viser dig de film og serier der er "requested" men ikke endnu på Plex. Kommandoen kan tage imod op til 4 parametre: `-d`, `--downloading`, `--dm`, `--downloadManager` - Viser de torrents der er _ved_ at downloade. Hvis ingen parametre er givet, bliver det her parameter givet automatisk som det eneste. diff --git a/gwendolyn/resources/help/help-movie.txt b/gwendolyn/resources/help/help-movie.txt index 8fa0eed..6c072de 100644 --- a/gwendolyn/resources/help/help-movie.txt +++ b/gwendolyn/resources/help/help-movie.txt @@ -1 +1 @@ -Giver titlen på en tilfældig film fra Bedre Netflix. \ No newline at end of file +Giver titlen på en tilfældig film fra Plex. \ No newline at end of file diff --git a/gwendolyn/resources/help/help.txt b/gwendolyn/resources/help/help.txt index 8db0a65..74da75f 100644 --- a/gwendolyn/resources/help/help.txt +++ b/gwendolyn/resources/help/help.txt @@ -3,7 +3,7 @@ `/spell` - Slå en besværgelse op. `/monster` - Slå et monster op. `/image` - Finder et tilfældigt billede fra internettet. -`/movie` - Giver titlen på en tilfældig film fra Bedre Netflix +`/movie` - Giver titlen på en tilfældig film fra Plex `/name` - Genererer et tilfældigt navn. `/tavern` - Genererer en tilfældig tavern. `/give` - Lader dig give GwendoBucks til andre. @@ -17,8 +17,8 @@ `/hex` - Lader dig spille et spil Hex. `/hangman` - Lader dig spille et spil hangman. `/wolf` - Lader dig slå ting op på Wolfram Alpha. -`/add_movie` - Lader dig tilføje film til Bedre Netflix. -`/add_show` - Lader dig tilføje tv shows til Bedre Netflix. +`/add_movie` - Lader dig tilføje film til Plex. +`/add_show` - Lader dig tilføje tv shows til Plex. `/downloading` - Viser dig hvor langt de torrents der er ved at downloade er kommet. `/thank` - Lader dig takke Gwendolyn. Du kan få ekstra information om kommandoerne med "/help [kommando]". diff --git a/gwendolyn/resources/longStrings.json b/gwendolyn/resources/longStrings.json deleted file mode 100644 index b5d9c11..0000000 --- a/gwendolyn/resources/longStrings.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "missing parameters" : "Missing command parameters. Try using `!help [command]` to find out how to use the command.", - "Can't log in": "Could not log in. Remember to write your bot token in the credentials.txt file", - "Blackjack all players standing": "All players are standing. The dealer now shows his cards and draws.", - "Blackjack first round": ". You can also double down with \"/blackjack double\" or split with \"/blackjack split\"", - "Blackjack commands": "You have 2 minutes to either hit or stand with \"/blackjack hit\" or \"/blackjack stand\"{}. It's assumed you're standing if you don't make a choice.", - "Blackjack double": "Adding another {} GwendoBucks to {}'s bet and drawing another card.", - "Blackjack different cards": "You can only split if your cards have the same value", - "Blackjack split": "Splitting {}'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.", - "Blackjack started": "Blackjack game started. Use \"/blackjack bet [amount]\" to enter the game within the next 30 seconds.", - "Blackjack going on": "There's already a blackjack game going on. Try again in a few minutes.", - "Stock value": "The current {} stock is valued at **{}** GwendoBucks", - "Stock parameters": "You must give both a stock name and an amount of GwendoBucks you wish to spend.", - "Trivia going on": "There's already a trivia question going on. Try again in like, a minute", - "Trivia time up": "Time's up! The answer was \"*{}) {}*\". Anyone who answered that has gotten 1 GwendoBuck", - "Connect 4 going on": "There's already a connect 4 game going on in this channel", - "Connect 4 placed": "{} placed a piece in column {}. It's now {}'s turn", - "Hangman going on": "There's already a Hangman game going on in the channel", - "Hangman lost game": " You've guessed wrong six times and have lost the game.", - "Hangman guessed word": " You've guessed the word! Congratulations! Adding 15 GwendoBucks to your account" -} \ No newline at end of file diff --git a/gwendolyn/resources/long_strings.json b/gwendolyn/resources/long_strings.json index b5d9c11..c5964a3 100644 --- a/gwendolyn/resources/long_strings.json +++ b/gwendolyn/resources/long_strings.json @@ -17,5 +17,11 @@ "Connect 4 placed": "{} placed a piece in column {}. It's now {}'s turn", "Hangman going on": "There's already a Hangman game going on in the channel", "Hangman lost game": " You've guessed wrong six times and have lost the game.", - "Hangman guessed word": " You've guessed the word! Congratulations! Adding 15 GwendoBucks to your account" + "Hangman guessed word": " You've guessed the word! Congratulations! Adding 15 GwendoBucks to your account", + "Already on Plex": "{} is either already on Plex, downloading, or not available", + "No torrent": "{}No torrent exists. Likely because the movie is not yet released on DVD", + "No torrents downloading": "There are no torrents downloading right. If the torrent you're looking for was added more than 24 hours ago, it might already be on Plex.", + "Update": "{}\nThis message will update every 10 seconds for {} more minutes\n```", + "No updates": "{}\nThis message will not update anymore\n```", + "Invalid parameters": "Invalid or repeated parameters. Use '/help downloading' to see valid parameters." } \ No newline at end of file diff --git a/gwendolyn/resources/slash_parameters.json b/gwendolyn/resources/slash_parameters.json index 119dd88..275b2e9 100644 --- a/gwendolyn/resources/slash_parameters.json +++ b/gwendolyn/resources/slash_parameters.json @@ -1,7 +1,7 @@ { "add_movie" : { "name" : "add_movie", - "description" : "Request a movie for Bedre Netflix", + "description" : "Request a movie for Plex", "options" : [ { "name" : "movie", @@ -13,7 +13,7 @@ }, "add_show" : { "name" : "add_show", - "description" : "Request a show for Bedre Netflix", + "description" : "Request a show for Plex", "options" : [ { "name" : "show", @@ -147,7 +147,7 @@ }, "downloading" : { "name" : "downloading", - "description" : "See current downloads for Bedre Netflix", + "description" : "See current downloads for Plex", "options" : [ { "name" : "parameters", @@ -162,7 +162,7 @@ "description" : "Set the 'playing' text for Gwendolyn", "options" : [ { - "name" : "gameText", + "name" : "game_text", "description" : "The game to set the 'playing' text to", "type" : 3, "required" : "true" diff --git a/gwendolyn/resources/starting_files.json b/gwendolyn/resources/starting_files.json index b22855e..78affaa 100644 --- a/gwendolyn/resources/starting_files.json +++ b/gwendolyn/resources/starting_files.json @@ -65,10 +65,10 @@ "folder" : [ "gwendolyn/resources/lookup", "gwendolyn/resources/games/blackjack_tables", - "gwendolyn/resources/games/connect4Boards", + "gwendolyn/resources/games/connect_four_boards", "gwendolyn/resources/games/hex_boards", "gwendolyn/resources/games/hangman_boards", - "gwendolyn/resources/bedre_netflix", + "gwendolyn/resources/plex", "gwendolyn/resources/games/old_images" ] } \ No newline at end of file diff --git a/gwendolyn/utils/event_handlers.py b/gwendolyn/utils/event_handlers.py index 80e73bb..ca8093b 100644 --- a/gwendolyn/utils/event_handlers.py +++ b/gwendolyn/utils/event_handlers.py @@ -72,7 +72,7 @@ class EventHandler(): channel = message.channel reacted_message = f"{user.display_name} reacted to a message" self.bot.log(reacted_message, str(channel.id)) - plex_data = tests.bedre_netflix_reaction_test(message) + plex_data = tests.plex_reaction_test(message) # plex_data is a list containing 3 elements: whether it was # the add_show/add_movie command message the reaction was to # (bool), whether it's a movie (bool) (if false, it's a @@ -84,10 +84,10 @@ class EventHandler(): if tests.connect_four_reaction_test(*reaction_test_parameters): column = emoji_to_command(reaction.emoji) params = [message, f"#{user.id}", column-1] - await self.bot.games.connect_four.placePiece(*params) + await self.bot.games.connect_four.place_piece(*params) if plex_data[0]: - plex_functions = self.bot.other.bedre_netflix + plex_functions = self.bot.other.plex if plex_data[1]: movie_pick = emoji_to_command(reaction.emoji) if movie_pick == "none": diff --git a/gwendolyn/utils/helper_classes.py b/gwendolyn/utils/helper_classes.py index cf950ed..8f9f535 100644 --- a/gwendolyn/utils/helper_classes.py +++ b/gwendolyn/utils/helper_classes.py @@ -27,7 +27,7 @@ class DatabaseFuncs(): user: discord.User) -> bool hangman_reaction_test(message: discord.Message, user: discord.User) -> bool - bedre_netflix_reaction_test(message: discord.Message, + plex_reaction_test(message: discord.Message, user: discord.User) -> bool, bool, list imdb_commands() @@ -206,7 +206,7 @@ class DatabaseFuncs(): return game_message - def bedre_netflix_reaction_test(self, message: discord.Message): + def plex_reaction_test(self, message: discord.Message): """ Test if the given message is the response to a plex request. @@ -227,7 +227,7 @@ class DatabaseFuncs(): Gwendolyn presented after the request. """ channel = message.channel - old_messages_path = "gwendolyn/resources/bedre_netflix/" + old_messages_path = "gwendolyn/resources/plex/" file_path = old_messages_path + f"old_message{str(channel.id)}" if os.path.isfile(file_path): with open(file_path, "r") as file_pointer: diff --git a/gwendolyn/utils/util_functions.py b/gwendolyn/utils/util_functions.py index cd1d185..d6f62b3 100644 --- a/gwendolyn/utils/util_functions.py +++ b/gwendolyn/utils/util_functions.py @@ -16,7 +16,7 @@ Contains utility functions used by parts of the bot. new_string: str) -> str emoji_to_command(emoji: str) -> str """ -import json # Used by longString(), get_params() and make_files() +import json # Used by long_strings(), get_params() and make_files() import logging # Used for logging import os # Used by make_files() to check if files exist import sys # Used to specify printing for logging diff --git a/requirements.txt b/requirements.txt index ce8b57f..a46b098 100644 --- a/requirements.txt +++ b/requirements.txt @@ -21,7 +21,7 @@ idna==2.10 IMDbPY==2020.9.25 isort==5.8.0 jaraco.context==4.0.0 -lark-parser==0.11.2 +lark-parser==0.9.0 lazy-object-proxy==1.6.0 lxml==4.6.3 mccabe==0.6.1