diff --git a/gwendolyn/ext/better_netflix.py b/gwendolyn/ext/better_netflix.py index 2d8628a..378bb5e 100644 --- a/gwendolyn/ext/better_netflix.py +++ b/gwendolyn/ext/better_netflix.py @@ -1,7 +1,7 @@ from interactions import Extension, slash_command, SlashContext from gwendolyn.utils import PARAMS as params -class BetterNeflixExtension(Extension): +class BetterNetflixExtension(Extension): """Contains the Better Netflix commands.""" @slash_command(**params["better_netflix"]["movie"]) async def movie(self, ctx: SlashContext): @@ -9,4 +9,8 @@ class BetterNeflixExtension(Extension): @slash_command(**params["better_netflix"]["show"]) async def show(self, ctx: SlashContext): - await self.bot.better_netflix.show(ctx) \ No newline at end of file + await self.bot.better_netflix.show(ctx) + + @slash_command(**params["better_netflix"]["downloading"]) + async def downloading(self, ctx: SlashContext): + await self.bot.better_netflix.downloading(ctx) \ No newline at end of file diff --git a/gwendolyn/ext/games.py b/gwendolyn/ext/games.py new file mode 100644 index 0000000..4909c87 --- /dev/null +++ b/gwendolyn/ext/games.py @@ -0,0 +1,12 @@ +from interactions import Extension, slash_command, SlashContext +from gwendolyn.utils import PARAMS as params + +class GamesExtension(Extension): + """Contains the game commands.""" + @slash_command(**params["games"]["money"]["balance"]) + async def balance(self, ctx: SlashContext): + await self.bot.money.sendBalance(ctx) + + @slash_command(**params["games"]["money"]["give"]) + async def give(self, ctx: SlashContext): + await self.bot.money.giveMoney(ctx) diff --git a/gwendolyn/funcs/__init__.py b/gwendolyn/funcs/__init__.py index acbcddb..6395966 100644 --- a/gwendolyn/funcs/__init__.py +++ b/gwendolyn/funcs/__init__.py @@ -1,6 +1,7 @@ """A collection of all Gwendolyn functions.""" -__all__ = ["Other","BetterNetflix", "Sonarr", "Radarr", "TMDb", "QBittorrent"] +__all__ = ["Other","BetterNetflix", "Sonarr", "Radarr", "TMDb", "QBittorrent", "Money"] from .other import Other -from .better_netflix import Radarr, Sonarr, BetterNetflix, TMDb, QBittorrent \ No newline at end of file +from .better_netflix import Radarr, Sonarr, BetterNetflix, TMDb, QBittorrent +from .games import Money, Games \ No newline at end of file diff --git a/gwendolyn/funcs/better_netflix/better_netflix.py b/gwendolyn/funcs/better_netflix/better_netflix.py index af029dc..ae3e96f 100644 --- a/gwendolyn/funcs/better_netflix/better_netflix.py +++ b/gwendolyn/funcs/better_netflix/better_netflix.py @@ -117,7 +117,7 @@ class BetterNetflix(): if "imdb" in picked_movie['ratings']: description += f"<:imdb:1301506320603676782> {picked_movie['ratings']['imdb']['value']}" - description += "/10" + description += "/10 " if "rottenTomatoes" in picked_movie['ratings']: rt_value = picked_movie['ratings']['rottenTomatoes']['value'] @@ -195,7 +195,7 @@ class BetterNetflix(): return progress_bar - message = [""] + message = [] all_downloaded = True dm_section_title = "*Torrent Downloads*" @@ -260,222 +260,45 @@ class BetterNetflix(): torrent["last_activity"] < time()-7200): message.append(torrent_info) - if download_ratio < 1 and torrent["state"] != "stalledDL": - all_downloaded = False + return message - return message, all_downloaded - - async def _generate_download_list(self, show_dm, show_movies, show_shows, - episodes): + async def _generate_download_list(self): """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 = [] + title_width = 90 + message = self._draw_torrent_list(title_width) - if show_dm: - m, all_downloaded = self._draw_torrent_list(title_width) - message += m - - # 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?apiKey="+self.credentials["radarr_key"] - # ).json() - # print( - # self.radarr_url+"movie?apiKey="+self.credentials["radarr_key"] - # ) - # movie_queue = requests.get( - # self.radarr_url+"queue?apiKey="+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?apiKey="+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:])+"```" + message_text = "```"+"\n".join(message)+"```" if message_text == "``````": message_text = self.long_strings["No torrents downloading"] - return message_text, all_downloaded + return message_text - # 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:]) + async def downloading(self, ctx: SlashContext): + """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) + ctx.defer() - # # 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 + message_text = await self._generate_download_list() + if not message_text.startswith("```"): + await ctx.send(message_text) - # 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 + elif len(message_text) > 2000: + message_text = message_text[3:-3] + await send_long_message(ctx,message_text) - # input_args.append(content[arg_start:arg_stop]) - # if arg_stop is None: - # content = "" - # else: - # content = content[arg_stop:] - # else: - # valid_arguments = False + else: + await ctx.send(message_text) - # 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/games/__init__.py b/gwendolyn/funcs/games/__init__.py new file mode 100644 index 0000000..413607e --- /dev/null +++ b/gwendolyn/funcs/games/__init__.py @@ -0,0 +1,11 @@ +"""Game functions for Gwendolyn.""" + +__all__ = ["Money", "Games"] + +from .money import Money + +class Games(): + def __init__(self, bot): + """Initialize the class.""" + self.bot = bot + self.database = bot.database \ No newline at end of file diff --git a/gwendolyn/funcs/games/money.py b/gwendolyn/funcs/games/money.py new file mode 100644 index 0000000..3a1468a --- /dev/null +++ b/gwendolyn/funcs/games/money.py @@ -0,0 +1,150 @@ +""" +Contains the code that deals with money. + +*Classes* +--------- + Money + Deals with money. +""" +import interactions # Used for typehints + + +class Money(): + """ + Deals with money. + + *Methods* + --------- + checkBalance(user: str) + sendBalance(ctx: interactions.SlashContext) + addMoney(user: str, amount: int) + giveMoney(ctx: interactions.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 + + 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") + + user_data = self.database["users"].find_one({"_id": user}) + + if user_data is not None: + return user_data["money"] + else: + return 0 + + async def sendBalance(self, ctx: interactions.SlashContext): + """ + Get your own account balance. + + *Parameters* + ------------ + ctx: interactions.SlashContext + The context of the command. + """ + await ctx.defer() + response = self.checkBalance("#"+str(ctx.author.id)) + user_name = ctx.author.display_name + if response == 1: + new_message = f"{user_name} has {response} GwendoBuck" + else: + new_message = f"{user_name} has {response} GwendoBucks" + await ctx.send(new_message) + + # Adds money to the account of a user + 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") + + user_data = self.database["users"].find_one({"_id": user}) + + if user_data is not None: + updater = {"$inc": {"money": amount}} + self.database["users"].update_one({"_id": user}, updater) + else: + new_user = { + "_id": user, + "user name": self.bot.database_funcs.get_name(user), + "money": amount + } + self.database["users"].insert_one(new_user) + + # Transfers money from one user to another + async def giveMoney(self, ctx: interactions.SlashContext, + user: interactions.User, amount: int): + """ + Give someone else money from your account. + + *Parameters* + ------------ + ctx: interactions.SlashContext + The context of the command. + user: interactions.User + The user to give money. + amount: int + The amount to transfer. + """ + await ctx.defer() + username = user.display_name + 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 + user_id = f"#{member.id}" + new_user = { + "_id": user_id, + "user name": username, + "money": 0 + } + self.bot.database["users"].insert_one(new_user) + + userid = f"#{ctx.author.id}" + user_data = self.database["users"].find_one({"_id": userid}) + targetUser = self.bot.database_funcs.get_id(username) + + 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 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: + self.addMoney(f"#{ctx.author.id}", -1 * amount) + self.addMoney(targetUser, amount) + await ctx.send(f"Transferred {amount} GwendoBucks to {username}") diff --git a/gwendolyn/funcs/other/other.py b/gwendolyn/funcs/other/other.py index d74a24d..68cc2c1 100644 --- a/gwendolyn/funcs/other/other.py +++ b/gwendolyn/funcs/other/other.py @@ -7,59 +7,59 @@ from .name_generator import NameGenerator from .roll import DieRoller def gen_help_text(commands: list[SlashCommand]): - return '\n'.join([f"`/{i.name}`\t— {i.description}" for i in commands]) + return '\n'.join(sorted([f"`/{i.name}`\t— {i.description}" for i in commands])) class Other(): - def __init__(self, bot): - self.bot = bot - self._name_generator = NameGenerator() - self._roller = DieRoller() + def __init__(self, bot): + self.bot = bot + self._name_generator = NameGenerator() + self._roller = DieRoller() - # Responds with a greeting of a time-appropriate maner - async def hello_func(self, ctx: SlashContext): - def time_in_range(start, end, i): - # Return true if i is in the range [start, end] - if start <= end: - return start <= i <= end - else: - return start <= i or i <= end + # Responds with a greeting of a time-appropriate maner + async def hello_func(self, ctx: SlashContext): + def time_in_range(start, end, i): + # Return true if i is in the range [start, end] + if start <= end: + return start <= i <= end + else: + return start <= i or i <= end - author = ctx.author.display_name - now = datetime.datetime.now() - if time_in_range(now.replace(hour=5, minute=0, second=0, microsecond=0),now.replace(hour=10, minute=0, second=0, microsecond=0), now): - 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): - 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): - 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): - send_message = "Good night, "+str(author) - else: - send_message = "Hello, "+str(author) + 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): + 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): + 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): + 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): + send_message = "Good night, "+str(author) + else: + send_message = "Hello, "+str(author) - await ctx.send(send_message) + await ctx.send(send_message) - async def help_func(self, ctx: SlashContext, command: str): - if command == "": - text = gen_help_text([i for i in self.bot.application_commands if isinstance(i,SlashCommand)]) - embed = Embed(title = "Help", description = text,color = 0x59f442) - await ctx.send(embed = embed) - else: - self.bot.log(f"Looking for help-{command}.txt",str(ctx.channel_id)) - try: - with open(f"gwendolyn/resources/help/help-{command}.txt",encoding="utf-8") as file_pointer: - text = file_pointer.read() - embed = Embed(title = command.capitalize(), description = text,color = 0x59f442) - await ctx.send(embed = embed) - except: - await ctx.send(f"Could not find a help file for the command '/{command}'") + async def help_func(self, ctx: SlashContext, command: str): + if command == "": + text = gen_help_text([i for i in self.bot.application_commands if isinstance(i,SlashCommand)]) + embed = Embed(title = "Help", description = text,color = 0x59f442) + await ctx.send(embed = embed) + else: + self.bot.log(f"Looking for help-{command}.txt",str(ctx.channel_id)) + try: + with open(f"gwendolyn/resources/help/help-{command}.txt",encoding="utf-8") as file_pointer: + text = file_pointer.read() + embed = Embed(title = command.capitalize(), description = text,color = 0x59f442) + await ctx.send(embed = embed) + except: + await ctx.send(f"Could not find a help file for the command '/{command}'") - async def generate_name(self, ctx: SlashContext): - await ctx.send(self._name_generator.generate()) + async def generate_name(self, ctx: SlashContext): + await ctx.send(self._name_generator.generate()) - async def roll_dice(self, ctx: SlashContext, dice: str): - try: - roll = self._roller.roll(dice) - await ctx.send(f":game_die:Rolling dice `{dice}`\n{roll}") - except: - await ctx.send("There was an error in the rolling") + async def roll_dice(self, ctx: SlashContext, dice: str): + try: + roll = self._roller.roll(dice) + await ctx.send(f":game_die:Rolling dice `{dice}`\n{roll}") + except: + await ctx.send("There was an error in the rolling") diff --git a/gwendolyn/gwendolyn_client.py b/gwendolyn/gwendolyn_client.py index f04188d..9df42e1 100644 --- a/gwendolyn/gwendolyn_client.py +++ b/gwendolyn/gwendolyn_client.py @@ -6,71 +6,71 @@ from pymongo import MongoClient # Used for database management from gwendolyn.utils import log from gwendolyn.exceptions import NoToken, CannotConnectToService -from gwendolyn.funcs import Other, BetterNetflix, Sonarr, Radarr, TMDb, QBittorrent +from gwendolyn.funcs import Other, BetterNetflix, Sonarr, Radarr, TMDb, QBittorrent, Money, Games class Gwendolyn(Client): - def __init__(self, testing: bool = True): - """Initialize the bot.""" - initiation_parameters = { - "status": Status.DND, - "delete_unused_application_cmds": True - } - super().__init__(**initiation_parameters) + def __init__(self, testing: bool = True): + """Initialize the bot.""" + initiation_parameters = { + "status": Status.DND, + "delete_unused_application_cmds": True + } + super().__init__(**initiation_parameters) - self.testing = testing + self.testing = testing - self._add_clients_and_options() - self._add_functions() - self._add_extensions() + self._add_clients_and_options() + self._add_functions() + self._add_extensions() - def _add_clients_and_options(self): - """Add all the client, option and credentials objects.""" - load_dotenv() - self.bot_token = getenv("DISCORD_TOKEN") + def _add_clients_and_options(self): + """Add all the client, option and credentials objects.""" + load_dotenv() + self.bot_token = getenv("DISCORD_TOKEN") - if self.bot_token == "TOKEN": - raise NoToken() + if self.bot_token == "TOKEN": + raise NoToken() - self.admins = getenv("ADMINS").split(",") + self.admins = getenv("ADMINS").split(",") - mongo_user = getenv("MONGODB_USER") - mongo_password = getenv("MONGODB_PASSWORD") - mongo_url = f"mongodb+srv://{mongo_user}:{mongo_password}@gwendolyn" - mongo_url += ".qkwfy.mongodb.net/Gwendolyn?retryWrites=true&w=majority" - database_client = MongoClient(mongo_url) + mongo_user = getenv("MONGODB_USER") + mongo_password = getenv("MONGODB_PASSWORD") + mongo_url = f"mongodb+srv://{mongo_user}:{mongo_password}@gwendolyn" + mongo_url += ".qkwfy.mongodb.net/Gwendolyn?retryWrites=true&w=majority" + database_client = MongoClient(mongo_url) - try: - database_client.admin.command("ping") - self.log("Connected to Mango Client") - except: - raise CannotConnectToService("Mango Client") + try: + database_client.admin.command("ping") + self.log("Connected to Mango Client") + except: + raise CannotConnectToService("Mango Client") - if self.testing: - self.log("Testing mode") - self.database = database_client["Gwendolyn-Test"] - self.load_extension("interactions.ext.jurigged") - else: - self.database = database_client["Gwendolyn"] + if self.testing: + self.log("Testing mode") + self.database = database_client["Gwendolyn-Test"] + else: + self.database = database_client["Gwendolyn"] - def _add_functions(self): - self.other = Other(self) - self.better_netflix = BetterNetflix( - self, - radarr=Radarr(getenv("RADARR_IP"),getenv("RADARR_PORT"),getenv("RADARR_API_KEY")), - sonarr=Sonarr(getenv("SONARR_IP"),getenv("SONARR_PORT"),getenv("SONARR_API_KEY")), - tmdb=TMDb(getenv("TMDB_API_ACCESS_TOKEN")), - qbittorrent=QBittorrent(getenv("QBITTORRENT_IP"),getenv("QBITTORRENT_PORT"),getenv("QBITTORRENT_USERNAME"),getenv("QBITTORRENT_PASSWORD")) - ) + def _add_functions(self): + self.other = Other(self) + self.money = Money(self) + self.better_netflix = BetterNetflix( + self, + radarr=Radarr(getenv("RADARR_IP"),getenv("RADARR_PORT"),getenv("RADARR_API_KEY")), + sonarr=Sonarr(getenv("SONARR_IP"),getenv("SONARR_PORT"),getenv("SONARR_API_KEY")), + tmdb=TMDb(getenv("TMDB_API_ACCESS_TOKEN")), + qbittorrent=QBittorrent(getenv("QBITTORRENT_IP"),getenv("QBITTORRENT_PORT"),getenv("QBITTORRENT_USERNAME"),getenv("QBITTORRENT_PASSWORD")) + ) - def _add_extensions(self): - """Load cogs.""" - for filename in listdir("./gwendolyn/ext"): - if filename.endswith(".py"): - self.load_extension(f"gwendolyn.ext.{filename[:-3]}") + def _add_extensions(self): + """Load cogs.""" + for filename in listdir("./gwendolyn/ext"): + if filename.endswith(".py"): + self.load_extension(f"gwendolyn.ext.{filename[:-3]}") - def log(self, messages, channel: str = "", level: int = 20): # pylint:disable=no-self-use - """Log a message. Described in utils/util_functions.py.""" - log(messages, channel, level) + def log(self, messages, channel: str = "", level: int = 20): # pylint:disable=no-self-use + """Log a message. Described in utils/util_functions.py.""" + log(messages, channel, level) - def start(self): - super().start(self.bot_token) \ No newline at end of file + def start(self): + super().start(self.bot_token) \ No newline at end of file diff --git a/gwendolyn/resources/slash_parameters.json b/gwendolyn/resources/slash_parameters.json index 60cf99c..4492112 100644 --- a/gwendolyn/resources/slash_parameters.json +++ b/gwendolyn/resources/slash_parameters.json @@ -34,7 +34,7 @@ }, "ping" : { "name" : "ping", - "description" : "Get the Gwendolyn's latency to the server" + "description" : "Get Gwendolyn's latency to the server" }, "roll" : { "name": "roll", @@ -65,6 +65,36 @@ "show": { "name": "show", "description": "Get a random show from Better Netflix" + }, + "downloading": { + "name": "downloading", + "description": "Get a list of everything being downloaded" + } + }, + "games": { + "money": { + "balance" : { + "name" : "balance", + "description" : "See your balance of GwendoBucks" + }, + "give" : { + "name" : "give", + "description" : "Give GwendoBucks to another user", + "options" : [ + { + "name" : "user", + "description" : "The user you're sending GwendoBucks to", + "type" : 6, + "required" : "true" + }, + { + "name" : "amount", + "description" : "The number of GwendoBucks you're sending", + "type" : 4, + "required" : "true" + } + ] + } } } } \ No newline at end of file diff --git a/gwendolyn/resources/temp.jpg b/gwendolyn/resources/temp.jpg index 2018f59..0ed3ea2 100644 Binary files a/gwendolyn/resources/temp.jpg and b/gwendolyn/resources/temp.jpg differ diff --git a/gwendolyn_old/cogs/event_cog.py b/gwendolyn_old/cogs/event_cog.py index 525a9b6..6302a65 100644 --- a/gwendolyn_old/cogs/event_cog.py +++ b/gwendolyn_old/cogs/event_cog.py @@ -3,38 +3,38 @@ from discord.ext import commands # Has the cog class class EventCog(commands.Cog): - """Handles bot events.""" + """Handles bot events.""" - def __init__(self, bot): - """Initialize the bot.""" - self.bot = bot - self.bot.on_error = self.on_error + def __init__(self, bot): + """Initialize the bot.""" + self.bot = bot + self.bot.on_error = self.on_error - @commands.Cog.listener() - async def on_ready(self): - """Log and set bot status when bot logs in.""" - await self.bot.event_handler.on_ready() + @commands.Cog.listener() + async def on_ready(self): + """Log and set bot status when bot logs in.""" + 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.event_handler.on_slash_command(ctx) + @commands.Cog.listener() + async def on_slash_command(self, ctx): + """Log when a slash command is run.""" + 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.error_handler.on_slash_command_error(ctx, error) + @commands.Cog.listener() + async def on_slash_command_error(self, ctx, error): + """Log when a slash error occurs.""" + await self.bot.error_handler.on_slash_command_error(ctx, error) - 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) + 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) - @commands.Cog.listener() - async def on_component(self, ctx): - """Handle when someone reacts to a message.""" - await self.bot.event_handler.on_component(ctx) + @commands.Cog.listener() + async def on_component(self, ctx): + """Handle when someone reacts to a message.""" + await self.bot.event_handler.on_component(ctx) def setup(bot): - """Add the eventcog to the bot.""" - bot.add_cog(EventCog(bot)) + """Add the eventcog to the bot.""" + bot.add_cog(EventCog(bot)) diff --git a/gwendolyn_old/cogs/game_cog.py b/gwendolyn_old/cogs/game_cog.py index e9060e0..e4d6c75 100644 --- a/gwendolyn_old/cogs/game_cog.py +++ b/gwendolyn_old/cogs/game_cog.py @@ -8,153 +8,153 @@ params = get_params() class GamesCog(commands.Cog): - """Contains miscellaneous game commands.""" + """Contains miscellaneous game commands.""" - def __init__(self, bot): - """Initialize the cog.""" - self.bot = bot + def __init__(self, bot): + """Initialize the cog.""" + self.bot = bot - @cog_ext.cog_slash(**params["balance"]) - async def balance(self, ctx): - """Check user balance.""" - await self.bot.money.sendBalance(ctx) + @cog_ext.cog_slash(**params["balance"]) + async def balance(self, ctx): + """Check user balance.""" + await self.bot.money.sendBalance(ctx) - @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) + @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) - @cog_ext.cog_slash(**params["trivia"]) - async def trivia(self, ctx, answer=""): - """Run a game of trivia.""" - await self.bot.games.trivia.triviaParse(ctx, answer) + @cog_ext.cog_slash(**params["trivia"]) + async def trivia(self, ctx, answer=""): + """Run a game of trivia.""" + await self.bot.games.trivia.triviaParse(ctx, answer) class BlackjackCog(commands.Cog): - """Contains the blackjack commands.""" + """Contains the blackjack commands.""" - def __init__(self, bot): - """Initialize the cog.""" - self.bot = bot + def __init__(self, bot): + """Initialize the cog.""" + self.bot = bot - @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["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["blackjack_bet"]) - async def blackjack_bet(self, ctx, bet): - """Enter the game of blackjack with a bet.""" - await self.bot.games.blackjack.enter_game(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.enter_game(ctx, bet) - @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["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["blackjack_shuffle"]) - async def blackjack_shuffle(self, ctx): - """Shuffle the blackjack game.""" - await self.bot.games.blackjack.shuffle(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["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) + @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) class ConnectFourCog(commands.Cog): - """Contains all the connect four commands.""" + """Contains all the connect four commands.""" - def __init__(self, bot): - """Initialize the cog.""" - self.bot = bot + def __init__(self, bot): + """Initialize the cog.""" + self.bot = bot - @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_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_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_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) class HangmanCog(commands.Cog): - """Contains all the hangman commands.""" + """Contains all the hangman commands.""" - def __init__(self, bot): - """Initialize the cog.""" - self.bot = bot + def __init__(self, bot): + """Initialize the cog.""" + self.bot = bot - @cog_ext.cog_slash(**params["hangman"]) - async def hangman(self, ctx): - """Start a game of hangman.""" - await self.bot.games.hangman.start(ctx) + @cog_ext.cog_slash(**params["hangman"]) + async def hangman(self, ctx): + """Start a game of hangman.""" + await self.bot.games.hangman.start(ctx) class WordleCog(commands.Cog): - """Contains all the wordle commands.""" + """Contains all the wordle commands.""" - def __init__(self, bot): - """Initialize the cog.""" - self.bot = bot + def __init__(self, bot): + """Initialize the cog.""" + self.bot = bot - @cog_ext.cog_subcommand(**params["wordle_start"]) - async def wordle_start(self, ctx, letters = 5): - """Start a game of wordle.""" - await self.bot.games.wordle.start(ctx, letters) + @cog_ext.cog_subcommand(**params["wordle_start"]) + async def wordle_start(self, ctx, letters = 5): + """Start a game of wordle.""" + await self.bot.games.wordle.start(ctx, letters) - @cog_ext.cog_subcommand(**params["wordle_guess"]) - async def wordle_guess(self, ctx, guess): - """Start a game of wordle.""" - await self.bot.games.wordle.guess(ctx, guess) + @cog_ext.cog_subcommand(**params["wordle_guess"]) + async def wordle_guess(self, ctx, guess): + """Start a game of wordle.""" + await self.bot.games.wordle.guess(ctx, guess) class HexCog(commands.Cog): - """Contains all the hex commands.""" + """Contains all the hex commands.""" - def __init__(self, bot): - """Initialize the cog.""" - self.bot = bot - self.hex = self.bot.games.hex + def __init__(self, bot): + """Initialize the cog.""" + self.bot = bot + self.hex = self.bot.games.hex - @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["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["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["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["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["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["hex_undo"]) - async def hex_undo(self, ctx): - """Undo your last hex move.""" - await self.hex.undo(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["hex_swap"]) - async def hex_swap(self, ctx): - """Perform a hex swap.""" - await self.hex.swap(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["hex_surrender"]) - async def hex_surrender(self, ctx): - """Surrender the hex game.""" - await self.hex.surrender(ctx) + @cog_ext.cog_subcommand(**params["hex_surrender"]) + async def hex_surrender(self, 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)) - bot.add_cog(WordleCog(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)) + bot.add_cog(WordleCog(bot)) diff --git a/gwendolyn_old/cogs/lookup_cog.py b/gwendolyn_old/cogs/lookup_cog.py index 79a0857..9a867ee 100644 --- a/gwendolyn_old/cogs/lookup_cog.py +++ b/gwendolyn_old/cogs/lookup_cog.py @@ -8,25 +8,25 @@ params = get_params() class LookupCog(commands.Cog): - """Contains the lookup commands.""" + """Contains the lookup commands.""" - def __init__(self, bot): - """Initialize the cog.""" - self.bot = bot + def __init__(self, bot): + """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.lookup_funcs.spell_func(ctx, query) + # Looks up a spell + @cog_ext.cog_slash(**params["spell"]) + async def spell(self, ctx, query): + """Look up a spell.""" + await self.bot.lookup_funcs.spell_func(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.lookup_funcs.monster_func(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.lookup_funcs.monster_func(ctx, query) def setup(bot): - """Add the cog to the bot.""" - bot.add_cog(LookupCog(bot)) + """Add the cog to the bot.""" + bot.add_cog(LookupCog(bot)) diff --git a/gwendolyn_old/cogs/misc_cog.py b/gwendolyn_old/cogs/misc_cog.py index a770867..7490629 100644 --- a/gwendolyn_old/cogs/misc_cog.py +++ b/gwendolyn_old/cogs/misc_cog.py @@ -9,92 +9,92 @@ params = get_params() class MiscCog(commands.Cog): - """Contains the miscellaneous commands.""" + """Contains the miscellaneous commands.""" - def __init__(self, bot): - """Initialize the cog.""" - self.bot = bot - self.bot.remove_command("help") - self.generators = bot.other.generators - self.plex = bot.other.plex - self.nerd_shit = bot.other.nerd_shit + def __init__(self, bot): + """Initialize the cog.""" + self.bot = bot + self.bot.remove_command("help") + self.generators = bot.other.generators + self.plex = bot.other.plex + self.nerd_shit = bot.other.nerd_shit - @cog_ext.cog_slash(**params["ping"]) - async def ping(self, ctx: SlashContext): - """Send the bot's latency.""" - await ctx.send(f"Pong!\nLatency is {round(self.bot.latency * 1000)}ms") + @cog_ext.cog_slash(**params["ping"]) + async def ping(self, ctx: SlashContext): + """Send the bot's latency.""" + await ctx.send(f"Pong!\nLatency is {round(self.bot.latency * 1000)}ms") - @cog_ext.cog_slash(**params["stop"]) - async def stop(self, ctx: SlashContext): - """Stop the bot.""" - await self.bot.stop(ctx) + @cog_ext.cog_slash(**params["stop"]) + async def stop(self, ctx: SlashContext): + """Stop the bot.""" + await self.bot.stop(ctx) - @cog_ext.cog_slash(**params["help"]) - async def help_command(self, ctx: SlashContext, command=""): - """Get help for commands.""" - await self.bot.other.help_func(ctx, command) + @cog_ext.cog_slash(**params["help"]) + async def help_command(self, ctx: SlashContext, command=""): + """Get help for commands.""" + await self.bot.other.help_func(ctx, command) - @cog_ext.cog_slash(**params["thank"]) - async def thank(self, ctx: SlashContext): - """Thank the bot.""" - await ctx.send("You're welcome :blush:") + @cog_ext.cog_slash(**params["thank"]) + async def thank(self, ctx: SlashContext): + """Thank the bot.""" + await ctx.send("You're welcome :blush:") - @cog_ext.cog_slash(**params["hello"]) - async def hello(self, ctx: SlashContext): - """Greet the bot.""" - await self.bot.other.hello_func(ctx) + @cog_ext.cog_slash(**params["hello"]) + async def hello(self, ctx: SlashContext): + """Greet the bot.""" + await self.bot.other.hello_func(ctx) - @cog_ext.cog_slash(**params["roll"]) - async def roll(self, ctx: SlashContext, dice="1d20"): - """Roll dice.""" - await self.bot.other.roll_dice(ctx, dice) + @cog_ext.cog_slash(**params["roll"]) + async def roll(self, ctx: SlashContext, dice="1d20"): + """Roll dice.""" + await self.bot.other.roll_dice(ctx, dice) - @cog_ext.cog_slash(**params["image"]) - async def image(self, ctx: SlashContext): - """Get a random image from Bing.""" - await self.bot.other.image_func(ctx) + @cog_ext.cog_slash(**params["image"]) + async def image(self, ctx: SlashContext): + """Get a random image from Bing.""" + await self.bot.other.image_func(ctx) - @cog_ext.cog_slash(**params["movie"]) - async def movie(self, ctx: SlashContext): - """Get a random movie from the Plex server.""" - await self.bot.other.movie_func(ctx) + @cog_ext.cog_slash(**params["movie"]) + async def movie(self, ctx: SlashContext): + """Get a random movie from the Plex server.""" + await self.bot.other.movie_func(ctx) - @cog_ext.cog_slash(**params["name"]) - async def name(self, ctx: SlashContext): - """Generate a random name.""" - await self.generators.name_gen(ctx) + @cog_ext.cog_slash(**params["name"]) + async def name(self, ctx: SlashContext): + """Generate a random name.""" + await self.generators.name_gen(ctx) - @cog_ext.cog_slash(**params["tavern"]) - async def tavern(self, ctx: SlashContext): - """Generate a random tavern name.""" - await self.generators.tavern_gen(ctx) + @cog_ext.cog_slash(**params["tavern"]) + async def tavern(self, ctx: SlashContext): + """Generate a random tavern name.""" + await self.generators.tavern_gen(ctx) - @cog_ext.cog_slash(**params["wiki"]) - async def wiki(self, ctx: SlashContext, wiki_page=""): - """Get a page on a fandom wiki.""" - await self.bot.other.find_wiki_page(ctx, wiki_page) + @cog_ext.cog_slash(**params["wiki"]) + async def wiki(self, ctx: SlashContext, wiki_page=""): + """Get a page on a fandom wiki.""" + await self.bot.other.find_wiki_page(ctx, wiki_page) - @cog_ext.cog_slash(**params["add_movie"]) - async def add_movie(self, ctx: SlashContext, movie): - """Search for a movie and add it to the Plex server.""" - await self.plex.request_movie(ctx, movie) + @cog_ext.cog_slash(**params["add_movie"]) + async def add_movie(self, ctx: SlashContext, movie): + """Search for a movie and add it to the Plex server.""" + await self.plex.request_movie(ctx, movie) - @cog_ext.cog_slash(**params["add_show"]) - async def add_show(self, ctx: SlashContext, show): - """Search for a show and add it to the Plex server.""" - await self.plex.request_show(ctx, show) + @cog_ext.cog_slash(**params["add_show"]) + async def add_show(self, ctx: SlashContext, show): + """Search for a show and add it to the Plex server.""" + await self.plex.request_show(ctx, show) - @cog_ext.cog_slash(**params["downloading"]) - async def downloading(self, ctx: SlashContext, parameters="-d"): - """Get the current downloading torrents.""" - await self.plex.downloading(ctx, parameters) + @cog_ext.cog_slash(**params["downloading"]) + async def downloading(self, ctx: SlashContext, parameters="-d"): + """Get the current downloading torrents.""" + await self.plex.downloading(ctx, parameters) - @cog_ext.cog_slash(**params["wolf"]) - async def wolf(self, ctx: SlashContext, query): - """Perform a search on Wolfram Alpha.""" - await self.nerd_shit.wolf_search(ctx, query) + @cog_ext.cog_slash(**params["wolf"]) + async def wolf(self, ctx: SlashContext, query): + """Perform a search on Wolfram Alpha.""" + await self.nerd_shit.wolf_search(ctx, query) def setup(bot): - """Add the cog to the bot.""" - bot.add_cog(MiscCog(bot)) + """Add the cog to the bot.""" + bot.add_cog(MiscCog(bot)) diff --git a/gwendolyn_old/cogs/star_wars_cog.py b/gwendolyn_old/cogs/star_wars_cog.py index 1440f65..09d375e 100644 --- a/gwendolyn_old/cogs/star_wars_cog.py +++ b/gwendolyn_old/cogs/star_wars_cog.py @@ -8,33 +8,33 @@ params = get_params() class StarWarsCog(commands.Cog): - """Contains the Star Wars commands.""" + """Contains the Star Wars commands.""" - def __init__(self, bot): - """Initialize the cog.""" - self.bot = bot + 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_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_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_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) + @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)) + """Add the cog to the bot.""" + bot.add_cog(StarWarsCog(bot)) diff --git a/gwendolyn_old/exceptions.py b/gwendolyn_old/exceptions.py index c7340ee..584bc97 100644 --- a/gwendolyn_old/exceptions.py +++ b/gwendolyn_old/exceptions.py @@ -1,11 +1,11 @@ """Exceptions for Gwendolyn""" class GameNotInDatabase(Exception): - def __init__(self, game: str, channel: str): - self.message = f"There is no {game} game in channel {channel}" - super().__init__(self.message) + def __init__(self, game: str, channel: str): + self.message = f"There is no {game} game in channel {channel}" + super().__init__(self.message) class InvalidInteraction(Exception): - def __init__(self, custom_id: str, decoded: str): - self.message = f"{custom_id = }, {decoded = }" - super().__init__(self.message) + def __init__(self, custom_id: str, decoded: str): + self.message = f"{custom_id = }, {decoded = }" + super().__init__(self.message) diff --git a/gwendolyn_old/funcs/games/blackjack.py b/gwendolyn_old/funcs/games/blackjack.py index 6b2bd4e..11e84f9 100644 --- a/gwendolyn_old/funcs/games/blackjack.py +++ b/gwendolyn_old/funcs/games/blackjack.py @@ -3,10 +3,10 @@ Runs commands, game logic and imaging for blackjack games. *Classes* --------- - Blackjack - Contains the blackjack game logic. - DrawBlackjack - Draws images of the blackjack table. + Blackjack + Contains the blackjack game logic. + DrawBlackjack + Draws images of the blackjack table. """ import math # Used for flooring decimal numbers import datetime # Used to generate the game id @@ -28,939 +28,939 @@ RED = (255, 50, 50) GOLD = (155, 123, 0) def _is_round_done(game: dict): + """ + Find out if the round is done. + + *Parameters* + ------------ + game: dict + The game to check. + + *Returns* + --------- + round_done: bool + Whether the round is done. + """ + return all( + hand["hit"] or hand["standing"] + for user_hands in game["user hands"].values() for hand in user_hands + ) + +class Blackjack(CardGame): + """ + Deals with blackjack commands and gameplay logic. + + *Methods* + --------- + hit(ctx: IntCont, hand_number: int = 0) + double(ctx: IntCont, hand_number: int = 0) + stand(ctx: IntCont, hand_number: int = 0) + split(ctx: IntCont, hand_number: int = 0) + enter_game(ctx: IntCont, bet: int) + start(ctx: IntCont) + hilo(ctx: IntCont) + shuffle(ctx: IntCont) + cards(ctx: IntCont) + """ + + def __init__(self, bot): + """Initialize the class.""" + super().__init__(bot, "blackjack", DrawBlackjack, 4) + default_buttons = ["Hit", "Stand", "Double", "Split"] + self.default_buttons = [(i, [i, "0"], 1) for i in default_buttons] + + async def _test_command(self, game: dict, user: str, command: str, + hand_number: int, ctx: IntCont): #pylint:disable=too-many-arguments + valid_command = False + + if user not in game["user hands"]: + self.bot.log(f"They tried to {command} without being in the game") + send_msg = f"You have to enter the game before you can {command}" + elif len(game["user hands"][user]) < hand_number: + self.bot.log(f"They tried to {command} with a hand they don't have") + send_msg = "You don't have that many hands" + elif game["round"] <= 0: + self.bot.log(f"They tried to {command} on the 0th round") + send_msg = "You haven't seen your cards yet!" + elif len(game["user hands"][user]) > 1 and hand_number == 0: + self._get_hand_number(ctx, command, game["user hands"][user]) + return False + elif game["user hands"][user][max(hand_number-1,0)]["hit"]: + self.bot.log("They've already hit this round") + send_msg = "You've already hit this round" + elif game["user hands"][user][max(hand_number-1,0)]["standing"]: + self.bot.log("They're already standing") + send_msg = "You're already standing" + else: + valid_command = True + + if not valid_command: + await ctx.send(send_msg, hidden=True) + + return valid_command + + def _shuffle_cards(self, channel: str): """ - Find out if the round is done. + Shuffle an amount of decks equal to self.decks_used. + + The shuffled cards are placed in the database as the cards that + are used by other blackjack functions. *Parameters* ------------ - game: dict - The game to check. + channel: str + The id of the channel where the cards will be used. + """ + super()._shuffle_cards(channel) + + # Creates hilo file + self.bot.log(f"creating hilo doc for {channel}") + hilo_updater = {"$set": {"_id": channel, "hilo": 0}} + self._update_document(channel, hilo_updater, "hilo") + + def _calc_hand_value(self, hand: list): + """ + Calculate the value of a blackjack hand. + + *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* --------- - round_done: bool - Whether the round is done. + hand_value: int + The blackjack value of the hand. """ - return all( - hand["hit"] or hand["standing"] - for user_hands in game["user hands"].values() for hand in user_hands - ) + values = [0] -class Blackjack(CardGame): + for card in hand: + card_value = replace_multiple(card[0], ["0", "k", "q", "j"], "10") + if card_value == "a": + values = [i + 1 for i in values] + [i + 11 for i in values] + else: + values = [i+int(card_value) for i in values] + + valid_values = [i for i in values if i <= 21] + hand_value = max(valid_values) if valid_values else min(values) + + self.bot.log(f"Calculated the value of {hand} to be {hand_value}") + + return hand_value + + def _draw_card(self, channel: str): + drawn_card = super()._draw_card(channel) + hilo_value = min(1,-1-((self._calc_hand_value([drawn_card])-10)//3)) + self._update_document(channel, {"$inc": {"hilo": hilo_value}}, "hilo") + + return drawn_card + + def _dealer_draw(self, channel: str): """ - Deals with blackjack commands and gameplay logic. + Draw a card for the dealer. - *Methods* + *Parameters* + ------------ + channel: str + The id of the channel to draw a card for the dealer in. + + *Returns* --------- - hit(ctx: IntCont, hand_number: int = 0) - double(ctx: IntCont, hand_number: int = 0) - stand(ctx: IntCont, hand_number: int = 0) - split(ctx: IntCont, hand_number: int = 0) - enter_game(ctx: IntCont, bet: int) - start(ctx: IntCont) - hilo(ctx: IntCont) - shuffle(ctx: IntCont) - cards(ctx: IntCont) + done: bool + Whether the dealer is done drawing cards. """ - - def __init__(self, bot): - """Initialize the class.""" - super().__init__(bot, "blackjack", DrawBlackjack, 4) - default_buttons = ["Hit", "Stand", "Double", "Split"] - self.default_buttons = [(i, [i, "0"], 1) for i in default_buttons] - - async def _test_command(self, game: dict, user: str, command: str, - hand_number: int, ctx: IntCont): #pylint:disable=too-many-arguments - valid_command = False - - if user not in game["user hands"]: - self.bot.log(f"They tried to {command} without being in the game") - send_msg = f"You have to enter the game before you can {command}" - elif len(game["user hands"][user]) < hand_number: - self.bot.log(f"They tried to {command} with a hand they don't have") - send_msg = "You don't have that many hands" - elif game["round"] <= 0: - self.bot.log(f"They tried to {command} on the 0th round") - send_msg = "You haven't seen your cards yet!" - elif len(game["user hands"][user]) > 1 and hand_number == 0: - self._get_hand_number(ctx, command, game["user hands"][user]) - return False - elif game["user hands"][user][max(hand_number-1,0)]["hit"]: - self.bot.log("They've already hit this round") - send_msg = "You've already hit this round" - elif game["user hands"][user][max(hand_number-1,0)]["standing"]: - self.bot.log("They're already standing") - send_msg = "You're already standing" - else: - valid_command = True - - if not valid_command: - await ctx.send(send_msg, hidden=True) - - return valid_command - - def _shuffle_cards(self, channel: str): - """ - Shuffle an amount of decks equal to self.decks_used. - - 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. - """ - super()._shuffle_cards(channel) - - # Creates hilo file - self.bot.log(f"creating hilo doc for {channel}") - hilo_updater = {"$set": {"_id": channel, "hilo": 0}} - self._update_document(channel, hilo_updater, "hilo") - - def _calc_hand_value(self, hand: list): - """ - Calculate the value of a blackjack hand. - - *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* - --------- - hand_value: int - The blackjack value of the hand. - """ - values = [0] - - for card in hand: - card_value = replace_multiple(card[0], ["0", "k", "q", "j"], "10") - if card_value == "a": - values = [i + 1 for i in values] + [i + 11 for i in values] - else: - values = [i+int(card_value) for i in values] - - valid_values = [i for i in values if i <= 21] - hand_value = max(valid_values) if valid_values else min(values) - - self.bot.log(f"Calculated the value of {hand} to be {hand_value}") - - return hand_value - - def _draw_card(self, channel: str): - drawn_card = super()._draw_card(channel) - hilo_value = min(1,-1-((self._calc_hand_value([drawn_card])-10)//3)) - self._update_document(channel, {"$inc": {"hilo": hilo_value}}, "hilo") - - return drawn_card - - def _dealer_draw(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.access_document(channel) - dealer_hand = game["dealer hand"] - - if self._calc_hand_value(dealer_hand) < 17: - dealer_hand.append(self._draw_card(channel)) - dealer_updater = {"$set": {"dealer hand": dealer_hand}} - self._update_document(channel, dealer_updater) - done = False - else: - done = True - - if self._calc_hand_value(dealer_hand) > 21: - self._update_document(channel, {"$set": {"dealer busted": True}}) - - return done - - def _blackjack_continue(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* - --------- - send_message: str - The message to send to the channel. - all_standing: bool - If all players are standing. - gameDone: bool - If the game has finished. - """ - self.bot.log("Continuing blackjack game") - game = self.access_document(channel) - - self._update_document(channel, {"$inc": {"round": 1}}) - message = self.long_strings["Blackjack all players standing"] - all_standing = True - pre_all_standing = True - done = False - - if game["all standing"]: - self.bot.log("All are standing") - done = self._dealer_draw(channel) - message = "The dealer draws a card." - - self.bot.log("Testing if all are standing") - for user, user_hands in game["user hands"].items(): - standing_test = (self._test_if_standing(user_hands)) - hand_updater = {"$set": {"user hands."+user: standing_test[0]}} - self._update_document(channel, hand_updater) - if not standing_test[1]: - all_standing = False - if not standing_test[2]: - pre_all_standing = False - - - if all_standing: - self._update_document(channel, {"$set": {"all standing": True}}) - - if done: - message = "The dealer is done drawing cards" - - return message, True, done - - if pre_all_standing: - return "", True, done - - send_message = self.long_strings["Blackjack commands"] - - if game["round"] == 0: - send_message = send_message.format( - self.long_strings["Blackjack first round"]) - else: - send_message = send_message.format("") - - return send_message, False, done - - def _test_if_standing(self, user_hands: list): - """ - Test if a player is standing on all their hands. - - Also resets the hand if it's not standing - - *Parameters* - ------------ - hand: dict - The hands to test and reset. - - *Returns* - --------- - hand: dict - The reset hand. - all_standing: bool - If the player is standing on all their hands. - 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. - """ - # If the user has not hit, they are by definition standing. - all_standing = True - pre_all_standing = True - - for hand in user_hands: - if not hand["hit"]: - hand["standing"] = True - - if not hand["standing"]: - all_standing = False - - if self._calc_hand_value(hand["hand"]) >= 21 or hand["doubled"]: - hand["standing"] = True - else: - pre_all_standing = False - - hand["hit"] = False - - return user_hands, all_standing, pre_all_standing - - async def _blackjack_finish(self, channel: Messageable): - """ - Generate the winnings message after the blackjack game ends. - - *Parameters* - ------------ - channel: str - The id of the channel the game is in. - - *Returns* - --------- - final_winnings: str - The winnings message. - """ - final_winnings = "*Final Winnings:*\n" - - game = self.access_document(str(channel.id)) - - for user in game["user hands"]: - winnings, net_winnings, reason = self._calc_winning( - game["user hands"][user], - self._calc_hand_value(game["dealer hand"]), - game["dealer blackjack"], - game["dealer busted"] - ) - - user_name = self.bot.database_funcs.get_name(user) - - if winnings < 0: - final_winnings += f"{user_name} lost" - else: - final_winnings += f"{user_name} won" - - if abs(winnings) == 1: - final_winnings += " 1 GwendoBuck" - else: - final_winnings += f" {abs(winnings)} GwendoBucks" - - final_winnings += f" {reason}\n" - - self.bot.money.addMoney(user, net_winnings) - - await self._end_game(channel) - - return final_winnings - - def _calc_winning(self, user_hands: dict, dealer_value: int, - dealer_blackjack: bool, dealer_busted: bool): - """ - Calculate how much a user has won/lost in the blackjack game. - - *Parameters* - ------------ - hand: dict - The hand to calculate the winnings of. - dealer_value: int - The dealer's hand value. - 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. - dealer_blackjack: bool - If the dealer has a blackjack. - dealer_busted: bool - If the dealer busted. - - *Returns* - --------- - winnings: int - How much the player has won/lost. - 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. - reason: str - The reason for why they won/lost. - """ - self.bot.log("Calculating winnings") - reason = "" - winnings = 0 - net_winnings = 0 - - for hand in user_hands: - winnings += -1 * hand["bet"] - hand_value = self._calc_hand_value(hand["hand"]) - - states = [ - (dealer_blackjack, "dealer blackjack", 0), - (hand["blackjack"], "blackjack", math.floor(2.5 * hand["bet"])), - (hand["busted"], "busted", 0), - (dealer_busted, dealer_busted, 2*hand["bet"]), - (hand_value == dealer_value, "pushed", hand["bet"]), - (hand_value > dealer_value, "highest value", 2 * hand["bet"]), - (True, "highest value", 0) - ] - - for state in states: - if state[0]: - reason += f"({state[1]})" - net_winnings += state[2] - break - - winnings += net_winnings - return winnings, net_winnings, reason - - async def _blackjack_loop(self, channel, game_round: int, game_id: str): - """ - Run blackjack logic and continue if enough time passes. - - *Parameters* - ------------ - channel: guildChannel or DMChannel - The channel the game is happening in. - game_round: int - The round to start. - game_id: str - The ID of the game. - """ - self.bot.log(f"Loop {game_round}", str(channel.id)) - - new_message, all_standing, game_done = self._blackjack_continue( - str(channel.id) - ) - if new_message != "": - self.bot.log(new_message, str(channel.id)) - await channel.send(new_message) - - if not game_done: - await self._delete_old_image(channel) - buttons = None if all_standing else self.default_buttons - await self._send_image(channel, buttons) - - await asyncio.sleep([120,5][all_standing]) - - if not self._test_document(str(channel.id)): - self.bot.log(f"Ending loop on round {game_round}", str(channel.id)) - return - - game = self.access_document(str(channel.id)) - if game_round != game["round"] or game_id != game["game_id"]: - self.bot.log(f"Ending loop on round {game_round}", str(channel.id)) - return - - 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, game_round+1, game_id) - else: - await channel.send(await self._blackjack_finish(channel)) - - async def _get_hand_number(self, ctx: IntCont, command: str, - hands_amount: int): - buttons = [ - (str(i+1), [command, "0", str(i+1)], 1) for i in range(hands_amount) - ] - await ctx.send( - f"Which hand do you want to {command}?", - hidden=True, - components=self._get_action_rows(buttons) - ) - - async def _hit(self, ctx: IntCont, hand_number: int = 0): - """ - Hit on a hand. - - *Parameters* - ------------ - ctx: IntCont - The context of the command. - hand_number: int = 1 - The number of the hand to hit. - """ - await self.bot.defer(ctx) - channel = str(ctx.channel_id) - user = f"#{ctx.author.id}" - - game = self.access_document(channel) - if not await self._test_command(game, user, "hit", hand_number, ctx): - return - - hand = game["user hands"][user][max(hand_number-1,0)] - hand["hand"].append(self._draw_card(channel)) - hand["hit"] = True - - hand_value = self._calc_hand_value(hand["hand"]) - - if hand_value > 21: - hand["busted"] = True - - hand_path = f"user hands.{user}.{max(hand_number-1,0)}" - - self._update_document(channel, {"$set": {hand_path: hand}}) - game = self.access_document(channel) - - await ctx.send(f"{ctx.author.display_name} hit") - self.bot.log("They succeeded") - - if _is_round_done(game): - game_id = game["game_id"] - self.bot.log("Hit calling self._blackjack_loop()", channel) - await self._blackjack_loop( - ctx.channel, game["round"]+1, game_id - ) - - async def _double(self, ctx: IntCont, hand_number: int = 0): - """ - Double a hand. - - *Parameters* - ------------ - ctx: IntCont - The context of the command. - 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}" - - game = self.access_document(channel) - - balance = self.bot.money.checkBalance(user) - - if not await self._test_command(game, user, "double", hand_number, ctx): - return - - if len(game["user hands"][user][max(hand_number-1,0)]["hand"]) != 2: - await ctx.send("You can only double on the first round") - self.bot.log("They tried to double after round 1") - elif balance < game["user hands"][user][max(hand_number-1,0)]["bet"]: - await ctx.send("You can't double when you don't have enough money") - self.bot.log("They tried to double without having enough money") - else: - hand = game["user hands"][user][max(hand_number-1,0)] - self.bot.money.addMoney(user, -1 * hand["bet"]) - - hand["hand"].append(self._draw_card(channel)) - hand["hit"] = True - hand["doubled"] = True - hand["bet"] += hand["bet"] - - if self._calc_hand_value(hand["hand"]) > 21: - hand["busted"] = True - - hand_path = f"user hands.{user}.{max(hand_number-1,0)}" - - self._update_document(channel, {"$set": {hand_path: hand}}) - - game = self.access_document(channel) - - user_name = self.bot.database_funcs.get_name(user) - send_message = self.long_strings["Blackjack double"] - - await ctx.send(send_message.format(hand["bet"], user_name)) - self.bot.log("They succeeded") - - if _is_round_done(game): - game_id = game["game_id"] - self.bot.log("Double calling self._blackjack_loop()", channel) - await self._blackjack_loop( - ctx.channel, game["round"]+1, game_id - ) - - - async def _stand(self, ctx: IntCont, hand_number: int = 0): - """ - Stand on a hand. - - *Parameters* - ------------ - ctx: IntCont - The context of the command. - 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}" - - game = self.access_document(channel) - - if not await self._test_command(game, user, "stand", hand_number, ctx): - return - - hand = game["user hands"][user][max(hand_number-1,0)] + game = self.access_document(channel) + dealer_hand = game["dealer hand"] + + if self._calc_hand_value(dealer_hand) < 17: + dealer_hand.append(self._draw_card(channel)) + dealer_updater = {"$set": {"dealer hand": dealer_hand}} + self._update_document(channel, dealer_updater) + done = False + else: + done = True + + if self._calc_hand_value(dealer_hand) > 21: + self._update_document(channel, {"$set": {"dealer busted": True}}) + + return done + + def _blackjack_continue(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* + --------- + send_message: str + The message to send to the channel. + all_standing: bool + If all players are standing. + gameDone: bool + If the game has finished. + """ + self.bot.log("Continuing blackjack game") + game = self.access_document(channel) + + self._update_document(channel, {"$inc": {"round": 1}}) + message = self.long_strings["Blackjack all players standing"] + all_standing = True + pre_all_standing = True + done = False + + if game["all standing"]: + self.bot.log("All are standing") + done = self._dealer_draw(channel) + message = "The dealer draws a card." + + self.bot.log("Testing if all are standing") + for user, user_hands in game["user hands"].items(): + standing_test = (self._test_if_standing(user_hands)) + hand_updater = {"$set": {"user hands."+user: standing_test[0]}} + self._update_document(channel, hand_updater) + if not standing_test[1]: + all_standing = False + if not standing_test[2]: + pre_all_standing = False + + + if all_standing: + self._update_document(channel, {"$set": {"all standing": True}}) + + if done: + message = "The dealer is done drawing cards" + + return message, True, done + + if pre_all_standing: + return "", True, done + + send_message = self.long_strings["Blackjack commands"] + + if game["round"] == 0: + send_message = send_message.format( + self.long_strings["Blackjack first round"]) + else: + send_message = send_message.format("") + + return send_message, False, done + + def _test_if_standing(self, user_hands: list): + """ + Test if a player is standing on all their hands. + + Also resets the hand if it's not standing + + *Parameters* + ------------ + hand: dict + The hands to test and reset. + + *Returns* + --------- + hand: dict + The reset hand. + all_standing: bool + If the player is standing on all their hands. + 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. + """ + # If the user has not hit, they are by definition standing. + all_standing = True + pre_all_standing = True + + for hand in user_hands: + if not hand["hit"]: hand["standing"] = True - hand_path = f"user hands.{user}.{max(hand_number-1,0)}" + if not hand["standing"]: + all_standing = False - self._update_document(channel, {"$set": {hand_path: hand}}) - game = self.access_document(channel) - - send_message = f"{ctx.author.display_name} is standing" - log_message = "They succeeded" - await ctx.send(send_message) - self.bot.log(log_message) - if _is_round_done(game): - game_id = game["game_id"] - self.bot.log("Stand calling self._blackjack_loop()", channel) - await self._blackjack_loop(ctx.channel, game["round"]+1, game_id) - - - async def _split(self, ctx: IntCont, hand_number: int = 0): - """ - Split a hand. - - *Parameters* - ------------ - ctx: IntCont - The context of the command. - 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}" - - game = self.access_document(channel) - - if not await self._test_command(game, user, "split", hand_number, ctx): - return - - old_hand = game["user hands"][user][max(hand_number-1,0)] - - if len(game["user hands"][user]) > 3: - await ctx.send("You can only split 3 times") - self.bot.log("They tried to split more than three times") - elif len(old_hand["hand"]) != 2: - await ctx.send("You can only split on the first round") - self.bot.log("They tried to split after the first round") - elif len({self._calc_hand_value([i]) for i in old_hand["hand"]}) != 1: - await ctx.send(self.long_strings["Blackjack different cards"]) - self.bot.log("They tried to split two different cards") - elif self.bot.money.checkBalance(user) < old_hand["bet"]: - await ctx.send("You don't have enough GwendoBucks") - self.bot.log("They didn't have enough GwendoBucks") + if self._calc_hand_value(hand["hand"]) >= 21 or hand["doubled"]: + hand["standing"] = True else: - self.bot.money.addMoney(user, -1 * old_hand["bet"]) + pre_all_standing = False - old_hand["hit"] = True + hand["hit"] = False - hands = [old_hand, { - "hand": [old_hand["hand"].pop(1), self._draw_card(channel)], - "bet": old_hand["bet"], "standing": False, - "busted": False, "blackjack": False, "hit": True, - "doubled": False - }] - old_hand["hand"].append(self._draw_card(channel)) + return user_hands, all_standing, pre_all_standing - for i, hand in enumerate(hands): - if self._calc_hand_value(hand["hand"]) > 21: - hands[i]["busted"] = True - elif self._calc_hand_value(hand["hand"]) == 21: - hands[i]["blackjack"] = True + async def _blackjack_finish(self, channel: Messageable): + """ + Generate the winnings message after the blackjack game ends. - game["user hands"][user][max(hand_number-1,0)] = hands[0] - game["user hands"][user].append(hands[1]) + *Parameters* + ------------ + channel: str + The id of the channel the game is in. - updater = {"$set": {f"user hands.{user}": game["user hands"][user]}} - self._update_document(channel, updater) + *Returns* + --------- + final_winnings: str + The winnings message. + """ + final_winnings = "*Final Winnings:*\n" - send_message = self.long_strings["Blackjack split"] - user_name = self.bot.database_funcs.get_name(user) + game = self.access_document(str(channel.id)) - await ctx.send(send_message.format(user_name)) - self.bot.log("They succeeded") + for user in game["user hands"]: + winnings, net_winnings, reason = self._calc_winning( + game["user hands"][user], + self._calc_hand_value(game["dealer hand"]), + game["dealer blackjack"], + game["dealer busted"] + ) - game = self.access_document(channel) - if _is_round_done(game): - game_id = game["game_id"] - self.bot.log("Split calling self._blackjack_loop()", channel) - await self._blackjack_loop( - ctx.channel, game["round"]+1, game_id - ) + user_name = self.bot.database_funcs.get_name(user) - async def enter_game(self, ctx: IntCont, bet: int): - """ - Enter the blackjack game. + if winnings < 0: + final_winnings += f"{user_name} lost" + else: + final_winnings += f"{user_name} won" - *Parameters* - ------------ - ctx: IntCont - 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}" + if abs(winnings) == 1: + final_winnings += " 1 GwendoBuck" + else: + final_winnings += f" {abs(winnings)} GwendoBucks" - game = self.access_document(channel) - user_name = self.bot.database_funcs.get_name(user) + final_winnings += f" {reason}\n" - self.bot.log(f"{user_name} is trying to join the Blackjack game") + self.bot.money.addMoney(user, net_winnings) - if user in game["user hands"]: - await ctx.send("You're already in the game!") - self.bot.log("They're already in the game") - elif len(game["user hands"]) >= 5: - await ctx.send("There can't be more than 5 players in a game") - self.bot.log("There were already 5 players in the game") - elif game["round"] != 0: - await ctx.send("The table is no longer taking bets") - self.bot.log("They tried to join after the game begun") - elif bet < 0: - await ctx.send("You can't bet a negative amount") - self.bot.log("They tried to bet a negative amount") - elif self.bot.money.checkBalance(user) < bet: - await ctx.send("You don't have enough GwendoBucks") - self.bot.log("They didn't have enough GwendoBucks") - else: - self.bot.money.addMoney(user, -1 * bet) - player_hand = [self._draw_card(channel) for _ in range(2)] + await self._end_game(channel) - new_hand = [{ - "hand": player_hand, "bet": bet, "standing": False, - "busted": False, - "blackjack": self._calc_hand_value(player_hand) == 21, - "hit": True, "doubled": False - }] + return final_winnings - updater = {"$set": {f"user hands.{user}": new_hand}} - self._update_document(channel, updater) + def _calc_winning(self, user_hands: dict, dealer_value: int, + dealer_blackjack: bool, dealer_busted: bool): + """ + Calculate how much a user has won/lost in the blackjack game. - send_message = "{} entered the game with a bet of {} GwendoBucks" - await ctx.send(send_message.format(user_name, bet)) - self.bot.log(send_message.format(user_name, bet)) + *Parameters* + ------------ + hand: dict + The hand to calculate the winnings of. + dealer_value: int + The dealer's hand value. + 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. + dealer_blackjack: bool + If the dealer has a blackjack. + dealer_busted: bool + If the dealer busted. - async def start(self, ctx: IntCont): - """ - Start a blackjack game. + *Returns* + --------- + winnings: int + How much the player has won/lost. + 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. + reason: str + The reason for why they won/lost. + """ + self.bot.log("Calculating winnings") + reason = "" + winnings = 0 + net_winnings = 0 - *Parameters* - ------------ - ctx: IntCont - The context of the command. - """ - await self.bot.defer(ctx) - channel = str(ctx.channel_id) - blackjack_min_cards = 50 + for hand in user_hands: + winnings += -1 * hand["bet"] + hand_value = self._calc_hand_value(hand["hand"]) - self.bot.log("Starting blackjack game") - await ctx.send("Starting a new game of blackjack") - cards_left = 0 - if self._test_document(channel, "cards"): - cards = self.access_document(channel, "cards") - cards_left = len(cards["cards"]) + states = [ + (dealer_blackjack, "dealer blackjack", 0), + (hand["blackjack"], "blackjack", math.floor(2.5 * hand["bet"])), + (hand["busted"], "busted", 0), + (dealer_busted, dealer_busted, 2*hand["bet"]), + (hand_value == dealer_value, "pushed", hand["bet"]), + (hand_value > dealer_value, "highest value", 2 * hand["bet"]), + (True, "highest value", 0) + ] - # Shuffles if not enough cards - if cards_left < blackjack_min_cards: - self._shuffle_cards(channel) - await ctx.channel.send("Shuffling the deck...") + for state in states: + if state[0]: + reason += f"({state[1]})" + net_winnings += state[2] + break - self.bot.log(f"Trying to start a blackjack game in {channel}") + winnings += net_winnings + return winnings, net_winnings, reason - if self._test_document(channel): - await ctx.channel.send(self.long_strings["Blackjack going on"]) - self.bot.log("There was already a game going on") - return + async def _blackjack_loop(self, channel, game_round: int, game_id: str): + """ + Run blackjack logic and continue if enough time passes. - dealer_hand = [self._draw_card(channel), self._draw_card(channel)] - game_id = datetime.datetime.now().strftime('%Y%m%d%H%M%S') + *Parameters* + ------------ + channel: guildChannel or DMChannel + The channel the game is happening in. + game_round: int + The round to start. + game_id: str + The ID of the game. + """ + self.bot.log(f"Loop {game_round}", str(channel.id)) - new_game = { - "dealer hand": dealer_hand, - "dealer busted": False, "game_id": game_id, - "dealer blackjack": (self._calc_hand_value(dealer_hand) == 21), - "user hands": {}, "all standing": False, "round": 0 - } + new_message, all_standing, game_done = self._blackjack_continue( + str(channel.id) + ) + if new_message != "": + self.bot.log(new_message, str(channel.id)) + await channel.send(new_message) - await ctx.channel.send(self.long_strings["Blackjack started"]) - labels = [0] + [10**i for i in range(4)] - betting_buttons = [ - (f"Bet {i}", ["bet", "0", str(i)], 1) for i in labels - ] - await self._start_new(ctx.channel, new_game, betting_buttons) + if not game_done: + await self._delete_old_image(channel) + buttons = None if all_standing else self.default_buttons + await self._send_image(channel, buttons) - await asyncio.sleep(30) - game = self.access_document(channel) + await asyncio.sleep([120,5][all_standing]) - if len(game["user hands"]) == 0: - await ctx.channel.send("No one entered the game. Ending the game.") - await ctx.channel.send(await self._blackjack_finish(ctx.channel)) - return + if not self._test_document(str(channel.id)): + self.bot.log(f"Ending loop on round {game_round}", str(channel.id)) + return + game = self.access_document(str(channel.id)) + if game_round != game["round"] or game_id != game["game_id"]: + self.bot.log(f"Ending loop on round {game_round}", str(channel.id)) + return + + 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, game_round+1, game_id) + else: + await channel.send(await self._blackjack_finish(channel)) + + async def _get_hand_number(self, ctx: IntCont, command: str, + hands_amount: int): + buttons = [ + (str(i+1), [command, "0", str(i+1)], 1) for i in range(hands_amount) + ] + await ctx.send( + f"Which hand do you want to {command}?", + hidden=True, + components=self._get_action_rows(buttons) + ) + + async def _hit(self, ctx: IntCont, hand_number: int = 0): + """ + Hit on a hand. + + *Parameters* + ------------ + ctx: IntCont + The context of the command. + hand_number: int = 1 + The number of the hand to hit. + """ + await self.bot.defer(ctx) + channel = str(ctx.channel_id) + user = f"#{ctx.author.id}" + + game = self.access_document(channel) + if not await self._test_command(game, user, "hit", hand_number, ctx): + return + + hand = game["user hands"][user][max(hand_number-1,0)] + hand["hand"].append(self._draw_card(channel)) + hand["hit"] = True + + hand_value = self._calc_hand_value(hand["hand"]) + + if hand_value > 21: + hand["busted"] = True + + hand_path = f"user hands.{user}.{max(hand_number-1,0)}" + + self._update_document(channel, {"$set": {hand_path: hand}}) + game = self.access_document(channel) + + await ctx.send(f"{ctx.author.display_name} hit") + self.bot.log("They succeeded") + + if _is_round_done(game): + game_id = game["game_id"] + self.bot.log("Hit calling self._blackjack_loop()", channel) + await self._blackjack_loop( + ctx.channel, game["round"]+1, game_id + ) + + async def _double(self, ctx: IntCont, hand_number: int = 0): + """ + Double a hand. + + *Parameters* + ------------ + ctx: IntCont + The context of the command. + 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}" + + game = self.access_document(channel) + + balance = self.bot.money.checkBalance(user) + + if not await self._test_command(game, user, "double", hand_number, ctx): + return + + if len(game["user hands"][user][max(hand_number-1,0)]["hand"]) != 2: + await ctx.send("You can only double on the first round") + self.bot.log("They tried to double after round 1") + elif balance < game["user hands"][user][max(hand_number-1,0)]["bet"]: + await ctx.send("You can't double when you don't have enough money") + self.bot.log("They tried to double without having enough money") + else: + hand = game["user hands"][user][max(hand_number-1,0)] + self.bot.money.addMoney(user, -1 * hand["bet"]) + + hand["hand"].append(self._draw_card(channel)) + hand["hit"] = True + hand["doubled"] = True + hand["bet"] += hand["bet"] + + if self._calc_hand_value(hand["hand"]) > 21: + hand["busted"] = True + + hand_path = f"user hands.{user}.{max(hand_number-1,0)}" + + self._update_document(channel, {"$set": {hand_path: hand}}) + + game = self.access_document(channel) + + user_name = self.bot.database_funcs.get_name(user) + send_message = self.long_strings["Blackjack double"] + + await ctx.send(send_message.format(hand["bet"], user_name)) + self.bot.log("They succeeded") + + if _is_round_done(game): game_id = game["game_id"] + self.bot.log("Double calling self._blackjack_loop()", channel) + await self._blackjack_loop( + ctx.channel, game["round"]+1, game_id + ) - # Loop of game rounds - self.bot.log("start() calling _blackjack_loop()", channel) - await self._blackjack_loop(ctx.channel, 1, game_id) - async def hilo(self, ctx: IntCont): - """ - Get the hilo of the blackjack game. + async def _stand(self, ctx: IntCont, hand_number: int = 0): + """ + Stand on a hand. - *Parameters* - ------------ - ctx: IntCont - The context of the command. - """ - data = self.access_document(str(ctx.channel_id), "hilo", False) - hilo = data["hilo"] if data else 0 + *Parameters* + ------------ + ctx: IntCont + The context of the command. + 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}" - await ctx.send(f"Hi-lo value: {hilo}", hidden=True) + game = self.access_document(channel) - async def shuffle(self, ctx: IntCont): - """ - Shuffle the cards used for blackjack. + if not await self._test_command(game, user, "stand", hand_number, ctx): + return - *Parameters* - ------------ - ctx: IntCont - The context of the command. - """ - self._shuffle_cards(str(ctx.channel_id)) - await ctx.send("Shuffling the deck...") + hand = game["user hands"][user][max(hand_number-1,0)] + hand["standing"] = True - async def cards(self, ctx: IntCont): - """ - Get how many cards are left for blackjack. + hand_path = f"user hands.{user}.{max(hand_number-1,0)}" - *Parameters* - ------------ - ctx: IntCont - The context of the command. - """ - cards = self.access_document(str(ctx.channel_id), "cards", False) - cards_left = len(cards["cards"]) if cards else 0 - decks_left = round(cards_left/52, 1) if cards else 0 + self._update_document(channel, {"$set": {hand_path: hand}}) + game = self.access_document(channel) - send_message = f"Cards left:\n{cards_left} cards, {decks_left} decks" - await ctx.send(send_message, hidden=True) + send_message = f"{ctx.author.display_name} is standing" + log_message = "They succeeded" + await ctx.send(send_message) + self.bot.log(log_message) + if _is_round_done(game): + game_id = game["game_id"] + self.bot.log("Stand calling self._blackjack_loop()", channel) + await self._blackjack_loop(ctx.channel, game["round"]+1, game_id) - async def decode_interaction(self, ctx: ComponentContext, info: list[str]): - if info[0].lower() == "bet": - await self.enter_game(ctx, int(info[2])) - elif info[0].lower() == "hit": - await self._hit(ctx, int(info[2]) if len(info) > 2 else 0) - elif info[0].lower() == "stand": - await self._stand(ctx, int(info[2]) if len(info) > 2 else 0) - elif info[0].lower() == "double": - await self._double(ctx, int(info[2]) if len(info) > 2 else 0) - elif info[0].lower() == "split": - await self._split(ctx, int(info[2]) if len(info) > 2 else 0) + + async def _split(self, ctx: IntCont, hand_number: int = 0): + """ + Split a hand. + + *Parameters* + ------------ + ctx: IntCont + The context of the command. + 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}" + + game = self.access_document(channel) + + if not await self._test_command(game, user, "split", hand_number, ctx): + return + + old_hand = game["user hands"][user][max(hand_number-1,0)] + + if len(game["user hands"][user]) > 3: + await ctx.send("You can only split 3 times") + self.bot.log("They tried to split more than three times") + elif len(old_hand["hand"]) != 2: + await ctx.send("You can only split on the first round") + self.bot.log("They tried to split after the first round") + elif len({self._calc_hand_value([i]) for i in old_hand["hand"]}) != 1: + await ctx.send(self.long_strings["Blackjack different cards"]) + self.bot.log("They tried to split two different cards") + elif self.bot.money.checkBalance(user) < old_hand["bet"]: + await ctx.send("You don't have enough GwendoBucks") + self.bot.log("They didn't have enough GwendoBucks") + else: + self.bot.money.addMoney(user, -1 * old_hand["bet"]) + + old_hand["hit"] = True + + hands = [old_hand, { + "hand": [old_hand["hand"].pop(1), self._draw_card(channel)], + "bet": old_hand["bet"], "standing": False, + "busted": False, "blackjack": False, "hit": True, + "doubled": False + }] + old_hand["hand"].append(self._draw_card(channel)) + + for i, hand in enumerate(hands): + if self._calc_hand_value(hand["hand"]) > 21: + hands[i]["busted"] = True + elif self._calc_hand_value(hand["hand"]) == 21: + hands[i]["blackjack"] = True + + game["user hands"][user][max(hand_number-1,0)] = hands[0] + game["user hands"][user].append(hands[1]) + + updater = {"$set": {f"user hands.{user}": game["user hands"][user]}} + self._update_document(channel, updater) + + send_message = self.long_strings["Blackjack split"] + user_name = self.bot.database_funcs.get_name(user) + + await ctx.send(send_message.format(user_name)) + self.bot.log("They succeeded") + + game = self.access_document(channel) + if _is_round_done(game): + game_id = game["game_id"] + self.bot.log("Split calling self._blackjack_loop()", channel) + await self._blackjack_loop( + ctx.channel, game["round"]+1, game_id + ) + + async def enter_game(self, ctx: IntCont, bet: int): + """ + Enter the blackjack game. + + *Parameters* + ------------ + ctx: IntCont + 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}" + + game = self.access_document(channel) + user_name = self.bot.database_funcs.get_name(user) + + self.bot.log(f"{user_name} is trying to join the Blackjack game") + + if user in game["user hands"]: + await ctx.send("You're already in the game!") + self.bot.log("They're already in the game") + elif len(game["user hands"]) >= 5: + await ctx.send("There can't be more than 5 players in a game") + self.bot.log("There were already 5 players in the game") + elif game["round"] != 0: + await ctx.send("The table is no longer taking bets") + self.bot.log("They tried to join after the game begun") + elif bet < 0: + await ctx.send("You can't bet a negative amount") + self.bot.log("They tried to bet a negative amount") + elif self.bot.money.checkBalance(user) < bet: + await ctx.send("You don't have enough GwendoBucks") + self.bot.log("They didn't have enough GwendoBucks") + else: + self.bot.money.addMoney(user, -1 * bet) + player_hand = [self._draw_card(channel) for _ in range(2)] + + new_hand = [{ + "hand": player_hand, "bet": bet, "standing": False, + "busted": False, + "blackjack": self._calc_hand_value(player_hand) == 21, + "hit": True, "doubled": False + }] + + updater = {"$set": {f"user hands.{user}": new_hand}} + self._update_document(channel, updater) + + send_message = "{} entered the game with a bet of {} GwendoBucks" + await ctx.send(send_message.format(user_name, bet)) + self.bot.log(send_message.format(user_name, bet)) + + async def start(self, ctx: IntCont): + """ + Start a blackjack game. + + *Parameters* + ------------ + ctx: IntCont + The context of the command. + """ + await self.bot.defer(ctx) + channel = str(ctx.channel_id) + blackjack_min_cards = 50 + + self.bot.log("Starting blackjack game") + await ctx.send("Starting a new game of blackjack") + cards_left = 0 + if self._test_document(channel, "cards"): + cards = self.access_document(channel, "cards") + cards_left = len(cards["cards"]) + + # Shuffles if not enough cards + if cards_left < blackjack_min_cards: + self._shuffle_cards(channel) + await ctx.channel.send("Shuffling the deck...") + + self.bot.log(f"Trying to start a blackjack game in {channel}") + + if self._test_document(channel): + await ctx.channel.send(self.long_strings["Blackjack going on"]) + self.bot.log("There was already a game going on") + return + + dealer_hand = [self._draw_card(channel), self._draw_card(channel)] + game_id = datetime.datetime.now().strftime('%Y%m%d%H%M%S') + + new_game = { + "dealer hand": dealer_hand, + "dealer busted": False, "game_id": game_id, + "dealer blackjack": (self._calc_hand_value(dealer_hand) == 21), + "user hands": {}, "all standing": False, "round": 0 + } + + await ctx.channel.send(self.long_strings["Blackjack started"]) + labels = [0] + [10**i for i in range(4)] + betting_buttons = [ + (f"Bet {i}", ["bet", "0", str(i)], 1) for i in labels + ] + await self._start_new(ctx.channel, new_game, betting_buttons) + + await asyncio.sleep(30) + game = self.access_document(channel) + + if len(game["user hands"]) == 0: + await ctx.channel.send("No one entered the game. Ending the game.") + await ctx.channel.send(await self._blackjack_finish(ctx.channel)) + return + + game_id = game["game_id"] + + # Loop of game rounds + self.bot.log("start() calling _blackjack_loop()", channel) + await self._blackjack_loop(ctx.channel, 1, game_id) + + async def hilo(self, ctx: IntCont): + """ + Get the hilo of the blackjack game. + + *Parameters* + ------------ + ctx: IntCont + The context of the command. + """ + data = self.access_document(str(ctx.channel_id), "hilo", False) + hilo = data["hilo"] if data else 0 + + await ctx.send(f"Hi-lo value: {hilo}", hidden=True) + + async def shuffle(self, ctx: IntCont): + """ + Shuffle the cards used for blackjack. + + *Parameters* + ------------ + ctx: IntCont + The context of the command. + """ + self._shuffle_cards(str(ctx.channel_id)) + await ctx.send("Shuffling the deck...") + + async def cards(self, ctx: IntCont): + """ + Get how many cards are left for blackjack. + + *Parameters* + ------------ + ctx: IntCont + The context of the command. + """ + cards = self.access_document(str(ctx.channel_id), "cards", False) + cards_left = len(cards["cards"]) if cards else 0 + decks_left = round(cards_left/52, 1) if cards else 0 + + send_message = f"Cards left:\n{cards_left} cards, {decks_left} decks" + await ctx.send(send_message, hidden=True) + + async def decode_interaction(self, ctx: ComponentContext, info: list[str]): + if info[0].lower() == "bet": + await self.enter_game(ctx, int(info[2])) + elif info[0].lower() == "hit": + await self._hit(ctx, int(info[2]) if len(info) > 2 else 0) + elif info[0].lower() == "stand": + await self._stand(ctx, int(info[2]) if len(info) > 2 else 0) + elif info[0].lower() == "double": + await self._double(ctx, int(info[2]) if len(info) > 2 else 0) + elif info[0].lower() == "split": + await self._split(ctx, int(info[2]) if len(info) > 2 else 0) class DrawBlackjack(CardDrawer): + """ + Draws the blackjack image. + + *Methods* + --------- + + *Attributes* + ------------ + bot: Gwendolyn + The instance of the bot. + PLACEMENT: list + The order to place the user hands in. + """ + # pylint: disable=too-few-public-methods + + def __init__(self, bot, game: Blackjack): + """Initialize the class.""" + super().__init__(bot, game) + + # pylint: disable=invalid-name + self.PLACEMENT = [2, 1, 3, 0, 4] + # pylint: enable=invalid-name + self._set_card_size(197) + + def _draw_dealer_hand(self, game: dict, table: Image.Image): + dealer_hand = self._draw_hand( + game["dealer hand"], + not game["all standing"], + game["dealer busted"] if game["all standing"] else False, + game["dealer blackjack"] if game["all standing"] else False + ) + + paste_position = (800-self.CARD_BORDER, 20-self.CARD_BORDER) + table.paste(dealer_hand, paste_position, dealer_hand) + + def _draw_player_name(self, user: str, placement: int, table: Image.Image): + user_name = self.bot.database_funcs.get_name(user) + font, font_size = self._adjust_font(50, user_name, 360) + text_width, text_height = font.getsize(user_name) + + text_x = 32+(384*placement)+117-(text_width//2) + text_y = 960+text_height + + colors = [BLACK, WHITE] + text_image = self._draw_shadow_text(user_name, colors, font_size) + table.paste(text_image, (text_x, text_y), text_image) + + def _draw_player_hands(self, all_hands: dict, table: Image.Image): + for i, (user, user_hands) in enumerate(all_hands.items()): + hand_images = [] + position_x = 32-self.CARD_BORDER+(384*self.PLACEMENT[i]) + + for hand in user_hands: + hand_images.append(self._draw_hand( + hand["hand"], + False, + hand["busted"], + hand["blackjack"] + )) + + for index, image in enumerate(hand_images): + position_y = 840 + position_y -= self.CARD_BORDER + (140 * (len(user_hands)-index)) + position = (position_x, position_y) + table.paste(image, position, image) + + self._draw_player_name(user, self.PLACEMENT[i], table) + + def _draw_image(self, game: dict, table: Image.Image): """ - Draws the blackjack image. + Draw the table image. - *Methods* - --------- - - *Attributes* + *Parameters* ------------ - bot: Gwendolyn - The instance of the bot. - PLACEMENT: list - The order to place the user hands in. + channel: str + The id of the channel the game is in. """ - # pylint: disable=too-few-public-methods + self._draw_dealer_hand(game, table) + self._draw_player_hands(game["user hands"], table) - def __init__(self, bot, game: Blackjack): - """Initialize the class.""" - super().__init__(bot, game) + def _draw_hand(self, hand: dict, dealer: bool, busted: bool, + blackjack: bool): + """ + Draw a hand. - # pylint: disable=invalid-name - self.PLACEMENT = [2, 1, 3, 0, 4] - # pylint: enable=invalid-name - self._set_card_size(197) + *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. - def _draw_dealer_hand(self, game: dict, table: Image.Image): - dealer_hand = self._draw_hand( - game["dealer hand"], - not game["all standing"], - game["dealer busted"] if game["all standing"] else False, - game["dealer blackjack"] if game["all standing"] else False - ) + *Returns* + --------- + background: Image + The image of the hand. + """ + self.bot.log("Drawing hand {hand}, {busted}, {blackjack}") + background = self._draw_cards(hand, int(dealer)) - paste_position = (800-self.CARD_BORDER, 20-self.CARD_BORDER) - table.paste(dealer_hand, paste_position, dealer_hand) + if busted or blackjack: + if busted: + text = "BUSTED" + last_color = RED + font_size = 60 + elif blackjack: + text = "BLACKJACK" + last_color = GOLD + font_size = 35 - def _draw_player_name(self, user: str, placement: int, table: Image.Image): - user_name = self.bot.database_funcs.get_name(user) - font, font_size = self._adjust_font(50, user_name, 360) - text_width, text_height = font.getsize(user_name) + colors = [BLACK, WHITE, last_color] + text_image = self._draw_shadow_text(text, colors, font_size) + width = background.size[0] + text_width = self._get_font(font_size).getsize(text)[0] + text_position = (int(width/2)-int(text_width/2), 85) + background.paste(text_image, text_position, text_image) - text_x = 32+(384*placement)+117-(text_width//2) - text_y = 960+text_height - - colors = [BLACK, WHITE] - text_image = self._draw_shadow_text(user_name, colors, font_size) - table.paste(text_image, (text_x, text_y), text_image) - - def _draw_player_hands(self, all_hands: dict, table: Image.Image): - for i, (user, user_hands) in enumerate(all_hands.items()): - hand_images = [] - position_x = 32-self.CARD_BORDER+(384*self.PLACEMENT[i]) - - for hand in user_hands: - hand_images.append(self._draw_hand( - hand["hand"], - False, - hand["busted"], - hand["blackjack"] - )) - - for index, image in enumerate(hand_images): - position_y = 840 - position_y -= self.CARD_BORDER + (140 * (len(user_hands)-index)) - position = (position_x, position_y) - table.paste(image, position, image) - - self._draw_player_name(user, self.PLACEMENT[i], table) - - def _draw_image(self, game: dict, table: Image.Image): - """ - Draw the table image. - - *Parameters* - ------------ - channel: str - The id of the channel the game is in. - """ - self._draw_dealer_hand(game, table) - self._draw_player_hands(game["user hands"], table) - - def _draw_hand(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}") - background = self._draw_cards(hand, int(dealer)) - - if busted or blackjack: - if busted: - text = "BUSTED" - last_color = RED - font_size = 60 - elif blackjack: - text = "BLACKJACK" - last_color = GOLD - font_size = 35 - - colors = [BLACK, WHITE, last_color] - text_image = self._draw_shadow_text(text, colors, font_size) - width = background.size[0] - text_width = self._get_font(font_size).getsize(text)[0] - text_position = (int(width/2)-int(text_width/2), 85) - background.paste(text_image, text_position, text_image) - - return background + return background diff --git a/gwendolyn_old/funcs/games/connect_four.py b/gwendolyn_old/funcs/games/connect_four.py index e891f5c..b78cb96 100644 --- a/gwendolyn_old/funcs/games/connect_four.py +++ b/gwendolyn_old/funcs/games/connect_four.py @@ -3,8 +3,8 @@ Contains the classes that deal with playing and drawing connect four. *Classes* --------- - ConnectFour - DrawConnectFour + ConnectFour + DrawConnectFour """ import random # Used to shuffle players and by the ai to pick from # similar options @@ -28,1102 +28,1102 @@ ROWCOUNT = 6 COLUMNCOUNT = 7 def _encode_board_string(board: list): - string = [str(i) for row in board for i in row] + string = [str(i) for row in board for i in row] - while len(string) > 0 and string[0] == "0": - string = string[1:] + while len(string) > 0 and string[0] == "0": + string = string[1:] - if string == "": - string = "0" + if string == "": + string = "0" - dec = 0 - for i, digit in enumerate(string[::-1]): - dec += (3**i)*int(digit) + dec = 0 + for i, digit in enumerate(string[::-1]): + dec += (3**i)*int(digit) - return str(dec) + return str(dec) def _decode_board_string(board_string: str): - dec = int(board_string) - string = [] - while dec: - string.append(str(dec % 3)) - dec = dec // 3 + dec = int(board_string) + string = [] + while dec: + string.append(str(dec % 3)) + dec = dec // 3 - while len(string) < ROWCOUNT * COLUMNCOUNT: - string.append("0") + while len(string) < ROWCOUNT * COLUMNCOUNT: + string.append("0") - string = string[::-1] + string = string[::-1] - board = [ - [int(x) for x in string[i*COLUMNCOUNT:i*COLUMNCOUNT+COLUMNCOUNT]] - for i in range(ROWCOUNT)] + board = [ + [int(x) for x in string[i*COLUMNCOUNT:i*COLUMNCOUNT+COLUMNCOUNT]] + for i in range(ROWCOUNT)] - return board + return board class ConnectFour(BoardGame): + """ + Deals with connect four commands and logic. + + *Methods* + --------- + start(ctx: SlashContext, opponent: Union[int, Discord.User]) + place_piece(ctx: Union[SlashContext, discord.Message], + user: int, column: int) + surrender(ctx: SlashContext) + """ + + def __init__(self, bot): + """Initialize the class.""" + super().__init__(bot, "connectfour", DrawConnectFour) + self.get_name = self.bot.database_funcs.get_name + # pylint: disable=invalid-name + self.AISCORES = { + "middle": 3, + "two in a row": 10, + "three in a row": 50, + "enemy two in a row": -35, + "enemy three in a row": -200, + "enemy win": -10000, + "win": 10000, + "avoid losing": 100 + } + # pylint: enable=invalid-name + + async def start(self, ctx: SlashContext, + opponent: Union[int, discord.User]): """ - Deals with connect four commands and logic. + Start a game of connect four. - *Methods* - --------- - start(ctx: SlashContext, opponent: Union[int, Discord.User]) - place_piece(ctx: Union[SlashContext, discord.Message], - user: int, column: int) - surrender(ctx: SlashContext) + *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) + channel = str(ctx.channel_id) - def __init__(self, bot): - """Initialize the class.""" - super().__init__(bot, "connectfour", DrawConnectFour) - self.get_name = self.bot.database_funcs.get_name - # pylint: disable=invalid-name - self.AISCORES = { - "middle": 3, - "two in a row": 10, - "three in a row": 50, - "enemy two in a row": -35, - "enemy three in a row": -200, - "enemy win": -10000, - "win": 10000, - "avoid losing": 100 - } - # pylint: enable=invalid-name + opponent, opponent_info = await self._test_opponent(ctx, opponent) + if not opponent_info: + return - async def start(self, ctx: SlashContext, - opponent: Union[int, discord.User]): - """ - Start a game of connect four. + difficulty = opponent_info[0] + difficulty_text = opponent_info[1] - *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) - channel = str(ctx.channel_id) + board = [[0 for _ in range(COLUMNCOUNT)] for _ in range(ROWCOUNT)] + players = [ctx.author.id, opponent] + random.shuffle(players) - opponent, opponent_info = await self._test_opponent(ctx, opponent) - if not opponent_info: - return + self.draw.draw_image(channel, board, players, [0, [0,0], ""]) - difficulty = opponent_info[0] - difficulty_text = opponent_info[1] + opponent_name = self.get_name(f"#{opponent}") + turn_name = self.get_name(f"#{players[0]}") - board = [[0 for _ in range(COLUMNCOUNT)] for _ in range(ROWCOUNT)] - players = [ctx.author.id, opponent] - random.shuffle(players) + started_text = "Started game against {}{}.".format( + opponent_name, + difficulty_text + ) + turn_text = f"It's {turn_name}'s turn" + await ctx.send(f"{started_text} {turn_text}") + self.bot.log("They started a game") - self.draw.draw_image(channel, board, players, [0, [0,0], ""]) + # Sets the whole game in motion + boards_path = "gwendolyn/resources/games/connect_four_boards/" + file_path = f"{boards_path}board{ctx.channel_id}.png" + image_message = await ctx.send(file=discord.File(file_path)) - opponent_name = self.get_name(f"#{opponent}") - turn_name = self.get_name(f"#{players[0]}") - - started_text = "Started game against {}{}.".format( - opponent_name, - difficulty_text + board_string = _encode_board_string(board) + if (players[0] == self.bot.user.id): + await self._connect_four_ai( + ctx, board_string, players, difficulty, image_message.id + ) + else: + buttons = [] + for i in range(7): + custom_id = encode_id( + [ + "connectfour", + "place", + str(players[0]), + board_string, + str(i), + str(players[0]), + str(players[1]), + str(difficulty), + str(image_message.id) + ] ) - turn_text = f"It's {turn_name}'s turn" - await ctx.send(f"{started_text} {turn_text}") - self.bot.log("They started a game") + buttons.append(create_button( + style=ButtonStyle.blue, + label=str(i+1), + custom_id=custom_id, + disabled=(board[0][i] != 0) + )) - # Sets the whole game in motion - boards_path = "gwendolyn/resources/games/connect_four_boards/" - file_path = f"{boards_path}board{ctx.channel_id}.png" - image_message = await ctx.send(file=discord.File(file_path)) + custom_id = encode_id( + [ + "connectfour", + "end", + str(players[0]), + str(players[1]), + str(image_message.id) + ] + ) + buttons.append(create_button( + style=ButtonStyle.red, + label="Surrender", + custom_id=custom_id + )) + action_rows = [] + for x in range(((len(buttons)-1)//4)+1): + row_buttons = buttons[ + (x*4):(min(len(buttons),x*4+4)) + ] + action_rows.append(create_actionrow(*row_buttons)) + + await image_message.edit( + components=action_rows + ) + + async def place_piece(self, ctx: ComponentContext, board_string: str, + column: int, players: list[int], difficulty: int, + placer: int, message_id: int): + """ + Place a piece on the board. + + *Parameters* + column: int + The column the player is placing the piece in. + """ + old_message = await ctx.channel.fetch_message(message_id) + await old_message.delete() + + player_number = players.index(placer)+1 + user_name = self.get_name(f"#{placer}") + placed_piece = False + + board = _decode_board_string(board_string) + board = self._place_on_board(board, player_number, column) + + if board is None: + send_message = "There isn't any room in that column" + log_message = "There wasn't any room in the column" + else: + turn = player_number % 2 + + self.bot.log("Checking for win") + winner, win_direction, win_coordinates = self._is_won(board) + + if winner != 0: + game_won = True + send_message = "{} placed a piece in column {} and won. " + send_message = send_message.format(user_name, column+1) + log_message = f"{user_name} won" + win_amount = difficulty**2+5 + if players[winner-1] != self.bot.user.id: + send_message += "Adding {} GwendoBucks to their account" + send_message = send_message.format(win_amount) + elif 0 not in board[0]: + game_won = True + send_message = "It's a draw!" + log_message = "The game ended in a draw" + else: + game_won = False + other_user_id = f"#{players[turn]}" + other_user_name = self.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" + + gwendolyn_turn = (players[turn] == self.bot.user.id) + + placed_piece = True + + self.bot.log(log_message) + await ctx.channel.send(send_message) + + if placed_piece: + channel = str(ctx.channel) + self.draw.draw_image( + channel, + board, + players, + [winner, win_coordinates, win_direction] + ) + + + boards_path = "gwendolyn/resources/games/connect_four_boards/" + file_path = boards_path + f"board{channel}.png" + image_message = await ctx.channel.send(file=discord.File(file_path)) + + if game_won: + self._end_game(winner, players, difficulty) + else: board_string = _encode_board_string(board) - if (players[0] == self.bot.user.id): - await self._connect_four_ai( - ctx, board_string, players, difficulty, image_message.id - ) + if gwendolyn_turn: + await self._connect_four_ai( + ctx, board_string, players, difficulty, image_message.id + ) else: - buttons = [] - for i in range(7): - custom_id = encode_id( - [ - "connectfour", - "place", - str(players[0]), - board_string, - str(i), - str(players[0]), - str(players[1]), - str(difficulty), - str(image_message.id) - ] - ) - buttons.append(create_button( - style=ButtonStyle.blue, - label=str(i+1), - custom_id=custom_id, - disabled=(board[0][i] != 0) - )) - + buttons = [] + for i in range(7): custom_id = encode_id( - [ - "connectfour", - "end", - str(players[0]), - str(players[1]), - str(image_message.id) - ] + [ + "connectfour", + "place", + str(players[turn]), + board_string, + str(i), + str(players[0]), + str(players[1]), + str(difficulty), + str(image_message.id) + ] ) buttons.append(create_button( - style=ButtonStyle.red, - label="Surrender", - custom_id=custom_id + style=ButtonStyle.blue, + label=str(i+1), + custom_id=custom_id, + disabled=(board[0][i] != 0) )) - action_rows = [] - for x in range(((len(buttons)-1)//4)+1): - row_buttons = buttons[ - (x*4):(min(len(buttons),x*4+4)) - ] - action_rows.append(create_actionrow(*row_buttons)) + custom_id = encode_id( + [ + "connectfour", + "end", + str(players[0]), + str(players[1]), + str(difficulty), + str(image_message.id) + ] + ) + buttons.append(create_button( + style=ButtonStyle.red, + label="Surrender", + custom_id=custom_id + )) - await image_message.edit( - components=action_rows - ) + action_rows = [] + for x in range(((len(buttons)-1)//4)+1): + row_buttons = buttons[ + (x*4):(min(len(buttons),x*4+4)) + ] + action_rows.append(create_actionrow(*row_buttons)) - async def place_piece(self, ctx: ComponentContext, board_string: str, - column: int, players: list[int], difficulty: int, - placer: int, message_id: int): - """ - Place a piece on the board. + await image_message.edit( + components=action_rows + ) - *Parameters* - column: int - The column the player is placing the piece in. - """ - old_message = await ctx.channel.fetch_message(message_id) - await old_message.delete() - player_number = players.index(placer)+1 - user_name = self.get_name(f"#{placer}") - placed_piece = False + async def surrender(self, ctx: ComponentContext, players: list, + difficulty: int, message_id: int): + """ + Surrender a connect four game. - board = _decode_board_string(board_string) - board = self._place_on_board(board, player_number, column) + *Parameters* + ------------ + ctx: SlashContext + The context of the command. + """ - if board is None: - send_message = "There isn't any room in that column" - log_message = "There wasn't any room in the column" - else: - turn = player_number % 2 + loser_index = players.index(ctx.author.id) + winner_index = (loser_index+1) % 2 + winner_id = players[winner_index] + winner_name = self.get_name(f"#{winner_id}") - self.bot.log("Checking for win") - winner, win_direction, win_coordinates = self._is_won(board) + send_message = f"{ctx.author.display_name} surrenders." + send_message += f" This means {winner_name} is the winner." + if winner_id != self.bot.user.id: + difficulty = int(difficulty) + reward = difficulty**2 + 5 + send_message += f" Adding {reward} to their account" - if winner != 0: - game_won = True - send_message = "{} placed a piece in column {} and won. " - send_message = send_message.format(user_name, column+1) - log_message = f"{user_name} won" - win_amount = difficulty**2+5 - if players[winner-1] != self.bot.user.id: - send_message += "Adding {} GwendoBucks to their account" - send_message = send_message.format(win_amount) - elif 0 not in board[0]: - game_won = True - send_message = "It's a draw!" - log_message = "The game ended in a draw" + await ctx.channel.send(send_message) + + self._end_game(winner_index+1, players, difficulty) + old_image = await ctx.channel.fetch_message(message_id) + await old_image.delete() + + + def _place_on_board(self, board: dict, player: int, column: int): + """ + Place a piece in a given board. + + 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* + ------------ + 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. + """ + placement_x, placement_y = -1, column + + for i, line in list(enumerate(board))[::-1]: + if line[column] == 0: + placement_x = i + break + + board[placement_x][placement_y] = player + + if placement_x == -1: + return None + else: + return board + + def _end_game(self, winner: int, players: list, difficulty: int): + if winner != 0: + if players[winner-1] != f"#{self.bot.user.id}": + difficulty = int(difficulty) + reward = difficulty**2 + 5 + self.bot.money.addMoney(f"#{players[winner-1]}", reward) + + def _is_won(self, board: dict): + won = 0 + win_direction = "" + win_coordinates = [0, 0] + + for row in range(ROWCOUNT): + for place in range(COLUMNCOUNT): + if won == 0: + piece_player = board[row][place] + if piece_player != 0: + # Checks horizontal + if place <= COLUMNCOUNT-4: + piece_one = board[row][place+1] + piece_two = board[row][place+2] + piece_three = board[row][place+3] + + pieces = [piece_one, piece_two, piece_three] else: - game_won = False - other_user_id = f"#{players[turn]}" - other_user_name = self.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" + pieces = [0] - gwendolyn_turn = (players[turn] == self.bot.user.id) + if all(x == piece_player for x in pieces): + won = piece_player + win_direction = "h" + win_coordinates = [row, place] - placed_piece = True + # Checks vertical + if row <= ROWCOUNT-4: + piece_one = board[row+1][place] + piece_two = board[row+2][place] + piece_three = board[row+3][place] - self.bot.log(log_message) - await ctx.channel.send(send_message) - - if placed_piece: - channel = str(ctx.channel) - self.draw.draw_image( - channel, - board, - players, - [winner, win_coordinates, win_direction] - ) - - - boards_path = "gwendolyn/resources/games/connect_four_boards/" - file_path = boards_path + f"board{channel}.png" - image_message = await ctx.channel.send(file=discord.File(file_path)) - - if game_won: - self._end_game(winner, players, difficulty) + pieces = [piece_one, piece_two, piece_three] else: - board_string = _encode_board_string(board) - if gwendolyn_turn: - await self._connect_four_ai( - ctx, board_string, players, difficulty, image_message.id - ) - else: - buttons = [] - for i in range(7): - custom_id = encode_id( - [ - "connectfour", - "place", - str(players[turn]), - board_string, - str(i), - str(players[0]), - str(players[1]), - str(difficulty), - str(image_message.id) - ] - ) - buttons.append(create_button( - style=ButtonStyle.blue, - label=str(i+1), - custom_id=custom_id, - disabled=(board[0][i] != 0) - )) + pieces = [0] - custom_id = encode_id( - [ - "connectfour", - "end", - str(players[0]), - str(players[1]), - str(difficulty), - str(image_message.id) - ] - ) - buttons.append(create_button( - style=ButtonStyle.red, - label="Surrender", - custom_id=custom_id - )) + if all(x == piece_player for x in pieces): + won = piece_player + win_direction = "v" + win_coordinates = [row, place] - action_rows = [] - for x in range(((len(buttons)-1)//4)+1): - row_buttons = buttons[ - (x*4):(min(len(buttons),x*4+4)) - ] - action_rows.append(create_actionrow(*row_buttons)) + # Checks right diagonal + 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] - await image_message.edit( - components=action_rows - ) + pieces = [piece_one, piece_two, piece_three] + else: + pieces = [0] + if all(x == piece_player for x in pieces): + won = piece_player + win_direction = "r" + win_coordinates = [row, place] - async def surrender(self, ctx: ComponentContext, players: list, - difficulty: int, message_id: int): - """ - Surrender a connect four game. + # Checks left diagonal + if row <= ROWCOUNT-4 and place >= 3: + piece_one = board[row+1][place-1] + piece_two = board[row+2][place-2] + piece_three = board[row+3][place-3] - *Parameters* - ------------ - ctx: SlashContext - The context of the command. - """ + pieces = [piece_one, piece_two, piece_three] + else: + pieces = [0] - loser_index = players.index(ctx.author.id) - winner_index = (loser_index+1) % 2 - winner_id = players[winner_index] - winner_name = self.get_name(f"#{winner_id}") + if all(i == piece_player for i in pieces): + won = piece_player + win_direction = "l" + win_coordinates = [row, place] - send_message = f"{ctx.author.display_name} surrenders." - send_message += f" This means {winner_name} is the winner." - if winner_id != self.bot.user.id: - difficulty = int(difficulty) - reward = difficulty**2 + 5 - send_message += f" Adding {reward} to their account" + return won, win_direction, win_coordinates - await ctx.channel.send(send_message) + async def _connect_four_ai(self, + ctx: Union[SlashContext, ComponentContext], + board_string: str, players: list, + difficulty: int, message_id: int): + 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 - self._end_game(winner_index+1, players, difficulty) - old_image = await ctx.channel.fetch_message(message_id) - await old_image.delete() + self.bot.log("Figuring out best move") + board = _decode_board_string(board_string) + player = players.index(self.bot.user.id)+1 - def _place_on_board(self, board: dict, player: int, column: int): - """ - Place a piece in a given board. + scores = [-math.inf for _ in range(COLUMNCOUNT)] + for column in range(COLUMNCOUNT): + 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, + -math.inf, + math.inf, + False + ] + scores[column] = await self._minimax(*minimax_parameters) + self.bot.log(f"Best score for column {column} is {scores[column]}") - This differs from place_piece() by not having anything to do - with any actual game. It just places the piece in a game dict. + possible_scores = scores.copy() - *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. + while out_of_range(possible_scores): + possible_scores.remove(min(possible_scores)) - *Returns* - --------- - board: dict - The board with the placed piece. - """ - placement_x, placement_y = -1, column + highest_score = random.choice(possible_scores) - for i, line in list(enumerate(board))[::-1]: - if line[column] == 0: - placement_x = i - break + best_columns = [i for i, x in enumerate(scores) if x == highest_score] + placement = random.choice(best_columns) - board[placement_x][placement_y] = player + await self.place_piece( + ctx, + board_string, + placement, + players, + difficulty, + self.bot.user.id, + message_id + ) - if placement_x == -1: - return None - else: - return board + def _ai_calc_points(self, board: dict, player: int): + score = 0 + other_player = player % 2+1 - def _end_game(self, winner: int, players: list, difficulty: int): - if winner != 0: - if players[winner-1] != f"#{self.bot.user.id}": - difficulty = int(difficulty) - reward = difficulty**2 + 5 - self.bot.money.addMoney(f"#{players[winner-1]}", reward) + # Adds points for middle placement + # Checks horizontal + for row in range(ROWCOUNT): + if board[row][3] == player: + score += self.AISCORES["middle"] + row_array = [int(i) for i in list(board[row])] + for place in range(COLUMNCOUNT-3): + window = row_array[place:place+4] + score += self._evaluate_window(window, player, other_player) - def _is_won(self, board: dict): - won = 0 - win_direction = "" - win_coordinates = [0, 0] + # Checks Vertical + for column in range(COLUMNCOUNT): + column_array = [int(i[column]) for i in list(board)] + for place in range(ROWCOUNT-3): + window = column_array[place:place+4] + score += self._evaluate_window(window, player, other_player) - for row in range(ROWCOUNT): - for place in range(COLUMNCOUNT): - if won == 0: - piece_player = board[row][place] - if piece_player != 0: - # Checks horizontal - if place <= COLUMNCOUNT-4: - piece_one = board[row][place+1] - piece_two = board[row][place+2] - piece_three = board[row][place+3] + # Checks right diagonal + for row in range(ROWCOUNT-3): + for place in range(COLUMNCOUNT-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] - pieces = [piece_one, piece_two, piece_three] - else: - pieces = [0] + window = [piece_one, piece_two, piece_three, piece_four] + score += self._evaluate_window(window, player, other_player) - if all(x == piece_player for x in pieces): - won = piece_player - win_direction = "h" - win_coordinates = [row, place] + for place in range(3, COLUMNCOUNT): + 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] - # Checks vertical - if row <= ROWCOUNT-4: - piece_one = board[row+1][place] - piece_two = board[row+2][place] - piece_three = board[row+3][place] + window = [piece_one, piece_two, piece_three, piece_four] + score += self._evaluate_window(window, player, other_player) - pieces = [piece_one, piece_two, piece_three] - else: - pieces = [0] + return score - if all(x == piece_player for x in pieces): - won = piece_player - win_direction = "v" - win_coordinates = [row, place] + 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(other_player) == 4: + return self.AISCORES["enemy win"] + else: + return 0 - # Checks right diagonal - 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] + async def _minimax(self, board: dict, depth: int, player: int, + original_player: int, alpha: int, beta: int, + maximizing_player: bool): + """ + Evaluate the minimax value of a board. - pieces = [piece_one, piece_two, piece_three] - else: - pieces = [0] + *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. + original_player: int + The AI player. + alpha, beta: int + Used with alpha/beta pruning, in order to prune options + that will never be picked. + maximizing_player: bool + Whether the current player is the one trying to + maximize their score. - 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: - piece_one = board[row+1][place-1] - piece_two = board[row+2][place-2] - piece_three = board[row+3][place-3] - - pieces = [piece_one, piece_two, piece_three] - else: - pieces = [0] - - if all(i == piece_player for i in pieces): - won = piece_player - win_direction = "l" - win_coordinates = [row, place] - - return won, win_direction, win_coordinates - - async def _connect_four_ai(self, - ctx: Union[SlashContext, ComponentContext], - board_string: str, players: list, - difficulty: int, message_id: int): - 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 - - self.bot.log("Figuring out best move") - - board = _decode_board_string(board_string) - player = players.index(self.bot.user.id)+1 - - scores = [-math.inf for _ in range(COLUMNCOUNT)] - for column in range(COLUMNCOUNT): - 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, - -math.inf, - math.inf, - False - ] - scores[column] = await self._minimax(*minimax_parameters) - self.bot.log(f"Best score for column {column} is {scores[column]}") - - possible_scores = scores.copy() - - while out_of_range(possible_scores): - possible_scores.remove(min(possible_scores)) - - highest_score = random.choice(possible_scores) - - best_columns = [i for i, x in enumerate(scores) if x == highest_score] - placement = random.choice(best_columns) - - await self.place_piece( - ctx, - board_string, - placement, - players, - difficulty, - self.bot.user.id, - message_id - ) - - def _ai_calc_points(self, board: dict, player: int): - score = 0 - 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"] - row_array = [int(i) for i in list(board[row])] - for place in range(COLUMNCOUNT-3): - window = row_array[place:place+4] - score += self._evaluate_window(window, player, other_player) - - # Checks Vertical - for column in range(COLUMNCOUNT): - column_array = [int(i[column]) for i in list(board)] - for place in range(ROWCOUNT-3): - 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): - 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 = [piece_one, piece_two, piece_three, piece_four] - score += self._evaluate_window(window, player, other_player) - - for place in range(3, COLUMNCOUNT): - 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 = [piece_one, piece_two, piece_three, piece_four] - score += self._evaluate_window(window, player, other_player) - - return score - - 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(other_player) == 4: - return self.AISCORES["enemy win"] - else: - return 0 - - async def _minimax(self, board: dict, depth: int, player: int, - original_player: int, alpha: int, beta: int, - maximizing_player: 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. - original_player: int - The AI player. - alpha, beta: int - Used with alpha/beta pruning, in order to prune options - that will never be picked. - maximizing_player: 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._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 maximizing_player: - value = -math.inf - for column in range(0, COLUMNCOUNT): - 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, - original_player, - alpha, - beta, - False - ] - evaluation = await self._minimax(*minimax_parameters) - 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, COLUMNCOUNT): - 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, - original_player, - alpha, - beta, - True - ] - evaluation = await self._minimax(*minimax_parameters) - if evaluation < -9000: - evaluation += self.AISCORES["avoid losing"] - value = min(value, evaluation) - beta = min(beta, evaluation) - if beta <= alpha: - break - return value + *Returns* + --------- + value: int + The minimax value of the board. + """ + terminal = 0 not in board[0] + 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 maximizing_player: + value = -math.inf + for column in range(0, COLUMNCOUNT): + 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, + original_player, + alpha, + beta, + False + ] + evaluation = await self._minimax(*minimax_parameters) + 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, COLUMNCOUNT): + 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, + original_player, + alpha, + beta, + True + ] + evaluation = await self._minimax(*minimax_parameters) + if evaluation < -9000: + evaluation += self.AISCORES["avoid losing"] + value = min(value, evaluation) + beta = min(beta, evaluation) + if beta <= alpha: + break + return value class DrawConnectFour(): + """ + Functions for drawing the connect four board. + + *Methods* + --------- + draw_image(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 win_bar_alpha. + """ + + def __init__(self, bot, game): + """Initialize the class.""" + self.bot = bot + self.get_name = self.bot.database_funcs.get_name + 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 + 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) + self.WINBARCOLOR = (250, 250, 250, 255) + self.PIECESIZE = 300 + # pylint: enable=invalid-name + + 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) + + # pylint: disable=invalid-name + self.FONT = ImageFont.truetype(font_path, self.TEXTSIZE) + self.PIECEGRIDSIZE = [piece_width, piece_height] + + self.PIECESTARTX = piece_start+(self.PIECEGRIDSIZE[0]//2) + self.PIECESTARTY = piece_start+(self.PIECEGRIDSIZE[1]//2) + + self.WINBARWHITE = (255, 255, 255, win_bar_alpha) + # pylint: enable=invalid-name + + # Draws the whole thing + def draw_image(self, channel: str, board: list, players: list, + win_info: list): """ - Functions for drawing the connect four board. + Draw an image of the connect four board. - *Methods* - --------- - draw_image(channel: str) - - *Attributes* + *Parameters* ------------ - 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 win_bar_alpha. """ + self.bot.log("Drawing connect four board") - def __init__(self, bot, game): - """Initialize the class.""" - self.bot = bot - self.get_name = self.bot.database_funcs.get_name - win_bar_alpha = 160 - font_path = "gwendolyn/resources/fonts/futura-bold.ttf" + image_size = (self.WIDTH, self.HEIGHT+self.BOTTOMBORDER) + background = Image.new("RGB", image_size, self.BACKGROUNDCOLOR) + drawer = ImageDraw.Draw(background, "RGBA") - # pylint: disable=invalid-name - self.BORDER = 40 - self.GRIDBORDER = 40 - self.CORNERSIZE = 300 - self.BOARDOUTLINESIZE = 10 - self.BOARDOUTLINECOLOR = (0, 0, 0) + self._draw_board(drawer) - self.PIECEOUTLINESIZE = 10 - self.PIECEOUTLINECOLOR = (244, 244, 248) - self.EMPTYOUTLINESIZE = 0 - self.EMPTYOUTLINECOLOR = (0, 0, 0) + self._draw_pieces(drawer, board) - 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) - self.WINBARCOLOR = (250, 250, 250, 255) - self.PIECESIZE = 300 - # pylint: enable=invalid-name + if win_info[0] != 0: + self._draw_win(background, win_info[1], win_info[2]) - 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) + self._draw_footer(drawer, players) - # pylint: disable=invalid-name - self.FONT = ImageFont.truetype(font_path, self.TEXTSIZE) - self.PIECEGRIDSIZE = [piece_width, piece_height] + boards_path = "gwendolyn/resources/games/connect_four_boards/" + background.save(boards_path + f"board{channel}.png") - self.PIECESTARTX = piece_start+(self.PIECEGRIDSIZE[0]//2) - self.PIECESTARTY = piece_start+(self.PIECEGRIDSIZE[1]//2) + def _draw_board(self, drawer: ImageDraw): + # This whole part was the easiest way to make a rectangle with + # rounded corners and an outline + draw_parameters = { + "fill": self.BOARDCOLOR, + "outline": self.BOARDOUTLINECOLOR, + "width": self.BOARDOUTLINESIZE + } - self.WINBARWHITE = (255, 255, 255, win_bar_alpha) - # pylint: enable=invalid-name - - # Draws the whole thing - def draw_image(self, channel: str, board: list, players: list, - win_info: list): - """ - Draw an image of the connect four board. - - *Parameters* - ------------ - """ - self.bot.log("Drawing connect four board") - - image_size = (self.WIDTH, self.HEIGHT+self.BOTTOMBORDER) - background = Image.new("RGB", image_size, self.BACKGROUNDCOLOR) - drawer = ImageDraw.Draw(background, "RGBA") - - self._draw_board(drawer) - - self._draw_pieces(drawer, board) - - if win_info[0] != 0: - self._draw_win(background, win_info[1], win_info[2]) - - self._draw_footer(drawer, players) - - boards_path = "gwendolyn/resources/games/connect_four_boards/" - background.save(boards_path + f"board{channel}.png") - - def _draw_board(self, drawer: ImageDraw): - # This whole part was the easiest way to make a rectangle with - # rounded corners and an outline - draw_parameters = { - "fill": self.BOARDCOLOR, - "outline": self.BOARDOUTLINECOLOR, - "width": self.BOARDOUTLINESIZE - } - - # - Corners -: - corner_positions = [ - [ - ( - 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 - ) - ] - ] - - 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 -: - rectangle_positions = [ - [ - ( - 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)) - ) - ] - ] - drawer.rectangle(rectangle_positions[0], **draw_parameters) - drawer.rectangle(rectangle_positions[1], **draw_parameters) - - # - Removing outline on the inside -: - clean_up_positions = [ - [ - ( - 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)) - ) - ] - ] - 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 _draw_pieces(self, drawer: ImageDraw, board: list): - for piece_x, line in enumerate(board): - for piece_y, piece in enumerate(line): - - if piece == 1: - piece_color = self.PLAYER1COLOR - outline_width = self.PIECEOUTLINESIZE - outline_color = self.PIECEOUTLINECOLOR - elif piece == 2: - piece_color = self.PLAYER2COLOR - outline_width = self.PIECEOUTLINESIZE - outline_color = self.PIECEOUTLINECOLOR - else: - piece_color = self.BACKGROUNDCOLOR - outline_width = self.EMPTYOUTLINESIZE - outline_color = self.EMPTYOUTLINECOLOR - - start_x = self.PIECESTARTX + self.PIECEGRIDSIZE[0]*piece_y - start_y = self.PIECESTARTY + self.PIECEGRIDSIZE[1]*piece_x - - position = [ - (start_x, start_y), - (start_x+self.PIECESIZE, start_y+self.PIECESIZE) - ] - - piece_parameters = { - "fill": piece_color, - "outline": outline_color, - "width": outline_width - } - drawer.ellipse(position, **piece_parameters) - - def _draw_win(self, background: Image, win_coordinates: list, - win_direction: str): - """ - Draw the bar that shows the winning pieces. - - *Parameters* - ------------ - background: Image - The image of the board. - game: dict - The game data. - """ - start = self.BORDER + self.GRIDBORDER - start_x = start + self.PIECEGRIDSIZE[0]*win_coordinates[1] - start_y = start + self.PIECEGRIDSIZE[1]*win_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] + # - Corners -: + corner_positions = [ + [ + ( + self.BORDER, + self.BORDER + ), ( + self.BORDER+self.CORNERSIZE, + self.BORDER+self.CORNERSIZE ) - size_ratio = self.PIECEGRIDSIZE[1]/self.PIECEGRIDSIZE[0] - diagonal_angle = math.degrees(math.atan(size_ratio)) + ], [ + ( + 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 + ) + ] + ] - if win_direction == "h": - 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]) - ], [ - ((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]) - ] - ] - 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) + 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) - elif win_direction == "v": - 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]) - ], [ - (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)) - ] - ] - 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) + # - Rectangle -: + rectangle_positions = [ + [ + ( + 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)) + ) + ] + ] + drawer.rectangle(rectangle_positions[0], **draw_parameters) + drawer.rectangle(rectangle_positions[1], **draw_parameters) - elif win_direction == "r": - 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]*(diagonal_length-1)), - 0 - ), ( - self.PIECEGRIDSIZE[0]*diagonal_length, - self.PIECEGRIDSIZE[1] - ) - ], [ - ( - int(self.PIECEGRIDSIZE[0]*0.5), - 0 - ), ( - int(self.PIECEGRIDSIZE[0]*(diagonal_length-0.5)), - self.PIECEGRIDSIZE[1] - ) - ] - ] - 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 + # - Removing outline on the inside -: + clean_up_positions = [ + [ + ( + 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)) + ) + ] + ] + 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) - elif win_direction == "l": - 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]*(diagonal_length-1)), - 0 - ), ( - self.PIECEGRIDSIZE[0]*diagonal_length, - self.PIECEGRIDSIZE[1] - ) - ], [ - ( - int(self.PIECEGRIDSIZE[0]*0.5), - 0 - ), ( - int(self.PIECEGRIDSIZE[0]*(diagonal_length-0.5)), - self.PIECEGRIDSIZE[1] - ) - ] - ] - 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 + def _draw_pieces(self, drawer: ImageDraw, board: list): + for piece_x, line in enumerate(board): + for piece_y, piece in enumerate(line): - mask = win_bar.copy() - - win_bar_image = Image.new("RGBA", mask.size, color=self.WINBARCOLOR) - background.paste(win_bar_image, (start_x, start_y), mask) - - def _draw_footer(self, drawer: ImageDraw, players: list): - if players[0] == "Gwendolyn": - player1 = "Gwendolyn" + if piece == 1: + piece_color = self.PLAYER1COLOR + outline_width = self.PIECEOUTLINESIZE + outline_color = self.PIECEOUTLINECOLOR + elif piece == 2: + piece_color = self.PLAYER2COLOR + outline_width = self.PIECEOUTLINESIZE + outline_color = self.PIECEOUTLINECOLOR else: - player1 = self.get_name(f"#{players[0]}") + piece_color = self.BACKGROUNDCOLOR + outline_width = self.EMPTYOUTLINESIZE + outline_color = self.EMPTYOUTLINECOLOR - if players[1] == "Gwendolyn": - player2 = "Gwendolyn" - else: - player2 = self.get_name(f"#{players[1]}") + start_x = self.PIECESTARTX + self.PIECEGRIDSIZE[0]*piece_y + start_y = self.PIECESTARTY + self.PIECEGRIDSIZE[1]*piece_x - 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, - circle_height - ), ( - self.BORDER+self.TEXTSIZE, - circle_height+self.TEXTSIZE - ) - ], [ - ( - self.WIDTH-circle_right_padding, - circle_height - ), ( - self.WIDTH-self.BORDER-text_width-self.TEXTPADDING, - circle_height+self.TEXTSIZE - ) - ] + position = [ + (start_x, start_y), + (start_x+self.PIECESIZE, start_y+self.PIECESIZE) ] - circle_parameters = { - "outline": self.BOARDOUTLINECOLOR, - "width": 3 + + piece_parameters = { + "fill": piece_color, + "outline": outline_color, + "width": outline_width } - drawer.ellipse( - circle_positions[0], - fill=self.PLAYER1COLOR, - **circle_parameters - ) - drawer.ellipse( - circle_positions[1], - fill=self.PLAYER2COLOR, - **circle_parameters - ) + drawer.ellipse(position, **piece_parameters) - text_positions = [ - (self.BORDER+self.TEXTSIZE+self.TEXTPADDING, circle_height), - (self.WIDTH-self.BORDER-text_width, circle_height) + def _draw_win(self, background: Image, win_coordinates: list, + win_direction: str): + """ + Draw the bar that shows the winning pieces. + + *Parameters* + ------------ + background: Image + The image of the board. + game: dict + The game data. + """ + start = self.BORDER + self.GRIDBORDER + start_x = start + self.PIECEGRIDSIZE[0]*win_coordinates[1] + start_y = start + self.PIECEGRIDSIZE[1]*win_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 win_direction == "h": + 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]) + ], [ + ((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]) ] - 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)) + ] + 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 win_direction == "v": + 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]) + ], [ + (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)) + ] + ] + 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 win_direction == "r": + 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]*(diagonal_length-1)), + 0 + ), ( + self.PIECEGRIDSIZE[0]*diagonal_length, + self.PIECEGRIDSIZE[1] + ) + ], [ + ( + int(self.PIECEGRIDSIZE[0]*0.5), + 0 + ), ( + int(self.PIECEGRIDSIZE[0]*(diagonal_length-0.5)), + self.PIECEGRIDSIZE[1] + ) + ] + ] + 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 win_direction == "l": + 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]*(diagonal_length-1)), + 0 + ), ( + self.PIECEGRIDSIZE[0]*diagonal_length, + self.PIECEGRIDSIZE[1] + ) + ], [ + ( + int(self.PIECEGRIDSIZE[0]*0.5), + 0 + ), ( + int(self.PIECEGRIDSIZE[0]*(diagonal_length-0.5)), + self.PIECEGRIDSIZE[1] + ) + ] + ] + 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 = win_bar.copy() + + win_bar_image = Image.new("RGBA", mask.size, color=self.WINBARCOLOR) + background.paste(win_bar_image, (start_x, start_y), mask) + + def _draw_footer(self, drawer: ImageDraw, players: list): + if players[0] == "Gwendolyn": + player1 = "Gwendolyn" + else: + player1 = self.get_name(f"#{players[0]}") + + if players[1] == "Gwendolyn": + player2 = "Gwendolyn" + else: + player2 = self.get_name(f"#{players[1]}") + + 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, + circle_height + ), ( + self.BORDER+self.TEXTSIZE, + circle_height+self.TEXTSIZE + ) + ], [ + ( + self.WIDTH-circle_right_padding, + circle_height + ), ( + self.WIDTH-self.BORDER-text_width-self.TEXTPADDING, + circle_height+self.TEXTSIZE + ) + ] + ] + circle_parameters = { + "outline": self.BOARDOUTLINECOLOR, + "width": 3 + } + drawer.ellipse( + circle_positions[0], + fill=self.PLAYER1COLOR, + **circle_parameters + ) + drawer.ellipse( + circle_positions[1], + fill=self.PLAYER2COLOR, + **circle_parameters + ) + + text_positions = [ + (self.BORDER+self.TEXTSIZE+self.TEXTPADDING, circle_height), + (self.WIDTH-self.BORDER-text_width, circle_height) + ] + 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_old/funcs/games/game_base.py b/gwendolyn_old/funcs/games/game_base.py index 857a0c0..7ddaaac 100644 --- a/gwendolyn_old/funcs/games/game_base.py +++ b/gwendolyn_old/funcs/games/game_base.py @@ -13,306 +13,306 @@ from gwendolyn_old.exceptions import GameNotInDatabase from gwendolyn_old.utils import encode_id class GameBase(): - """The base class for the games.""" + """The base class for the games.""" - def __init__(self, bot, game_name: int, drawer): - """Initialize the class.""" - self.bot = bot - self.long_strings = self.bot.long_strings - self.resources = "gwendolyn/resources/games/" - self.game_name = game_name - self.draw = drawer(bot, self) + def __init__(self, bot, game_name: int, drawer): + """Initialize the class.""" + self.bot = bot + self.long_strings = self.bot.long_strings + self.resources = "gwendolyn/resources/games/" + self.game_name = game_name + self.draw = drawer(bot, self) - def _get_action_rows(self, buttons: list[tuple[str, list]]): - self.bot.log("Generation action rows") - button_objects = [] - for label, data, style in buttons: - custom_id = encode_id([self.game_name] + data) - button_objects.append(Button( - style=style, - label=label, - custom_id=custom_id - )) + def _get_action_rows(self, buttons: list[tuple[str, list]]): + self.bot.log("Generation action rows") + button_objects = [] + for label, data, style in buttons: + custom_id = encode_id([self.game_name] + data) + button_objects.append(Button( + style=style, + label=label, + custom_id=custom_id + )) - action_rows = [] - for i in range(((len(button_objects)-1)//5)+1): - action_rows.append(ActionRow(*button_objects[i*5:i*5+5])) + action_rows = [] + for i in range(((len(button_objects)-1)//5)+1): + action_rows.append(ActionRow(*button_objects[i*5:i*5+5])) - return action_rows + return action_rows - async def _send_image(self, channel: Messageable, - buttons: list[tuple[str, list]] = None, delete=True): - self.draw.draw(str(channel.id)) - file_path = f"{self.resources}images/{self.game_name}{channel.id}.png" - old_image = await channel.send( - file=File(file_path), delete_after=120 if delete else None) + async def _send_image(self, channel: Messageable, + buttons: list[tuple[str, list]] = None, delete=True): + self.draw.draw(str(channel.id)) + file_path = f"{self.resources}images/{self.game_name}{channel.id}.png" + old_image = await channel.send( + file=File(file_path), delete_after=120 if delete else None) - if buttons is not None and len(buttons) < 25: - await old_image.edit(components = self._get_action_rows(buttons)) + if buttons is not None and len(buttons) < 25: + await old_image.edit(components = self._get_action_rows(buttons)) - return old_image + return old_image class DatabaseGame(GameBase): - """The base class for the games.""" + """The base class for the games.""" - def __init__(self, bot, game_name, drawer): - """Initialize the class.""" - super().__init__(bot, game_name, drawer) - self.database = self.bot.database - self.old_images_path = f"{self.resources}old_images/{self.game_name}" + def __init__(self, bot, game_name, drawer): + """Initialize the class.""" + super().__init__(bot, game_name, drawer) + self.database = self.bot.database + self.old_images_path = f"{self.resources}old_images/{self.game_name}" - def access_document(self, channel: str, collection_name: str="games", - raise_missing_error: bool=True): - collection = self.bot.database[f"{self.game_name} {collection_name}"] - game = collection.find_one({"_id": channel}) - if game is None and raise_missing_error: - raise GameNotInDatabase(self.game_name, channel) + def access_document(self, channel: str, collection_name: str="games", + raise_missing_error: bool=True): + collection = self.bot.database[f"{self.game_name} {collection_name}"] + game = collection.find_one({"_id": channel}) + if game is None and raise_missing_error: + raise GameNotInDatabase(self.game_name, channel) - return game + return game - def _test_document(self, channel: str, collection_name: str="games"): - collection = self.bot.database[f"{self.game_name} {collection_name}"] - game = collection.find_one({"_id": channel}) - return game is not None + def _test_document(self, channel: str, collection_name: str="games"): + collection = self.bot.database[f"{self.game_name} {collection_name}"] + game = collection.find_one({"_id": channel}) + return game is not None - def _update_document(self, channel: str, updater: dict, - collection_name: str="games"): - self.database[f"{self.game_name} {collection_name}"].update_one( - {"_id": channel}, - updater, - upsert=True - ) + def _update_document(self, channel: str, updater: dict, + collection_name: str="games"): + self.database[f"{self.game_name} {collection_name}"].update_one( + {"_id": channel}, + updater, + upsert=True + ) - def _delete_document(self, channel: str, collection_name: str="games"): - self.database[f"{self.game_name} {collection_name}"].delete_one( - {"_id": channel} - ) + def _delete_document(self, channel: str, collection_name: str="games"): + self.database[f"{self.game_name} {collection_name}"].delete_one( + {"_id": channel} + ) - def _insert_document(self, data: dict, collection_name: str="games"): - self.database[f"{self.game_name} {collection_name}"].insert_one(data) + def _insert_document(self, data: dict, collection_name: str="games"): + self.database[f"{self.game_name} {collection_name}"].insert_one(data) - async def _delete_old_image(self, channel: Messageable): - with open(self.old_images_path + str(channel.id), "r") as file_pointer: - old_image = await channel.fetch_message(int(file_pointer.read())) + async def _delete_old_image(self, channel: Messageable): + with open(self.old_images_path + str(channel.id), "r") as file_pointer: + old_image = await channel.fetch_message(int(file_pointer.read())) - await old_image.delete() + await old_image.delete() - async def _send_image(self, channel: Messageable, - buttons: list[tuple[str, list]] = None, delete=True): - old_image = await super()._send_image(channel, buttons, delete) - with open(self.old_images_path + str(channel.id), "w") as file_pointer: - file_pointer.write(str(old_image.id)) + async def _send_image(self, channel: Messageable, + buttons: list[tuple[str, list]] = None, delete=True): + old_image = await super()._send_image(channel, buttons, delete) + with open(self.old_images_path + str(channel.id), "w") as file_pointer: + file_pointer.write(str(old_image.id)) - async def _start_new(self, channel: Messageable, new_game: dict, - buttons: list[tuple[str, list]] = None, - delete = True): - new_game['_id'] = str(channel.id) - self._insert_document(new_game) - await self._send_image(channel, buttons, delete) + async def _start_new(self, channel: Messageable, new_game: dict, + buttons: list[tuple[str, list]] = None, + delete = True): + new_game['_id'] = str(channel.id) + self._insert_document(new_game) + await self._send_image(channel, buttons, delete) - async def _end_game(self, channel: Messageable): - await self._delete_old_image(channel) - await self._send_image(channel, delete=False) - self._delete_document(str(channel.id)) + async def _end_game(self, channel: Messageable): + await self._delete_old_image(channel) + await self._send_image(channel, delete=False) + self._delete_document(str(channel.id)) class CardGame(DatabaseGame): - """The class for card games.""" - def __init__(self, bot, game_name, drawer, deck_used): - """Initialize the class.""" - super().__init__(bot, game_name, drawer) - self.decks_used = deck_used + """The class for card games.""" + def __init__(self, bot, game_name, drawer, deck_used): + """Initialize the class.""" + super().__init__(bot, game_name, drawer) + self.decks_used = deck_used - def _shuffle_cards(self, channel: str): - self.bot.log(f"Shuffling cards for {self.game_name}") - with open(f"{self.resources}deck_of_cards.txt", "r") as file_pointer: - deck = file_pointer.read() + def _shuffle_cards(self, channel: str): + self.bot.log(f"Shuffling cards for {self.game_name}") + with open(f"{self.resources}deck_of_cards.txt", "r") as file_pointer: + deck = file_pointer.read() - all_decks = deck.split("\n") * self.decks_used - random.shuffle(all_decks) + all_decks = deck.split("\n") * self.decks_used + random.shuffle(all_decks) - self._update_document( - channel, - {"$set": {"_id": channel, "cards": all_decks}}, - "cards" - ) + self._update_document( + channel, + {"$set": {"_id": channel, "cards": all_decks}}, + "cards" + ) - def _draw_card(self, channel: str): - """ - Draw a card from the stack. + def _draw_card(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") + *Parameters* + ------------ + channel: str + The id of the channel the card is drawn in. + """ + self.bot.log("drawing a card") - drawn_card = self.access_document(channel, "cards")["cards"][0] - self._update_document(channel, {"$pop": {"cards": -1}}, "cards") + drawn_card = self.access_document(channel, "cards")["cards"][0] + self._update_document(channel, {"$pop": {"cards": -1}}, "cards") - return drawn_card + return drawn_card class BoardGame(GameBase): - async def _test_opponent(self, ctx: IntCont, opponent: Union[int, User]): - if isinstance(opponent, int): - # Opponent is Gwendolyn - if opponent in range(1, 6): - difficulty = int(opponent) - difficulty_text = f" with difficulty {difficulty}" - opponent = self.bot.user.id - else: - await ctx.send("Difficulty doesn't exist") - self.bot.log("They challenged a difficulty that doesn't exist") - return False - elif isinstance(opponent, User): - if opponent.bot: - # User has challenged a bot - if opponent == self.bot.user: - # It was Gwendolyn - difficulty = 3 - difficulty_text = f" with difficulty {difficulty}" - opponent = self.bot.user.id - else: - await ctx.send("You can't challenge a bot!") - self.bot.log("They tried to challenge a bot") - return False - else: - # Opponent is another player - if ctx.author != opponent: - opponent = opponent.id - difficulty = 5 - difficulty_text = "" - else: - await ctx.send("You can't play against yourself") - self.bot.log("They tried to play against themself") - return False + async def _test_opponent(self, ctx: IntCont, opponent: Union[int, User]): + if isinstance(opponent, int): + # Opponent is Gwendolyn + if opponent in range(1, 6): + difficulty = int(opponent) + difficulty_text = f" with difficulty {difficulty}" + opponent = self.bot.user.id + else: + await ctx.send("Difficulty doesn't exist") + self.bot.log("They challenged a difficulty that doesn't exist") + return False + elif isinstance(opponent, User): + if opponent.bot: + # User has challenged a bot + if opponent == self.bot.user: + # It was Gwendolyn + difficulty = 3 + difficulty_text = f" with difficulty {difficulty}" + opponent = self.bot.user.id + else: + await ctx.send("You can't challenge a bot!") + self.bot.log("They tried to challenge a bot") + return False + else: + # Opponent is another player + if ctx.author != opponent: + opponent = opponent.id + difficulty = 5 + difficulty_text = "" + else: + await ctx.send("You can't play against yourself") + self.bot.log("They tried to play against themself") + return False - return opponent, (difficulty, difficulty_text) + return opponent, (difficulty, difficulty_text) class BaseDrawer(): - """Class for drawing games.""" - def __init__(self, bot, game: GameBase): - self.bot = bot - self.game = game - self.fonts_path = "gwendolyn/resources/fonts/" - self.font_name = "futura-bold" + """Class for drawing games.""" + def __init__(self, bot, game: GameBase): + self.bot = bot + self.game = game + self.fonts_path = "gwendolyn/resources/fonts/" + self.font_name = "futura-bold" - self.resources = game.resources - game_name = game.game_name - self.default_image = f"{self.resources}default_images/{game_name}.png" - self.default_size = None - self.default_color = None - self.images_path = f"{self.resources}images/{game_name}" + self.resources = game.resources + game_name = game.game_name + self.default_image = f"{self.resources}default_images/{game_name}.png" + self.default_size = None + self.default_color = None + self.images_path = f"{self.resources}images/{game_name}" - def _get_size(self, game: dict): - return self.default_size + def _get_size(self, game: dict): + return self.default_size - def _draw_image(self, game: dict, image: Image.Image): - pass + def _draw_image(self, game: dict, image: Image.Image): + pass - def draw(self, channel: str): - game = self.game.access_document(channel) - if self.default_image is not None: - image = Image.open(self.default_image) - else: - image = Image.new("RGB", self._get_size(game), self.default_color) - self._draw_image(game, image) - self._save_image(image, channel) + def draw(self, channel: str): + game = self.game.access_document(channel) + if self.default_image is not None: + image = Image.open(self.default_image) + else: + image = Image.new("RGB", self._get_size(game), self.default_color) + self._draw_image(game, image) + self._save_image(image, channel) - def _get_font(self, size: int, font_name: str = None): - if font_name is None: - font_name = self.font_name - return ImageFont.truetype(f"{self.fonts_path}{font_name}.ttf", size) + def _get_font(self, size: int, font_name: str = None): + if font_name is None: + font_name = self.font_name + return ImageFont.truetype(f"{self.fonts_path}{font_name}.ttf", size) - def _adjust_font(self, max_font_size: int, text: str, max_text_size: int, - font_name: str = None): - font_size = max_font_size - font = self._get_font(font_size, font_name) - text_width = font.getsize(text)[0] + def _adjust_font(self, max_font_size: int, text: str, max_text_size: int, + font_name: str = None): + font_size = max_font_size + font = self._get_font(font_size, font_name) + text_width = font.getsize(text)[0] - while text_width > max_text_size: - font_size -= 1 - font = self._get_font(font_size, font_name) - text_width = font.getsize(text)[0] + while text_width > max_text_size: + font_size -= 1 + font = self._get_font(font_size, font_name) + text_width = font.getsize(text)[0] - return font, font_size + return font, font_size - def _save_image(self, image: Image.Image, channel: str): - self.bot.log("Saving image") - image.save(f"{self.images_path}{channel}.png") + def _save_image(self, image: Image.Image, channel: str): + self.bot.log("Saving image") + image.save(f"{self.images_path}{channel}.png") - def _draw_shadow_text(self, text: str, colors: list[tuple[int, int, int]], - font_size: int): - font = self._get_font(font_size) - offset = font_size//20 - shadow_offset = font_size//10 + def _draw_shadow_text(self, text: str, colors: list[tuple[int, int, int]], + font_size: int): + font = self._get_font(font_size) + offset = font_size//20 + shadow_offset = font_size//10 - text_size = list(font.getsize(text)) - text_size[0] += 1 + (offset * 2) - text_size[1] += 1 + (offset * 2) + shadow_offset + text_size = list(font.getsize(text)) + text_size[0] += 1 + (offset * 2) + text_size[1] += 1 + (offset * 2) + shadow_offset - image = Image.new("RGBA", tuple(text_size), (0, 0, 0, 0)) - text_image = ImageDraw.Draw(image) + image = Image.new("RGBA", tuple(text_size), (0, 0, 0, 0)) + text_image = ImageDraw.Draw(image) - for color_index, color in enumerate(colors[:-1]): - color_offset = offset//(color_index+1) - if color_index == 0: - color_shadow_offset = shadow_offset - else: - color_shadow_offset = 0 + for color_index, color in enumerate(colors[:-1]): + color_offset = offset//(color_index+1) + if color_index == 0: + color_shadow_offset = shadow_offset + else: + color_shadow_offset = 0 - for i in range(4): - x_pos = [-1,1][i % 2] * color_offset - y_pos = [-1,1][(i//2) % 2] * color_offset + color_shadow_offset - position = (offset + x_pos, offset + y_pos) - text_image.text(position, text, fill=color, font=font) + for i in range(4): + x_pos = [-1,1][i % 2] * color_offset + y_pos = [-1,1][(i//2) % 2] * color_offset + color_shadow_offset + position = (offset + x_pos, offset + y_pos) + text_image.text(position, text, fill=color, font=font) - text_image.text((offset, offset), text, fill=colors[-1], font=font) + text_image.text((offset, offset), text, fill=colors[-1], font=font) - return image + return image class CardDrawer(BaseDrawer): - def __init__(self, bot, game: GameBase): - super().__init__(bot, game) - self.cards_path = f"{self.resources}cards/" + def __init__(self, bot, game: GameBase): + super().__init__(bot, game) + self.cards_path = f"{self.resources}cards/" - # pylint: disable=invalid-name - self.CARD_WIDTH = 691 - self.CARD_HEIGHT = 1065 + # pylint: disable=invalid-name + self.CARD_WIDTH = 691 + self.CARD_HEIGHT = 1065 - self.CARD_BORDER = 100 - self.CARD_OFFSET = 125 - # pylint: enable=invalid-name + self.CARD_BORDER = 100 + self.CARD_OFFSET = 125 + # pylint: enable=invalid-name - def _set_card_size(self, wanted_card_width: int): - ratio = wanted_card_width / self.CARD_WIDTH - self.CARD_WIDTH = wanted_card_width - self.CARD_HEIGHT = int(self.CARD_HEIGHT * ratio) - self.CARD_BORDER = int(self.CARD_BORDER * ratio) - self.CARD_OFFSET = int(self.CARD_OFFSET * ratio) + def _set_card_size(self, wanted_card_width: int): + ratio = wanted_card_width / self.CARD_WIDTH + self.CARD_WIDTH = wanted_card_width + self.CARD_HEIGHT = int(self.CARD_HEIGHT * ratio) + self.CARD_BORDER = int(self.CARD_BORDER * ratio) + self.CARD_OFFSET = int(self.CARD_OFFSET * ratio) - def _draw_cards(self, hand: list[str], hidden_cards: int = 0): - image_width = (self.CARD_BORDER * 2) + self.CARD_WIDTH - image_width += (self.CARD_OFFSET * (len(hand)-1)) - image_size = (image_width, (self.CARD_BORDER * 2) + self.CARD_HEIGHT) - background = Image.new("RGBA", image_size, (0, 0, 0, 0)) + def _draw_cards(self, hand: list[str], hidden_cards: int = 0): + image_width = (self.CARD_BORDER * 2) + self.CARD_WIDTH + image_width += (self.CARD_OFFSET * (len(hand)-1)) + image_size = (image_width, (self.CARD_BORDER * 2) + self.CARD_HEIGHT) + background = Image.new("RGBA", image_size, (0, 0, 0, 0)) - for i, card in enumerate(hand): - position = ( - self.CARD_BORDER + (self.CARD_OFFSET*i), - self.CARD_BORDER - ) + for i, card in enumerate(hand): + position = ( + self.CARD_BORDER + (self.CARD_OFFSET*i), + self.CARD_BORDER + ) - if i + hidden_cards < len(hand): - card_image = Image.open(f"{self.cards_path}{card.upper()}.png") - else: - card_image = Image.open(f"{self.cards_path}red_back.png") + if i + hidden_cards < len(hand): + card_image = Image.open(f"{self.cards_path}{card.upper()}.png") + else: + card_image = Image.open(f"{self.cards_path}red_back.png") - card_image = card_image.resize( - (self.CARD_WIDTH, self.CARD_HEIGHT), - resample=Image.BILINEAR - ) - background.paste(card_image, position, card_image) + card_image = card_image.resize( + (self.CARD_WIDTH, self.CARD_HEIGHT), + resample=Image.BILINEAR + ) + background.paste(card_image, position, card_image) - return background + return background diff --git a/gwendolyn_old/funcs/games/games_container.py b/gwendolyn_old/funcs/games/games_container.py index 794a150..aa1aaa8 100644 --- a/gwendolyn_old/funcs/games/games_container.py +++ b/gwendolyn_old/funcs/games/games_container.py @@ -3,8 +3,8 @@ Has a container for game functions. *Classes* --------- - Games - Container for game functions. + Games + Container for game functions. """ @@ -17,30 +17,30 @@ from .wordle import WordleGame class Games(): - """ - Contains game classes. + """ + Contains game classes. - *Attributes* - ------------ - bot: Gwendolyn - The instance of Gwendolyn. - blackjack - Contains blackjack functions. - connect_four - Contains connect four functions. - hangman - Contains hangman functions. - hex - Contains hex functions - """ + *Attributes* + ------------ + bot: Gwendolyn + The instance of Gwendolyn. + blackjack + Contains blackjack functions. + connect_four + Contains connect four functions. + hangman + Contains hangman functions. + hex + Contains hex functions + """ - def __init__(self, bot): - """Initialize the container.""" - self.bot = bot + def __init__(self, bot): + """Initialize the container.""" + self.bot = bot - self.trivia = Trivia(bot) - self.blackjack = Blackjack(bot) - self.connect_four = ConnectFour(bot) - self.hangman = Hangman(bot) - self.hex = HexGame(bot) - self.wordle = WordleGame(bot) + self.trivia = Trivia(bot) + self.blackjack = Blackjack(bot) + self.connect_four = ConnectFour(bot) + self.hangman = Hangman(bot) + self.hex = HexGame(bot) + self.wordle = WordleGame(bot) diff --git a/gwendolyn_old/funcs/games/hangman.py b/gwendolyn_old/funcs/games/hangman.py index debf293..652c42f 100644 --- a/gwendolyn_old/funcs/games/hangman.py +++ b/gwendolyn_old/funcs/games/hangman.py @@ -3,10 +3,10 @@ Deals with commands and logic for hangman games. *Classes* --------- - Hangman() - Deals with the game logic of hangman. - DrawHangman() - Draws the image shown to the player. + Hangman() + Deals with the game logic of hangman. + DrawHangman() + Draws the image shown to the player. """ import os import datetime # Used for generating the game id @@ -26,632 +26,632 @@ from PIL import ImageDraw, Image, ImageFont # Used to draw the image from gwendolyn_old.utils import encode_id class Hangman(): - """ - Controls hangman commands and game logic. + """ + Controls hangman commands and game logic. - *Methods* - --------- - start(ctx: SlashContext) - stop(ctx: SlashContext) - guess(message: discord.message, user: str, guess: str) + *Methods* + --------- + start(ctx: SlashContext) + stop(ctx: SlashContext) + guess(message: discord.message, user: str, guess: str) + """ + + def __init__(self, bot): + """ + Initialize the class. + + *Attributes* + ------------ + draw: DrawHangman + The DrawHangman used to draw the hangman image. + 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._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, + "minDictionaryCount": 1, + "maxDictionaryCount": -1, + "minLength": 3, + "maxLength": 11, + "limit": 1, + "api_key": api_key + } + + async def start(self, ctx: SlashContext): + """ + Start a game of hangman. + + *Parameters* + ------------ + ctx: SlashContext + The context of the command. + """ + await self.bot.defer(ctx) + + word = "-" + while "-" in word or "." in word: + 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) + game_id = datetime.datetime.now().strftime("%Y%m%d%H%M%S") + + remaining_letters = list(string.ascii_uppercase) + + self._draw.draw_image(game_id, 0, word, guessed, []) + + send_message = f"{ctx.author.display_name} started a game of hangman." + + self.bot.log("Game started") + await ctx.send(send_message) + + boards_path = "gwendolyn/resources/games/hangman_boards/" + file_path = f"{boards_path}hangman_board{game_id}.png" + image_message = await ctx.channel.send( + file=discord.File(file_path) + ) + + blank_message_one = await ctx.channel.send("_ _") + blank_message_two = await ctx.channel.send("_ _") + + buttons = [] + for letter in remaining_letters: + custom_id = encode_id([ + "hangman", + "guess", + str(ctx.author.id), + letter, + "".join(word), + "", + game_id, + str(image_message.id), + str(blank_message_one.id), + str(blank_message_two.id) + ]) + buttons.append(create_button( + style=ButtonStyle.blue, + label=letter, + custom_id=custom_id + )) + + custom_id = encode_id([ + "hangman", + "end", + str(ctx.author.id), + game_id, + str(image_message.id), + str(blank_message_one.id), + str(blank_message_two.id) + ]) + buttons.append(create_button( + style=ButtonStyle.red, + label="End game", + custom_id=custom_id + )) + + action_row_lists = [] + for i in range(((len(buttons)-1)//20)+1): + action_rows = [] + available_buttons = buttons[(i*20):(min(len(buttons),i*20+20))] + for x in range(((len(available_buttons)-1)//4)+1): + row_buttons = available_buttons[ + (x*4):(min(len(available_buttons),x*4+4)) + ] + action_rows.append(create_actionrow(*row_buttons)) + action_row_lists.append(action_rows) + + + await blank_message_one.edit(components=action_row_lists[0]) + await blank_message_two.edit(components=action_row_lists[1]) + + + async def stop(self, ctx: ComponentContext, game_id: str, + *messages: list[str]): + """ + Stop the game of hangman. + + *Parameters* + ------------ + ctx: SlashContext + The context of the command. """ - def __init__(self, bot): - """ - Initialize the class. + for msg_id in messages: + msg = await ctx.channel.fetch_message(msg_id) + await msg.delete() - *Attributes* - ------------ - draw: DrawHangman - The DrawHangman used to draw the hangman image. - 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._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, - "minDictionaryCount": 1, - "maxDictionaryCount": -1, - "minLength": 3, - "maxLength": 11, - "limit": 1, - "api_key": api_key - } + self.bot.log("Deleting old messages") - async def start(self, ctx: SlashContext): - """ - Start a game of hangman. + await ctx.channel.send("Hangman game stopped") + boards_path = "gwendolyn/resources/games/hangman_boards/" + file_path = f"{boards_path}hangman_board{game_id}.png" + os.remove(file_path) - *Parameters* - ------------ - ctx: SlashContext - The context of the command. - """ - await self.bot.defer(ctx) + async def guess(self, ctx: ComponentContext, guess: str, word: str, + guessed_letters: str, game_id: str, *messages: list[str]): + """ + Guess a letter. - word = "-" - while "-" in word or "." in word: - response = requests.get(self._API_url, params=self._APIPARAMS) - word = list(response.json()[0]["word"].upper()) + *Parameters* + ------------ + message: discord.Message + The message that the reaction was placed on. + user: str + The id of the user. + guess: str + The guess. + """ + for msg_id in messages: + msg = await ctx.channel.fetch_message(msg_id) + await msg.delete() - self.bot.log("Found the word \""+"".join(word)+"\"") - guessed = [False] * len(word) - game_id = datetime.datetime.now().strftime("%Y%m%d%H%M%S") + misses = 0 + guessed_letters += guess + guessed = [False for i in range(len(word))] + for guessed_letter in guessed_letters: + correct_guess = 0 + for i, letter in enumerate(word): + if guessed_letter == letter: + correct_guess += 1 + guessed[i] = True - remaining_letters = list(string.ascii_uppercase) + if correct_guess == 0: + misses += 1 - self._draw.draw_image(game_id, 0, word, guessed, []) - send_message = f"{ctx.author.display_name} started a game of hangman." + remaining_letters = list(string.ascii_uppercase) + for letter in guessed_letters: + remaining_letters.remove(letter) - self.bot.log("Game started") - await ctx.send(send_message) + if correct_guess == 1: + send_message = "Guessed {}. There was 1 {} in the word." + send_message = send_message.format(guess, guess) + else: + send_message = "Guessed {}. There were {} {}s in the word." + send_message = send_message.format(guess, correct_guess, guess) - boards_path = "gwendolyn/resources/games/hangman_boards/" - file_path = f"{boards_path}hangman_board{game_id}.png" - image_message = await ctx.channel.send( - file=discord.File(file_path) - ) + self._draw.draw_image(game_id, misses, word, guessed, guessed_letters) - blank_message_one = await ctx.channel.send("_ _") - blank_message_two = await ctx.channel.send("_ _") + if misses == 6: + send_message += self.bot.long_strings["Hangman lost game"] + remaining_letters = [] + elif all(guessed): + self.bot.money.addMoney(f"#{ctx.author_id}", 15) + send_message += self.bot.long_strings["Hangman guessed word"] + remaining_letters = [] - buttons = [] - for letter in remaining_letters: - custom_id = encode_id([ - "hangman", - "guess", - str(ctx.author.id), - letter, - "".join(word), - "", - game_id, - str(image_message.id), - str(blank_message_one.id), - str(blank_message_two.id) - ]) - buttons.append(create_button( - style=ButtonStyle.blue, - label=letter, - custom_id=custom_id - )) + if remaining_letters != []: + await ctx.channel.send(send_message) + boards_path = "gwendolyn/resources/games/hangman_boards/" + file_path = f"{boards_path}hangman_board{game_id}.png" + image_message = await ctx.channel.send( + file=discord.File(file_path) + ) + blank_message_one = await ctx.channel.send("_ _") + blank_message_two = await ctx.channel.send("_ _") + buttons = [] + for letter in list(string.ascii_uppercase): custom_id = encode_id([ - "hangman", - "end", - str(ctx.author.id), - game_id, - str(image_message.id), - str(blank_message_one.id), - str(blank_message_two.id) + "hangman", + "guess", + str(ctx.author.id), + letter, + word, + guessed_letters, + game_id, + str(image_message.id), + str(blank_message_one.id), + str(blank_message_two.id) ]) buttons.append(create_button( - style=ButtonStyle.red, - label="End game", - custom_id=custom_id + style=ButtonStyle.blue, + label=letter, + custom_id=custom_id, + disabled=(letter not in remaining_letters) )) + custom_id = encode_id([ + "hangman", + "end", + str(ctx.author.id), + game_id, + str(image_message.id), + str(blank_message_one.id), + str(blank_message_two.id) + ]) + buttons.append(create_button( + style=ButtonStyle.red, + label="End game", + custom_id=custom_id + )) - action_row_lists = [] - for i in range(((len(buttons)-1)//20)+1): - action_rows = [] - available_buttons = buttons[(i*20):(min(len(buttons),i*20+20))] - for x in range(((len(available_buttons)-1)//4)+1): - row_buttons = available_buttons[ - (x*4):(min(len(available_buttons),x*4+4)) - ] - action_rows.append(create_actionrow(*row_buttons)) - action_row_lists.append(action_rows) + action_row_lists = [] + for i in range(((len(buttons)-1)//20)+1): + action_rows = [] + available_buttons = buttons[(i*20):(min(len(buttons),i*20+20))] + for x in range(((len(available_buttons)-1)//4)+1): + row_buttons = available_buttons[ + (x*4):(min(len(available_buttons),x*4+4)) + ] + action_rows.append(create_actionrow(*row_buttons)) + action_row_lists.append(action_rows) + await blank_message_one.edit(components=action_row_lists[0]) + await blank_message_two.edit(components=action_row_lists[1]) + else: + boards_path = "gwendolyn/resources/games/hangman_boards/" + file_path = f"{boards_path}hangman_board{game_id}.png" + await ctx.channel.send(send_message, file=discord.File(file_path)) - await blank_message_one.edit(components=action_row_lists[0]) - await blank_message_two.edit(components=action_row_lists[1]) - - - async def stop(self, ctx: ComponentContext, game_id: str, - *messages: list[str]): - """ - Stop the game of hangman. - - *Parameters* - ------------ - ctx: SlashContext - The context of the command. - """ - - for msg_id in messages: - msg = await ctx.channel.fetch_message(msg_id) - await msg.delete() - - self.bot.log("Deleting old messages") - - await ctx.channel.send("Hangman game stopped") - boards_path = "gwendolyn/resources/games/hangman_boards/" - file_path = f"{boards_path}hangman_board{game_id}.png" - os.remove(file_path) - - async def guess(self, ctx: ComponentContext, guess: str, word: str, - guessed_letters: str, game_id: str, *messages: list[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. - """ - for msg_id in messages: - msg = await ctx.channel.fetch_message(msg_id) - await msg.delete() - - misses = 0 - guessed_letters += guess - guessed = [False for i in range(len(word))] - for guessed_letter in guessed_letters: - correct_guess = 0 - for i, letter in enumerate(word): - if guessed_letter == letter: - correct_guess += 1 - guessed[i] = True - - if correct_guess == 0: - misses += 1 - - - remaining_letters = list(string.ascii_uppercase) - for letter in guessed_letters: - remaining_letters.remove(letter) - - if correct_guess == 1: - send_message = "Guessed {}. There was 1 {} in the word." - send_message = send_message.format(guess, guess) - else: - send_message = "Guessed {}. There were {} {}s in the word." - send_message = send_message.format(guess, correct_guess, guess) - - self._draw.draw_image(game_id, misses, word, guessed, guessed_letters) - - if misses == 6: - send_message += self.bot.long_strings["Hangman lost game"] - remaining_letters = [] - elif all(guessed): - self.bot.money.addMoney(f"#{ctx.author_id}", 15) - send_message += self.bot.long_strings["Hangman guessed word"] - remaining_letters = [] - - - if remaining_letters != []: - await ctx.channel.send(send_message) - boards_path = "gwendolyn/resources/games/hangman_boards/" - file_path = f"{boards_path}hangman_board{game_id}.png" - image_message = await ctx.channel.send( - file=discord.File(file_path) - ) - blank_message_one = await ctx.channel.send("_ _") - blank_message_two = await ctx.channel.send("_ _") - buttons = [] - for letter in list(string.ascii_uppercase): - custom_id = encode_id([ - "hangman", - "guess", - str(ctx.author.id), - letter, - word, - guessed_letters, - game_id, - str(image_message.id), - str(blank_message_one.id), - str(blank_message_two.id) - ]) - buttons.append(create_button( - style=ButtonStyle.blue, - label=letter, - custom_id=custom_id, - disabled=(letter not in remaining_letters) - )) - custom_id = encode_id([ - "hangman", - "end", - str(ctx.author.id), - game_id, - str(image_message.id), - str(blank_message_one.id), - str(blank_message_two.id) - ]) - buttons.append(create_button( - style=ButtonStyle.red, - label="End game", - custom_id=custom_id - )) - - action_row_lists = [] - for i in range(((len(buttons)-1)//20)+1): - action_rows = [] - available_buttons = buttons[(i*20):(min(len(buttons),i*20+20))] - for x in range(((len(available_buttons)-1)//4)+1): - row_buttons = available_buttons[ - (x*4):(min(len(available_buttons),x*4+4)) - ] - action_rows.append(create_actionrow(*row_buttons)) - action_row_lists.append(action_rows) - - await blank_message_one.edit(components=action_row_lists[0]) - await blank_message_two.edit(components=action_row_lists[1]) - else: - boards_path = "gwendolyn/resources/games/hangman_boards/" - file_path = f"{boards_path}hangman_board{game_id}.png" - await ctx.channel.send(send_message, file=discord.File(file_path)) - - os.remove(file_path) + os.remove(file_path) class DrawHangman(): + """ + Draws the image of the hangman game. + + *Methods* + --------- + draw_image(channel: str) + """ + + def __init__(self, bot): """ - Draws the image of the hangman game. + Initialize the class. - *Methods* - --------- - draw_image(channel: str) + *Attributes* + ------------ + CIRCLESIZE + LINEWIDTH + BODYSIZE + LIMBSIZE + ARMPOSITION + MANX, MANY + LETTERLINELENGTH + LETTERLINEDISTANCE + GALLOWX, GALLOWY + PHI + FONT + SMALLFONT """ + self._bot = bot + # pylint: disable=invalid-name + self._CIRCLESIZE = 120 + self._LINEWIDTH = 12 - def __init__(self, bot): - """ - 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 - # pylint: disable=invalid-name - self._CIRCLESIZE = 120 - self._LINEWIDTH = 12 + self._MANX = (self._LIMBSIZE*2) + self._MANY = (self._CIRCLESIZE+self._BODYSIZE+self._LIMBSIZE) + MANPADDING = self._LINEWIDTH*4 + self._MANX += MANPADDING + self._MANY += 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._PHI = 1-(1 / ((1 + 5 ** 0.5) / 2)) - self._LETTERLINELENGTH = 90 - self._LETTERLINEDISTANCE = 30 + LETTERSIZE = 75 # Wrong guesses letter size + WORDSIZE = 70 # Correct guesses letter size - self._GALLOWX, self._GALLOWY = 360, 600 - self._PHI = 1-(1 / ((1 + 5 ** 0.5) / 2)) + FONTPATH = "gwendolyn/resources/fonts/comic-sans-bold.ttf" + self._FONT = ImageFont.truetype(FONTPATH, LETTERSIZE) + self._SMALLFONT = ImageFont.truetype(FONTPATH, WORDSIZE) + # pylint: enable=invalid-name - LETTERSIZE = 75 # Wrong guesses letter size - WORDSIZE = 70 # Correct guesses letter size + 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 - FONTPATH = "gwendolyn/resources/fonts/comic-sans-bold.ttf" - self._FONT = ImageFont.truetype(FONTPATH, LETTERSIZE) - self._SMALLFONT = ImageFont.truetype(FONTPATH, WORDSIZE) - # pylint: enable=invalid-name + deviance = pre_deviance + deviance_accuracy + if deviance > maxmin: + deviance = maxmin + elif deviance < -maxmin: + deviance = -maxmin + return deviance, deviance_accuracy - 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 + 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)) - deviance = pre_deviance + deviance_accuracy - if deviance > maxmin: - deviance = maxmin - elif deviance < -maxmin: - deviance = -maxmin - return deviance, deviance_accuracy + drawer = ImageDraw.Draw(background, "RGBA") + middle = (self._CIRCLESIZE+(self._LINEWIDTH*3))/2 + deviance_x = 0 + deviance_y = 0 + deviance_accuracy_x = 0 + deviance_accuracy_y = 0 + start = random.randint(-100, -80) + degrees_amount = 360 + random.randint(-10, 30) - 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)) + for degree in range(degrees_amount): + deviance_x, deviance_accuracy_x = self._deviate( + deviance_x, + deviance_accuracy_x, + self._LINEWIDTH/100, + self._LINEWIDTH, + 0.03 + ) + deviance_y, deviance_accuracy_y = self._deviate( + deviance_y, + deviance_accuracy_y, + self._LINEWIDTH/100, + self._LINEWIDTH, + 0.03 + ) - drawer = ImageDraw.Draw(background, "RGBA") - middle = (self._CIRCLESIZE+(self._LINEWIDTH*3))/2 - deviance_x = 0 - deviance_y = 0 - deviance_accuracy_x = 0 - deviance_accuracy_y = 0 - start = random.randint(-100, -80) - degrees_amount = 360 + random.randint(-10, 30) + radians = math.radians(degree+start) + circle_x = (math.cos(radians) * (self._CIRCLESIZE/2)) + circle_y = (math.sin(radians) * (self._CIRCLESIZE/2)) - for degree in range(degrees_amount): - deviance_x, deviance_accuracy_x = self._deviate( - deviance_x, - deviance_accuracy_x, - self._LINEWIDTH/100, - self._LINEWIDTH, - 0.03 - ) - deviance_y, deviance_accuracy_y = self._deviate( - deviance_y, - deviance_accuracy_y, - self._LINEWIDTH/100, - self._LINEWIDTH, - 0.03 - ) + position_x = middle + circle_x - (self._LINEWIDTH/2) + deviance_x + position_y = middle + circle_y - (self._LINEWIDTH/2) + deviance_y - radians = math.radians(degree+start) - circle_x = (math.cos(radians) * (self._CIRCLESIZE/2)) - circle_y = (math.sin(radians) * (self._CIRCLESIZE/2)) + circle_position = [ + (position_x, position_y), + (position_x+self._LINEWIDTH, position_y+self._LINEWIDTH) + ] + drawer.ellipse(circle_position, fill=(0, 0, 0, 255)) - position_x = middle + circle_x - (self._LINEWIDTH/2) + deviance_x - position_y = middle + circle_y - (self._LINEWIDTH/2) + deviance_y + return background - circle_position = [ - (position_x, position_y), - (position_x+self._LINEWIDTH, position_y+self._LINEWIDTH) - ] - drawer.ellipse(circle_position, fill=(0, 0, 0, 255)) + def _bad_line(self, length: int, rotated: bool = False): + if rotated: + width, height = length+self._LINEWIDTH*3, self._LINEWIDTH*3 + else: + width, height = self._LINEWIDTH*3, length+self._LINEWIDTH*3 + background = Image.new("RGBA", (width, height), color=(0, 0, 0, 0)) - return background + drawer = ImageDraw.Draw(background, "RGBA") - def _bad_line(self, length: int, rotated: bool = False): - if rotated: - width, height = length+self._LINEWIDTH*3, self._LINEWIDTH*3 + 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): + deviance_x, deviance_accuracy_x = self._deviate( + deviance_x, + deviance_accuracy_x, + self._LINEWIDTH/1000, + self._LINEWIDTH, + 0.004 + ) + deviance_y, deviance_accuracy_y = self._deviate( + deviance_y, + deviance_accuracy_y, + self._LINEWIDTH/1000, + self._LINEWIDTH, + 0.004 + ) + + if rotated: + position_x = self._LINEWIDTH + pixel + deviance_x + position_y = self._LINEWIDTH + deviance_y + else: + position_x = self._LINEWIDTH + deviance_x + position_y = self._LINEWIDTH + pixel + deviance_y + + 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 _draw_man(self, misses: int, seed: str): + random.seed(seed) + man_size = (self._MANX, self._MANY) + background = Image.new("RGBA", man_size, color=(0, 0, 0, 0)) + + if misses >= 1: + 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._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)) + else: + limbs = [] + + random.seed(seed) + + for limb in limbs: + 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)) + 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: - width, height = self._LINEWIDTH*3, length+self._LINEWIDTH*3 - background = Image.new("RGBA", (width, height), color=(0, 0, 0, 0)) + x_position -= self._LIMBSIZE + compensation = min(compensation, 0) - drawer = ImageDraw.Draw(background, "RGBA") - - 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): - deviance_x, deviance_accuracy_x = self._deviate( - deviance_x, - deviance_accuracy_x, - self._LINEWIDTH/1000, - self._LINEWIDTH, - 0.004 - ) - deviance_y, deviance_accuracy_y = self._deviate( - deviance_y, - deviance_accuracy_y, - self._LINEWIDTH/1000, - self._LINEWIDTH, - 0.004 - ) - - if rotated: - position_x = self._LINEWIDTH + pixel + deviance_x - position_y = self._LINEWIDTH + deviance_y - else: - position_x = self._LINEWIDTH + deviance_x - position_y = self._LINEWIDTH + pixel + deviance_y - - 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 _draw_man(self, misses: int, seed: str): - random.seed(seed) - man_size = (self._MANX, self._MANY) - background = Image.new("RGBA", man_size, color=(0, 0, 0, 0)) - - if misses >= 1: - 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._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)) + y_position += compensation + else: + rotation = random.randint(-15, 15) + y_position = self._CIRCLESIZE+self._BODYSIZE-self._LINEWIDTH + if limb == "rl": + limb_drawing = limb_drawing.rotate(rotation-45, expand=1) else: - limbs = [] + x_position += -limb_drawing.size[0]+self._LINEWIDTH*3 + limb_drawing = limb_drawing.rotate(rotation+45, expand=1) - random.seed(seed) + paste_position = (x_position, y_position) + background.paste(limb_drawing, paste_position, limb_drawing) - for limb in limbs: - limb_drawing = self._bad_line(self._LIMBSIZE, True) - x_position = (self._MANX-(self._LINEWIDTH*3))//2 + return background - if limb[1] == "a": - rotation = random.randint(-45, 45) - shift = math.sin(math.radians(rotation)) - 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: - x_position -= self._LIMBSIZE - compensation = min(compensation, 0) + def _bad_text(self, text: str, big: bool, color: tuple = (0, 0, 0, 255)): + if big: + font = self._FONT + else: + font = self._SMALLFONT + width, height = font.getsize(text) + img = Image.new("RGBA", (width, height), color=(0, 0, 0, 0)) + drawer = ImageDraw.Draw(img, "RGBA") - y_position += compensation - else: - rotation = random.randint(-15, 15) - y_position = self._CIRCLESIZE+self._BODYSIZE-self._LINEWIDTH - if limb == "rl": - limb_drawing = limb_drawing.rotate(rotation-45, expand=1) - else: - x_position += -limb_drawing.size[0]+self._LINEWIDTH*3 - limb_drawing = limb_drawing.rotate(rotation+45, expand=1) + drawer.text((0, 0), text, font=font, fill=color) + return img - paste_position = (x_position, y_position) - background.paste(limb_drawing, paste_position, limb_drawing) + def _draw_gallows(self): + gallow_size = (self._GALLOWX, self._GALLOWY) + background = Image.new("RGBA", gallow_size, color=(0, 0, 0, 0)) - return background + 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) - def _bad_text(self, text: str, big: bool, color: tuple = (0, 0, 0, 255)): - if big: - font = self._FONT - else: - font = self._SMALLFONT - width, height = font.getsize(text) - img = Image.new("RGBA", (width, height), color=(0, 0, 0, 0)) - drawer = ImageDraw.Draw(img, "RGBA") + 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) - drawer.text((0, 0), text, font=font, fill=color) - return img + 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) - def _draw_gallows(self): - gallow_size = (self._GALLOWX, self._GALLOWY) - background = Image.new("RGBA", gallow_size, color=(0, 0, 0, 0)) + 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 - 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) + 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: + 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 + ) - 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) + return letter_lines - 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) + def _shortest_dist(self, positions: list, new_position: tuple): + shortest_dist = math.inf + for i, j in positions: + 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 - 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 _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._bad_text(guess, True) + w, h = self._FONT.getsize(guess) + x = random.randint(0, 600-w) + y = random.randint(0, 400-h) + if self._shortest_dist(pos, (x, y)) > 70: + pos.append((x, y)) + background.paste(letter, (x, y), letter) + placed = True + return background - 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: - 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 - ) + def draw_image(self, game_id: str, misses: int, word: str, + guessed: list[bool], guessed_letters: str): + """ + Draw a hangman Image. - return letter_lines + *Parameters* + ------------ + channel: str + The id of the channel the game is in. + """ + self._bot.log("Drawing hangman image") - def _shortest_dist(self, positions: list, new_position: tuple): - shortest_dist = math.inf - for i, j in positions: - 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 + random.seed(game_id) - 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._bad_text(guess, True) - w, h = self._FONT.getsize(guess) - x = random.randint(0, 600-w) - y = random.randint(0, 400-h) - if self._shortest_dist(pos, (x, y)) > 70: - pos.append((x, y)) - background.paste(letter, (x, y), letter) - placed = True - return background + background = Image.open("gwendolyn/resources/games/default_images/hangman.png") + gallow = self._draw_gallows() + man = self._draw_man(misses, game_id) - def draw_image(self, game_id: str, misses: int, word: str, - guessed: list[bool], guessed_letters: str): - """ - Draw a hangman Image. + random.seed(game_id) + letter_line_parameters = [word, guessed, misses] + letter_lines = self._draw_letter_lines(*letter_line_parameters) - *Parameters* - ------------ - channel: str - The id of the channel the game is in. - """ - self._bot.log("Drawing hangman image") + random.seed(game_id) + misses = self._draw_misses(list(guessed_letters), word) - random.seed(game_id) + background.paste(gallow, (100, 100), gallow) + background.paste(man, (300, 210), man) + background.paste(letter_lines, (120, 840), letter_lines) + background.paste(misses, (600, 150), misses) - background = Image.open("gwendolyn/resources/games/default_images/hangman.png") - gallow = self._draw_gallows() - man = self._draw_man(misses, game_id) + 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) - random.seed(game_id) - letter_line_parameters = [word, guessed, misses] - letter_lines = self._draw_letter_lines(*letter_line_parameters) - - random.seed(game_id) - misses = self._draw_misses(list(guessed_letters), word) - - background.paste(gallow, (100, 100), gallow) - background.paste(man, (300, 210), man) - background.paste(letter_lines, (120, 840), letter_lines) - background.paste(misses, (600, 150), misses) - - 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) - - boards_path = "gwendolyn/resources/games/hangman_boards/" - image_path = f"{boards_path}hangman_board{game_id}.png" - background.save(image_path) + boards_path = "gwendolyn/resources/games/hangman_boards/" + image_path = f"{boards_path}hangman_board{game_id}.png" + background.save(image_path) diff --git a/gwendolyn_old/funcs/games/hex.py b/gwendolyn_old/funcs/games/hex.py index 6c5818b..ac95a44 100644 --- a/gwendolyn_old/funcs/games/hex.py +++ b/gwendolyn_old/funcs/games/hex.py @@ -6,637 +6,637 @@ import discord from PIL import Image, ImageDraw, ImageFont class HexGame(): - def __init__(self, bot): - self.bot = bot - self.draw = DrawHex(bot) - self.BOARDWIDTH = 11 - self.ALLPOSITIONS = [(i,j) for i in range(11) for j in range(11)] - self.ALLSET = set(self.ALLPOSITIONS) - self.EMPTYDIJKSTRA = {} - for position in self.ALLPOSITIONS: - self.EMPTYDIJKSTRA[position] = math.inf # an impossibly high number - self.HEXDIRECTIONS = [(0,1),(-1,1),(-1,0),(0,-1),(1,-1),(1,0)] + def __init__(self, bot): + self.bot = bot + self.draw = DrawHex(bot) + self.BOARDWIDTH = 11 + self.ALLPOSITIONS = [(i,j) for i in range(11) for j in range(11)] + self.ALLSET = set(self.ALLPOSITIONS) + self.EMPTYDIJKSTRA = {} + for position in self.ALLPOSITIONS: + self.EMPTYDIJKSTRA[position] = math.inf # an impossibly high number + self.HEXDIRECTIONS = [(0,1),(-1,1),(-1,0),(0,-1),(1,-1),(1,0)] - async def surrender(self, ctx): - channel = str(ctx.channel_id) - game = self.bot.database["hex games"].find_one({"_id":channel}) - user = f"#{ctx.author.id}" - players = game["players"] + async def surrender(self, ctx): + channel = str(ctx.channel_id) + game = self.bot.database["hex games"].find_one({"_id":channel}) + user = f"#{ctx.author.id}" + players = game["players"] - if user not in players: - await ctx.send("You can't surrender when you're not a player.") + if user not in players: + await ctx.send("You can't surrender when you're not a player.") + else: + opponent = (players.index(user) + 1) % 2 + 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") + + with open(f"gwendolyn/resources/games/old_images/hex{channel}", "r") as f: + old_image = await ctx.channel.fetch_message(int(f.read())) + + 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") + 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"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}) + + # Swap + async def swap(self, ctx): + channel = str(ctx.channel_id) + game = self.bot.database["hex games"].find_one({"_id":channel}) + user = f"#{ctx.author.id}" + + if game is None: + await ctx.send("You can't swap nothing") + elif user not in game["players"]: + await ctx.send("You're not in the game") + elif len(game["gameHistory"]) != 1: # Only after the first move + await ctx.send("You can only swap as the second player after the very first move.") + elif user != game["players"][game["turn"]-1]: + await ctx.send("You can only swap after your opponent has placed their piece") + else: + self.bot.database["hex games"].update_one({"_id":channel}, + {"$set":{"players":game["players"][::-1]}}) # Swaps their player-number + + # Swaps the color of the hexes on the board drawing: + self.draw.drawSwap(channel) + + opponent = game["players"][::-1][game["turn"]-1] + 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())) + + 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") + 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"gwendolyn/resources/games/old_images/hex{channel}", "w") as f: + f.write(str(old_image.id)) + + if gwendolyn_turn: + await self.hexAI(ctx) + + # Starts the game + async def start(self, ctx, opponent): + await self.bot.defer(ctx) + user = f"#{ctx.author.id}" + channel = str(ctx.channel_id) + game = self.bot.database["hex games"].find_one({"_id":channel}) + + started_game = False + can_start = True + + if game != None: + send_message = "There's already a hex game going on in this channel" + log_message = "There was already a game going on" + can_start = False + else: + if type(opponent) == int: + # Opponent is Gwendolyn + if opponent in range(1, 6): + opponent_name = "Gwendolyn" + difficulty = int(opponent) + difficulty_text = f" with difficulty {difficulty}" + opponent = f"#{self.bot.user.id}" else: - opponent = (players.index(user) + 1) % 2 - 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") + send_message = "Difficulty doesn't exist" + log_message = "They tried to play against a difficulty that doesn't exist" + can_start = False - with open(f"gwendolyn/resources/games/old_images/hex{channel}", "r") as f: - old_image = await ctx.channel.fetch_message(int(f.read())) - - 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") - 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"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}) - - # Swap - async def swap(self, ctx): - channel = str(ctx.channel_id) - game = self.bot.database["hex games"].find_one({"_id":channel}) - user = f"#{ctx.author.id}" - - if game is None: - await ctx.send("You can't swap nothing") - elif user not in game["players"]: - await ctx.send("You're not in the game") - elif len(game["gameHistory"]) != 1: # Only after the first move - await ctx.send("You can only swap as the second player after the very first move.") - elif user != game["players"][game["turn"]-1]: - await ctx.send("You can only swap after your opponent has placed their piece") - else: - self.bot.database["hex games"].update_one({"_id":channel}, - {"$set":{"players":game["players"][::-1]}}) # Swaps their player-number - - # Swaps the color of the hexes on the board drawing: - self.draw.drawSwap(channel) - - opponent = game["players"][::-1][game["turn"]-1] - 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())) - - 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") - 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"gwendolyn/resources/games/old_images/hex{channel}", "w") as f: - f.write(str(old_image.id)) - - if gwendolyn_turn: - await self.hexAI(ctx) - - # Starts the game - async def start(self, ctx, opponent): - await self.bot.defer(ctx) - user = f"#{ctx.author.id}" - channel = str(ctx.channel_id) - game = self.bot.database["hex games"].find_one({"_id":channel}) - - started_game = False - can_start = True - - if game != None: - send_message = "There's already a hex game going on in this channel" - log_message = "There was already a game going on" + elif type(opponent) == discord.member.Member: + if opponent.bot: + # User has challenged a bot + if opponent == self.bot.user: + # It was Gwendolyn + opponent_name = "Gwendolyn" + difficulty = 2 + difficulty_text = f" with difficulty {difficulty}" + opponent = f"#{self.bot.user.id}" + else: + send_message = "You can't challenge a bot!" + log_message = "They tried to challenge a bot" can_start = False else: - if type(opponent) == int: - # Opponent is Gwendolyn - if opponent in range(1, 6): - opponent_name = "Gwendolyn" - difficulty = int(opponent) - difficulty_text = f" with difficulty {difficulty}" - opponent = f"#{self.bot.user.id}" - else: - send_message = "Difficulty doesn't exist" - log_message = "They tried to play against a difficulty that doesn't exist" - can_start = False + # Opponent is another player + if ctx.author != opponent: + opponent_name = opponent.display_name + opponent = f"#{opponent.id}" + difficulty = 5 + difficulty_text = "" + else: + send_message = "You can't play against yourself" + log_message = "They tried to play against themself" + can_start = False + else: + can_start = False + log_message = f"Opponent was neither int or member. It was {type(opponent)}" + send_message = "Something went wrong" - elif type(opponent) == discord.member.Member: - if opponent.bot: - # User has challenged a bot - if opponent == self.bot.user: - # It was Gwendolyn - opponent_name = "Gwendolyn" - difficulty = 2 - difficulty_text = f" with difficulty {difficulty}" - opponent = f"#{self.bot.user.id}" - else: - send_message = "You can't challenge a bot!" - log_message = "They tried to challenge a bot" - can_start = False - else: - # Opponent is another player - if ctx.author != opponent: - opponent_name = opponent.display_name - opponent = f"#{opponent.id}" - difficulty = 5 - difficulty_text = "" - else: - send_message = "You can't play against yourself" - log_message = "They tried to play against themself" - can_start = False - else: - can_start = False - log_message = f"Opponent was neither int or member. It was {type(opponent)}" - send_message = "Something went wrong" + 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 = [] - 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 = [] + new_game = {"_id":channel,"board":board, "winner":0, + "players":players, "turn":1, "difficulty":difficulty, "gameHistory":gameHistory} - new_game = {"_id":channel,"board":board, "winner":0, - "players":players, "turn":1, "difficulty":difficulty, "gameHistory":gameHistory} + self.bot.database["hex games"].insert_one(new_game) - self.bot.database["hex games"].insert_one(new_game) + # draw the board + self.draw.drawBoard(channel) - # draw the board - self.draw.drawBoard(channel) + gwendolyn_turn = (players[0] == f"#{self.bot.user.id}") + started_game = True - gwendolyn_turn = (players[0] == f"#{self.bot.user.id}") - started_game = True + 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" - 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(send_message) + self.bot.log(log_message) - await ctx.send(send_message) - self.bot.log(log_message) + if started_game: + file_path = f"gwendolyn/resources/games/hex_boards/board{ctx.channel_id}.png" + new_image = await ctx.channel.send(file = discord.File(file_path)) - if started_game: - file_path = f"gwendolyn/resources/games/hex_boards/board{ctx.channel_id}.png" - 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(new_image.id)) - with open(f"gwendolyn/resources/games/old_images/hex{ctx.channel_id}", "w") as f: - f.write(str(new_image.id)) + if gwendolyn_turn: + await self.hexAI(ctx) - 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}) + placed_piece = False - # 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}) - placed_piece = False - - if game == None: - 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]): - 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: - 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: - send_message = "It's not your turn" - self.bot.log("It wasn't their turn") - else: - player = game["turn"] - turn = game["turn"] - board = game["board"] - - self.bot.log("Placing a piece on the board with placeHex()") - # Places on board - board = self.placeOnHexBoard(board,player,position) - - if board is None: - self.bot.log("It was an invalid position") - 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}}) - turn = (turn % 2) + 1 - self.bot.database["hex games"].update_one({"_id":channel},{"$set":{"turn":turn}}) - - # Checking for a win - self.bot.log("Checking for win") - winner = self.evaluateBoard(game["board"])[1] - - if winner == 0: # Continue with the game. - 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! - game_won = True - self.bot.database["hex games"].update_one({"_id":channel},{"$set":{"winner":winner}}) - 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}": - 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? - gwendolyn_turn = False - if game["players"][turn-1] == f"#{self.bot.user.id}": - self.bot.log("It's Gwendolyn's turn") - gwendolyn_turn = True - - placed_piece = True - - if user == f"#{self.bot.user.id}": - await ctx.channel.send(send_message) - else: - await ctx.send(send_message) - - if placed_piece: - # Update the board - self.draw.drawHexPlacement(channel,player, position) - - with open(f"gwendolyn/resources/games/old_images/hex{channel}", "r") as f: - old_image = await ctx.channel.fetch_message(int(f.read())) - - 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") - file_path = f"gwendolyn/resources/games/hex_boards/board{channel}.png" - old_image = await ctx.channel.send(file = discord.File(file_path)) - - if game_won: - self.bot.log("Dealing with the winning player") - game = self.bot.database["hex games"].find_one({"_id":channel}) - - winner = game["winner"] - if game["players"][winner-1] != f"#{self.bot.user.id}": - winnings = game["difficulty"]*10 - self.bot.money.addMoney(game["players"][winner-1].lower(),winnings) - - self.bot.database["hex games"].delete_one({"_id":channel}) - else: - with open(f"gwendolyn/resources/games/old_images/hex{channel}", "w") as f: - f.write(str(old_image.id)) - - if gwendolyn_turn: - await self.hexAI(ctx) - - # Returns a board where the placement has ocurred - def placeOnHexBoard(self, board,player,position): - # Translates the position - position = position.lower() - # Error handling - column = ord(position[0]) - 97 # ord() translates from letter to number - row = int(position[1:]) - 1 - if column not in range(self.BOARDWIDTH) or row not in range(self.BOARDWIDTH): - self.bot.log("Position out of bounds") - return None - # Place at the position - if board[row][column] == 0: - board[row][column] = player - return board - else: - self.bot.log("Cannot place on existing piece") - return None - - # After your move, you have the option to undo get your turn back #TimeTravel - async def undo(self, ctx): - channel = str(ctx.channel_id) - user = f"#{ctx.author.id}" - undid = False - - game = self.bot.database["hex games"].find_one({"_id":channel}) - - if user not in game["players"]: - send_message = "You're not a player in the game" - elif len(game["gameHistory"]) == 0: - send_message = "You can't undo nothing" - elif user != game["players"][(game["turn"] % 2)]: # If 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))) - - lastMove = game["gameHistory"].pop() - game["board"][lastMove[0]][lastMove[1]] = 0 - self.bot.database["hex games"].update_one({"_id":channel}, - {"$set":{"board":game["board"]}}) - self.bot.database["hex games"].update_one({"_id":channel}, - {"$set":{"turn":turn%2 + 1}}) - - # Update the board - self.draw.drawHexPlacement(channel,0,"abcdefghijk"[lastMove[1]]+str(lastMove[0]+1)) # The zero makes the hex disappear - send_message = f"You undid your last move at {lastMove}" - undid = True - - 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())) - - 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") - 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"gwendolyn/resources/games/old_images/hex{channel}", "w") as f: - f.write(str(old_image.id)) - - - # Plays as the AI - async def hexAI(self, ctx): - channel = str(ctx.channel_id) - self.bot.log("Figuring out best move") - game = self.bot.database["hex games"].find_one({"_id":channel}) + if game == None: + 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]): + 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: + 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: + send_message = "It's not your turn" + self.bot.log("It wasn't their turn") + else: + player = game["turn"] + turn = game["turn"] board = game["board"] - if len(game["gameHistory"]): - lastMove = game["gameHistory"][-1] + self.bot.log("Placing a piece on the board with placeHex()") + # Places on board + board = self.placeOnHexBoard(board,player,position) + + if board is None: + self.bot.log("It was an invalid position") + send_message = ("That's an invalid position. You must place your piece on an empty field.") else: - lastMove = (5,5) + # If the move is valid: + self.bot.database["hex games"].update_one({"_id":channel},{"$set":{"board":board}}) + turn = (turn % 2) + 1 + self.bot.database["hex games"].update_one({"_id":channel},{"$set":{"turn":turn}}) - # These moves are the last move +- 2. - moves = [[(lastMove[0]+j-2,lastMove[1]+i-2) for i in range(5) if lastMove[1]+i-2 in range(11)] for j in range(5) if lastMove[0]+j-2 in range(11)] - moves = sum(moves,[]) - movesCopy = moves.copy() - for move in movesCopy: - if board[move[0]][move[1]] != 0: - moves.remove(move) - chosenMove = random.choice(moves) + # Checking for a win + self.bot.log("Checking for win") + winner = self.evaluateBoard(game["board"])[1] - placement = "abcdefghijk"[chosenMove[1]]+str(chosenMove[0]+1) - self.bot.log(f"ChosenMove is {chosenMove} at {placement}") + if winner == 0: # Continue with the game. + 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) - await self.placeHex(ctx, placement, f"#{self.bot.user.id}") + else: # Congratulations! + game_won = True + self.bot.database["hex games"].update_one({"_id":channel},{"$set":{"winner":winner}}) + 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}": + 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? + gwendolyn_turn = False + if game["players"][turn-1] == f"#{self.bot.user.id}": + self.bot.log("It's Gwendolyn's turn") + gwendolyn_turn = True + + placed_piece = True + + if user == f"#{self.bot.user.id}": + await ctx.channel.send(send_message) + else: + await ctx.send(send_message) + + if placed_piece: + # Update the board + self.draw.drawHexPlacement(channel,player, position) + + with open(f"gwendolyn/resources/games/old_images/hex{channel}", "r") as f: + old_image = await ctx.channel.fetch_message(int(f.read())) + + 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") + file_path = f"gwendolyn/resources/games/hex_boards/board{channel}.png" + old_image = await ctx.channel.send(file = discord.File(file_path)) + + if game_won: + self.bot.log("Dealing with the winning player") + game = self.bot.database["hex games"].find_one({"_id":channel}) + + winner = game["winner"] + if game["players"][winner-1] != f"#{self.bot.user.id}": + winnings = game["difficulty"]*10 + self.bot.money.addMoney(game["players"][winner-1].lower(),winnings) + + self.bot.database["hex games"].delete_one({"_id":channel}) + else: + with open(f"gwendolyn/resources/games/old_images/hex{channel}", "w") as f: + f.write(str(old_image.id)) + + if gwendolyn_turn: + await self.hexAI(ctx) + + # Returns a board where the placement has ocurred + def placeOnHexBoard(self, board,player,position): + # Translates the position + position = position.lower() + # Error handling + column = ord(position[0]) - 97 # ord() translates from letter to number + row = int(position[1:]) - 1 + if column not in range(self.BOARDWIDTH) or row not in range(self.BOARDWIDTH): + self.bot.log("Position out of bounds") + return None + # Place at the position + if board[row][column] == 0: + board[row][column] = player + return board + else: + self.bot.log("Cannot place on existing piece") + return None + + # After your move, you have the option to undo get your turn back #TimeTravel + async def undo(self, ctx): + channel = str(ctx.channel_id) + user = f"#{ctx.author.id}" + undid = False + + game = self.bot.database["hex games"].find_one({"_id":channel}) + + if user not in game["players"]: + send_message = "You're not a player in the game" + elif len(game["gameHistory"]) == 0: + send_message = "You can't undo nothing" + elif user != game["players"][(game["turn"] % 2)]: # If 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))) + + lastMove = game["gameHistory"].pop() + game["board"][lastMove[0]][lastMove[1]] = 0 + self.bot.database["hex games"].update_one({"_id":channel}, + {"$set":{"board":game["board"]}}) + self.bot.database["hex games"].update_one({"_id":channel}, + {"$set":{"turn":turn%2 + 1}}) + + # Update the board + self.draw.drawHexPlacement(channel,0,"abcdefghijk"[lastMove[1]]+str(lastMove[0]+1)) # The zero makes the hex disappear + send_message = f"You undid your last move at {lastMove}" + undid = True + + 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())) + + 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") + 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"gwendolyn/resources/games/old_images/hex{channel}", "w") as f: + f.write(str(old_image.id)) - def evaluateBoard(self, board): - scores = {1:0, 2:0} - winner = 0 - # Here, I use Dijkstra's algorithm to evaluate the board, as proposed by this article: https://towardsdatascience.com/hex-creating-intelligent-adversaries-part-2-heuristics-dijkstras-algorithm-597e4dcacf93 - for player in [1,2]: - Distance = copy.deepcopy(self.EMPTYDIJKSTRA) - # Initialize the starting hexes. For the blue player, this is the leftmost column. For the red player, this is the tom row. - for start in (self.ALLPOSITIONS[::11] if player == 2 else self.ALLPOSITIONS[:11]): - # An empty hex adds a of distance of 1. A hex of own color add distance 0. Opposite color adds infinite distance. - Distance[start] = 1 if (board[start[0]][start[1]] == 0) else 0 if (board[start[0]][start[1]] == player) else math.inf - visited = set() # Also called sptSet, short for "shortest path tree Set" - for _ in range(self.BOARDWIDTH**2): # We can at most check every 121 hexes - # Find the next un-visited hex, that has the lowest distance - remainingHexes = self.ALLSET.difference(visited) - A = [Distance[k] for k in remainingHexes] # Find the distance to each un-visited hex - u = list(remainingHexes)[A.index(min(A))] # Chooses the one with the lowest distance + # Plays as the AI + async def hexAI(self, ctx): + channel = str(ctx.channel_id) + self.bot.log("Figuring out best move") + game = self.bot.database["hex games"].find_one({"_id":channel}) + board = game["board"] - # Find neighbors of the hex u - for di in self.HEXDIRECTIONS: - v = (u[0] + di[0] , u[1] + di[1]) # v is a neighbor of u - if v[0] in range(11) and v[1] in range(11) and v not in visited: - new_dist = Distance[u] + (1 if (board[v[0]][v[1]] == 0) else 0 if (board[v[0]][v[1]] == player) else math.inf) - Distance[v] = min(Distance[v], new_dist) - # After a hex has been visited, this is noted - visited.add(u) - #self.bot.log("Distance from player {}'s start to {} is {}".format(player,u,Distance[u])) - if u[player-1] == 10: # if the right coordinate of v is 10, it means we're at the goal - scores[player] = Distance[u] # A player's score is the shortest distance to goal. Which equals the number of remaining moves they need to win if unblocked by the opponent. - break - else: - self.bot.log("For some reason, no path to the goal was found. ") - if scores[player] == 0: - winner = player - break # We don't need to check the other player's score, if player1 won. - return scores[2]-scores[1], winner + if len(game["gameHistory"]): + lastMove = game["gameHistory"][-1] + else: + lastMove = (5,5) + + # These moves are the last move +- 2. + moves = [[(lastMove[0]+j-2,lastMove[1]+i-2) for i in range(5) if lastMove[1]+i-2 in range(11)] for j in range(5) if lastMove[0]+j-2 in range(11)] + moves = sum(moves,[]) + movesCopy = moves.copy() + for move in movesCopy: + if board[move[0]][move[1]] != 0: + moves.remove(move) + chosenMove = random.choice(moves) + + placement = "abcdefghijk"[chosenMove[1]]+str(chosenMove[0]+1) + self.bot.log(f"ChosenMove is {chosenMove} at {placement}") + + await self.placeHex(ctx, placement, f"#{self.bot.user.id}") - 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 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: - 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: - #self.bot.log("Just pruned something!") - break - return maxEval - else: # blue player predicts next move - minEval = math.inf - 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: - 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: - #self.bot.log("Just pruned something!") - break - return minEval + def evaluateBoard(self, board): + scores = {1:0, 2:0} + winner = 0 + # Here, I use Dijkstra's algorithm to evaluate the board, as proposed by this article: https://towardsdatascience.com/hex-creating-intelligent-adversaries-part-2-heuristics-dijkstras-algorithm-597e4dcacf93 + for player in [1,2]: + Distance = copy.deepcopy(self.EMPTYDIJKSTRA) + # Initialize the starting hexes. For the blue player, this is the leftmost column. For the red player, this is the tom row. + for start in (self.ALLPOSITIONS[::11] if player == 2 else self.ALLPOSITIONS[:11]): + # An empty hex adds a of distance of 1. A hex of own color add distance 0. Opposite color adds infinite distance. + Distance[start] = 1 if (board[start[0]][start[1]] == 0) else 0 if (board[start[0]][start[1]] == player) else math.inf + visited = set() # Also called sptSet, short for "shortest path tree Set" + for _ in range(self.BOARDWIDTH**2): # We can at most check every 121 hexes + # Find the next un-visited hex, that has the lowest distance + remainingHexes = self.ALLSET.difference(visited) + A = [Distance[k] for k in remainingHexes] # Find the distance to each un-visited hex + u = list(remainingHexes)[A.index(min(A))] # Chooses the one with the lowest distance + + # Find neighbors of the hex u + for di in self.HEXDIRECTIONS: + v = (u[0] + di[0] , u[1] + di[1]) # v is a neighbor of u + if v[0] in range(11) and v[1] in range(11) and v not in visited: + new_dist = Distance[u] + (1 if (board[v[0]][v[1]] == 0) else 0 if (board[v[0]][v[1]] == player) else math.inf) + Distance[v] = min(Distance[v], new_dist) + # After a hex has been visited, this is noted + visited.add(u) + #self.bot.log("Distance from player {}'s start to {} is {}".format(player,u,Distance[u])) + if u[player-1] == 10: # if the right coordinate of v is 10, it means we're at the goal + scores[player] = Distance[u] # A player's score is the shortest distance to goal. Which equals the number of remaining moves they need to win if unblocked by the opponent. + break + else: + self.bot.log("For some reason, no path to the goal was found. ") + if scores[player] == 0: + winner = player + break # We don't need to check the other player's score, if player1 won. + return scores[2]-scores[1], winner + + + 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 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: + 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: + #self.bot.log("Just pruned something!") + break + return maxEval + else: # blue player predicts next move + minEval = math.inf + 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: + 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: + #self.bot.log("Just pruned something!") + break + return minEval class DrawHex(): - def __init__(self,bot): - self.bot = bot - # Defining all the variables - self.CANVASWIDTH = 2400 - self.CANVASHEIGHT = 1800 - self.SIDELENGTH = 75 + def __init__(self,bot): + self.bot = bot + # Defining all the variables + self.CANVASWIDTH = 2400 + self.CANVASHEIGHT = 1800 + self.SIDELENGTH = 75 - # The offsets centers the board in the picture - self.XOFFSET = self.CANVASWIDTH/2 - 8*math.sqrt(3)*self.SIDELENGTH + # The offsets centers the board in the picture + self.XOFFSET = self.CANVASWIDTH/2 - 8*math.sqrt(3)*self.SIDELENGTH - # The offsets are the coordinates of the upperleft point in the - # upperleftmost hexagon - self.YOFFSET = self.CANVASHEIGHT/2 - 8*self.SIDELENGTH + # The offsets are the coordinates of the upperleft point in the + # upperleftmost hexagon + self.YOFFSET = self.CANVASHEIGHT/2 - 8*self.SIDELENGTH - # The whole width of one hexagon - self.HEXAGONWIDTH = math.sqrt(3) * self.SIDELENGTH + # The whole width of one hexagon + self.HEXAGONWIDTH = math.sqrt(3) * self.SIDELENGTH - # The height difference between two layers - self.HEXAGONHEIGHT = 1.5 * self.SIDELENGTH - self.FONTSIZE = 45 - self.TEXTCOLOR = (0,0,0) - self.FONT = ImageFont.truetype('gwendolyn/resources/fonts/futura-bold.ttf', self.FONTSIZE) + # The height difference between two layers + self.HEXAGONHEIGHT = 1.5 * self.SIDELENGTH + self.FONTSIZE = 45 + self.TEXTCOLOR = (0,0,0) + 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 - self.XTHICKNESS = self.HEXTHICKNESS * math.cos(math.pi/6) - self.YTHICKNESS = self.HEXTHICKNESS * math.sin(math.pi/6) - self.BACKGROUNDCOLOR = (230,230,230) - self.BETWEENCOLOR = (231,231,231) - self.BLANKCOLOR = "lightgrey" - self.PIECECOLOR = {1:(237,41,57),2:(0,165,255),0:self.BLANKCOLOR} # player1 is red, player2 is blue - self.BOARDCOORDINATES = [ [(self.XOFFSET + self.HEXAGONWIDTH*(column + row/2),self.YOFFSET + self.HEXAGONHEIGHT*row) for column in range(11)] for row in range(11)] # These are the coordinates for the upperleft corner of every hex - self.COLHEXTHICKNESS = 4 # When placing a hex, it is a little bigger than the underlying hex in the background (4 < 6) - self.COLXTHICKNESS = self.COLHEXTHICKNESS * math.cos(math.pi/6) - self.COLYTHICKNESS = self.COLHEXTHICKNESS * math.sin(math.pi/6) - # The Name display things: - self.NAMESIZE = 60 - 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 - self.SMALLWIDTH = self.HEXAGONWIDTH * 0.6 - self.SMALLSIDELENGTH = self.SIDELENGTH * 0.6 + self.LINETHICKNESS = 15 + self.HEXTHICKNESS = 6 # This is half the width of the background lining between every hex + self.XTHICKNESS = self.HEXTHICKNESS * math.cos(math.pi/6) + self.YTHICKNESS = self.HEXTHICKNESS * math.sin(math.pi/6) + self.BACKGROUNDCOLOR = (230,230,230) + self.BETWEENCOLOR = (231,231,231) + self.BLANKCOLOR = "lightgrey" + self.PIECECOLOR = {1:(237,41,57),2:(0,165,255),0:self.BLANKCOLOR} # player1 is red, player2 is blue + self.BOARDCOORDINATES = [ [(self.XOFFSET + self.HEXAGONWIDTH*(column + row/2),self.YOFFSET + self.HEXAGONHEIGHT*row) for column in range(11)] for row in range(11)] # These are the coordinates for the upperleft corner of every hex + self.COLHEXTHICKNESS = 4 # When placing a hex, it is a little bigger than the underlying hex in the background (4 < 6) + self.COLXTHICKNESS = self.COLHEXTHICKNESS * math.cos(math.pi/6) + self.COLYTHICKNESS = self.COLHEXTHICKNESS * math.sin(math.pi/6) + # The Name display things: + self.NAMESIZE = 60 + 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 + self.SMALLWIDTH = self.HEXAGONWIDTH * 0.6 + self.SMALLSIDELENGTH = self.SIDELENGTH * 0.6 - def drawBoard(self, channel): - self.bot.log("Drawing empty Hex board") + def drawBoard(self, channel): + self.bot.log("Drawing empty Hex board") - # Creates the empty image - im = Image.new('RGB', size=(self.CANVASWIDTH, self.CANVASHEIGHT),color = self.BACKGROUNDCOLOR) - # 'd' is a shortcut to drawing on the image + # Creates the empty image + im = Image.new('RGB', size=(self.CANVASWIDTH, self.CANVASHEIGHT),color = self.BACKGROUNDCOLOR) + # 'd' is a shortcut to drawing on the image + d = ImageDraw.Draw(im,"RGBA") + + # Drawing all the hexagons + for column in self.BOARDCOORDINATES: + for startingPoint in column: + x = startingPoint[0] + y = startingPoint[1] + d.polygon([ + (x, y), + (x+self.HEXAGONWIDTH/2, y-0.5*self.SIDELENGTH), + (x+self.HEXAGONWIDTH, y), + (x+self.HEXAGONWIDTH, y+self.SIDELENGTH), + (x+self.HEXAGONWIDTH/2, y+1.5*self.SIDELENGTH), + (x, y+self.SIDELENGTH), + ],fill = self.BETWEENCOLOR) + d.polygon([ + (x+self.XTHICKNESS, y + self.YTHICKNESS), + (x+self.HEXAGONWIDTH/2, y-0.5*self.SIDELENGTH + self.HEXTHICKNESS), + (x+self.HEXAGONWIDTH-self.XTHICKNESS, y + self.YTHICKNESS), + (x+self.HEXAGONWIDTH-self.XTHICKNESS, y+self.SIDELENGTH - self.YTHICKNESS), + (x+self.HEXAGONWIDTH/2, y+1.5*self.SIDELENGTH - self.HEXTHICKNESS), + (x+self.XTHICKNESS, y+self.SIDELENGTH - self.YTHICKNESS), + ],fill = self.BLANKCOLOR) + + # Draw color on the outside of the board + # Top line, red + d.line(sum((sum([(point[0],point[1],point[0]+self.HEXAGONWIDTH/2,point[1]-self.HEXAGONHEIGHT+self.SIDELENGTH) for point in self.BOARDCOORDINATES[0]],()),(self.BOARDCOORDINATES[0][10][0]+self.HEXAGONWIDTH*3/4,self.BOARDCOORDINATES[0][10][1]-self.SIDELENGTH/4)),()), + fill = self.PIECECOLOR[1],width = self.LINETHICKNESS) + # Bottom line, red + d.line(sum(((self.BOARDCOORDINATES[10][0][0]+self.HEXAGONWIDTH/4,self.BOARDCOORDINATES[10][0][1]+self.SIDELENGTH*5/4),sum([(point[0]+self.HEXAGONWIDTH/2,point[1]+self.HEXAGONHEIGHT,point[0]+self.HEXAGONWIDTH,point[1]+self.SIDELENGTH) for point in self.BOARDCOORDINATES[10]],())),()), + fill = self.PIECECOLOR[1],width = self.LINETHICKNESS) + # Left line, blue + d.line(sum((sum([(row[0][0],row[0][1],row[0][0],row[0][1]+self.SIDELENGTH) for row in self.BOARDCOORDINATES],()),(self.BOARDCOORDINATES[10][0][0]+self.HEXAGONWIDTH/4,self.BOARDCOORDINATES[10][0][1]+self.SIDELENGTH*5/4)),()), + fill = self.PIECECOLOR[2],width = self.LINETHICKNESS) + # Right line, blue + d.line(sum(((self.BOARDCOORDINATES[0][10][0]+self.HEXAGONWIDTH*3/4,self.BOARDCOORDINATES[0][10][1]-self.SIDELENGTH/4),sum([(row[10][0]+self.HEXAGONWIDTH,row[10][1],row[10][0]+self.HEXAGONWIDTH,row[10][1]+self.SIDELENGTH) for row in self.BOARDCOORDINATES],())),()), + fill = self.PIECECOLOR[2],width = self.LINETHICKNESS) + + # Writes "abc..", "123.." on the columns and rows + for i in range(11): + # Top letters + d.text( (self.XOFFSET + self.HEXAGONWIDTH*i, self.YOFFSET-66) , "ABCDEFGHIJK"[i], font=self.FONT, fill=self.TEXTCOLOR) + # Bottom letters + d.text( (self.XOFFSET + self.HEXAGONWIDTH*(i+11.5/2), self.YOFFSET - 15 + 11*self.HEXAGONHEIGHT) , "ABCDEFGHIJK"[i], font=self.FONT, fill=self.TEXTCOLOR) + # Left numbers + d.multiline_text( (self.XOFFSET + self.HEXAGONWIDTH*i/2 - 72 -4*(i>8), self.YOFFSET + 18 + i*self.HEXAGONHEIGHT) , str(i+1), font=self.FONT, fill=self.TEXTCOLOR, align="right") + # Right numbers + d.text( (self.XOFFSET + self.HEXAGONWIDTH*(i/2+11) + 30 , self.YOFFSET + 6 + i*self.HEXAGONHEIGHT) , str(i+1), font=self.FONT, fill=self.TEXTCOLOR) + + # Write player names and color + game = self.bot.database["hex games"].find_one({"_id":channel}) + + for p in [1,2]: + 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 + y = self.YNAME[p] + d.text((x,y),playername, font=self.NAMEFONT, fill = self.TEXTCOLOR) + # Draw a half-size Hexagon to indicate the player's color + x -= self.NAMEHEXPADDING # To the left of both names + d.polygon([ + (x, y), + (x+self.SMALLWIDTH/2, y-self.SMALLSIDELENGTH/2), + (x+self.SMALLWIDTH, y), + (x+self.SMALLWIDTH, y+self.SMALLSIDELENGTH), + (x+self.SMALLWIDTH/2, y+self.SMALLSIDELENGTH*3/2), + (x, y+self.SMALLSIDELENGTH), + ],fill = self.PIECECOLOR[p]) + + im.save("gwendolyn/resources/games/hex_boards/board"+channel+".png") + + + + + def drawHexPlacement(self, channel,player,position): + FILEPATH = "gwendolyn/resources/games/hex_boards/board"+channel+".png" + self.bot.log(f"Drawing a newly placed hex. Filename: board{channel}.png") + + # Translates position + # We don't need to error-check, because the position is already checked in placeOnHexBoard() + position = position.lower() + column = ord(position[0])-97 # ord() translates from letter to number + row = int(position[1:])-1 + + # Find the coordinates for the filled hex drawing + 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), + (self.BOARDCOORDINATES[row][column][0]+self.HEXAGONWIDTH-self.COLXTHICKNESS, self.BOARDCOORDINATES[row][column][1]+self.SIDELENGTH - self.COLYTHICKNESS), + (self.BOARDCOORDINATES[row][column][0]+self.HEXAGONWIDTH/2, self.BOARDCOORDINATES[row][column][1]+1.5*self.SIDELENGTH - self.COLHEXTHICKNESS), + (self.BOARDCOORDINATES[row][column][0]+self.COLXTHICKNESS, self.BOARDCOORDINATES[row][column][1]+self.SIDELENGTH - self.COLYTHICKNESS), + ] + + # Opens the image + try: + with Image.open(FILEPATH) as im: + d = ImageDraw.Draw(im,"RGBA") + # Draws the hex piece + d.polygon(hex_coords,fill = self.PIECECOLOR[player], outline = self.BETWEENCOLOR) + + # Save + im.save(FILEPATH) + except: + self.bot.log("Error drawing new hex on board") + + def drawSwap(self, channel): + FILEPATH = "gwendolyn/resources/games/hex_boards/board"+channel+".png" + game = self.bot.database["hex games"].find_one({"_id":channel}) + # Opens the image + try: + with Image.open(FILEPATH) as im: d = ImageDraw.Draw(im,"RGBA") - # Drawing all the hexagons - for column in self.BOARDCOORDINATES: - for startingPoint in column: - x = startingPoint[0] - y = startingPoint[1] - d.polygon([ - (x, y), - (x+self.HEXAGONWIDTH/2, y-0.5*self.SIDELENGTH), - (x+self.HEXAGONWIDTH, y), - (x+self.HEXAGONWIDTH, y+self.SIDELENGTH), - (x+self.HEXAGONWIDTH/2, y+1.5*self.SIDELENGTH), - (x, y+self.SIDELENGTH), - ],fill = self.BETWEENCOLOR) - d.polygon([ - (x+self.XTHICKNESS, y + self.YTHICKNESS), - (x+self.HEXAGONWIDTH/2, y-0.5*self.SIDELENGTH + self.HEXTHICKNESS), - (x+self.HEXAGONWIDTH-self.XTHICKNESS, y + self.YTHICKNESS), - (x+self.HEXAGONWIDTH-self.XTHICKNESS, y+self.SIDELENGTH - self.YTHICKNESS), - (x+self.HEXAGONWIDTH/2, y+1.5*self.SIDELENGTH - self.HEXTHICKNESS), - (x+self.XTHICKNESS, y+self.SIDELENGTH - self.YTHICKNESS), - ],fill = self.BLANKCOLOR) - - # Draw color on the outside of the board - # Top line, red - d.line(sum((sum([(point[0],point[1],point[0]+self.HEXAGONWIDTH/2,point[1]-self.HEXAGONHEIGHT+self.SIDELENGTH) for point in self.BOARDCOORDINATES[0]],()),(self.BOARDCOORDINATES[0][10][0]+self.HEXAGONWIDTH*3/4,self.BOARDCOORDINATES[0][10][1]-self.SIDELENGTH/4)),()), - fill = self.PIECECOLOR[1],width = self.LINETHICKNESS) - # Bottom line, red - d.line(sum(((self.BOARDCOORDINATES[10][0][0]+self.HEXAGONWIDTH/4,self.BOARDCOORDINATES[10][0][1]+self.SIDELENGTH*5/4),sum([(point[0]+self.HEXAGONWIDTH/2,point[1]+self.HEXAGONHEIGHT,point[0]+self.HEXAGONWIDTH,point[1]+self.SIDELENGTH) for point in self.BOARDCOORDINATES[10]],())),()), - fill = self.PIECECOLOR[1],width = self.LINETHICKNESS) - # Left line, blue - d.line(sum((sum([(row[0][0],row[0][1],row[0][0],row[0][1]+self.SIDELENGTH) for row in self.BOARDCOORDINATES],()),(self.BOARDCOORDINATES[10][0][0]+self.HEXAGONWIDTH/4,self.BOARDCOORDINATES[10][0][1]+self.SIDELENGTH*5/4)),()), - fill = self.PIECECOLOR[2],width = self.LINETHICKNESS) - # Right line, blue - d.line(sum(((self.BOARDCOORDINATES[0][10][0]+self.HEXAGONWIDTH*3/4,self.BOARDCOORDINATES[0][10][1]-self.SIDELENGTH/4),sum([(row[10][0]+self.HEXAGONWIDTH,row[10][1],row[10][0]+self.HEXAGONWIDTH,row[10][1]+self.SIDELENGTH) for row in self.BOARDCOORDINATES],())),()), - fill = self.PIECECOLOR[2],width = self.LINETHICKNESS) - - # Writes "abc..", "123.." on the columns and rows - for i in range(11): - # Top letters - d.text( (self.XOFFSET + self.HEXAGONWIDTH*i, self.YOFFSET-66) , "ABCDEFGHIJK"[i], font=self.FONT, fill=self.TEXTCOLOR) - # Bottom letters - d.text( (self.XOFFSET + self.HEXAGONWIDTH*(i+11.5/2), self.YOFFSET - 15 + 11*self.HEXAGONHEIGHT) , "ABCDEFGHIJK"[i], font=self.FONT, fill=self.TEXTCOLOR) - # Left numbers - d.multiline_text( (self.XOFFSET + self.HEXAGONWIDTH*i/2 - 72 -4*(i>8), self.YOFFSET + 18 + i*self.HEXAGONHEIGHT) , str(i+1), font=self.FONT, fill=self.TEXTCOLOR, align="right") - # Right numbers - d.text( (self.XOFFSET + self.HEXAGONWIDTH*(i/2+11) + 30 , self.YOFFSET + 6 + i*self.HEXAGONHEIGHT) , str(i+1), font=self.FONT, fill=self.TEXTCOLOR) - # Write player names and color - game = self.bot.database["hex games"].find_one({"_id":channel}) - for p in [1,2]: - 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 - y = self.YNAME[p] - d.text((x,y),playername, font=self.NAMEFONT, fill = self.TEXTCOLOR) - # Draw a half-size Hexagon to indicate the player's color - x -= self.NAMEHEXPADDING # To the left of both names - d.polygon([ - (x, y), - (x+self.SMALLWIDTH/2, y-self.SMALLSIDELENGTH/2), - (x+self.SMALLWIDTH, y), - (x+self.SMALLWIDTH, y+self.SMALLSIDELENGTH), - (x+self.SMALLWIDTH/2, y+self.SMALLSIDELENGTH*3/2), - (x, y+self.SMALLSIDELENGTH), - ],fill = self.PIECECOLOR[p]) + playername = self.bot.database_funcs.get_name(game["players"][p%2]) - im.save("gwendolyn/resources/games/hex_boards/board"+channel+".png") + x = self.XNAME[p] + x -= self.NAMEFONT.getsize(playername)[0] if p==2 else 0 # player2's name is right-aligned + y = self.YNAME[p] + # Draw a half-size Hexagon to indicate the player's color + x -= self.NAMEHEXPADDING # To the left of both names + d.polygon([ + (x, y), + (x+self.SMALLWIDTH/2, y-self.SMALLSIDELENGTH/2), + (x+self.SMALLWIDTH, y), + (x+self.SMALLWIDTH, y+self.SMALLSIDELENGTH), + (x+self.SMALLWIDTH/2, y+self.SMALLSIDELENGTH*3/2), + (x, y+self.SMALLSIDELENGTH), + ],fill = self.PIECECOLOR[p % 2 + 1]) - - - def drawHexPlacement(self, channel,player,position): - FILEPATH = "gwendolyn/resources/games/hex_boards/board"+channel+".png" - self.bot.log(f"Drawing a newly placed hex. Filename: board{channel}.png") - - # Translates position - # We don't need to error-check, because the position is already checked in placeOnHexBoard() - position = position.lower() - column = ord(position[0])-97 # ord() translates from letter to number - row = int(position[1:])-1 - - # Find the coordinates for the filled hex drawing - 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), - (self.BOARDCOORDINATES[row][column][0]+self.HEXAGONWIDTH-self.COLXTHICKNESS, self.BOARDCOORDINATES[row][column][1]+self.SIDELENGTH - self.COLYTHICKNESS), - (self.BOARDCOORDINATES[row][column][0]+self.HEXAGONWIDTH/2, self.BOARDCOORDINATES[row][column][1]+1.5*self.SIDELENGTH - self.COLHEXTHICKNESS), - (self.BOARDCOORDINATES[row][column][0]+self.COLXTHICKNESS, self.BOARDCOORDINATES[row][column][1]+self.SIDELENGTH - self.COLYTHICKNESS), - ] - - # Opens the image - try: - with Image.open(FILEPATH) as im: - d = ImageDraw.Draw(im,"RGBA") - # Draws the hex piece - d.polygon(hex_coords,fill = self.PIECECOLOR[player], outline = self.BETWEENCOLOR) - - # Save - im.save(FILEPATH) - except: - self.bot.log("Error drawing new hex on board") - - def drawSwap(self, channel): - FILEPATH = "gwendolyn/resources/games/hex_boards/board"+channel+".png" - game = self.bot.database["hex games"].find_one({"_id":channel}) - # Opens the image - try: - with Image.open(FILEPATH) as im: - d = ImageDraw.Draw(im,"RGBA") - - # Write player names and color - for p in [1,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 - y = self.YNAME[p] - - # Draw a half-size Hexagon to indicate the player's color - x -= self.NAMEHEXPADDING # To the left of both names - d.polygon([ - (x, y), - (x+self.SMALLWIDTH/2, y-self.SMALLSIDELENGTH/2), - (x+self.SMALLWIDTH, y), - (x+self.SMALLWIDTH, y+self.SMALLSIDELENGTH), - (x+self.SMALLWIDTH/2, y+self.SMALLSIDELENGTH*3/2), - (x, y+self.SMALLSIDELENGTH), - ],fill = self.PIECECOLOR[p % 2 + 1]) - - # Save - im.save(FILEPATH) - except: - self.bot.log("Error drawing swap") + # Save + im.save(FILEPATH) + except: + self.bot.log("Error drawing swap") diff --git a/gwendolyn_old/funcs/games/money.py b/gwendolyn_old/funcs/games/money.py index 73c3e06..d3056c6 100644 --- a/gwendolyn_old/funcs/games/money.py +++ b/gwendolyn_old/funcs/games/money.py @@ -3,149 +3,149 @@ Contains the code that deals with money. *Classes* --------- - Money - Deals with money. + Money + Deals with money. """ import interactions # Used for typehints import discord # Used for typehints class Money(): + """ + Deals with money. + + *Methods* + --------- + checkBalance(user: str) + sendBalance(ctx: interactions.SlashContext) + addMoney(user: str, amount: int) + giveMoney(ctx: interactions.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 + + def checkBalance(self, user: str): """ - Deals with money. + Get the account balance of a user. - *Methods* - --------- - checkBalance(user: str) - sendBalance(ctx: interactions.SlashContext) - addMoney(user: str, amount: int) - giveMoney(ctx: interactions.SlashContext, user: discord.User, - amount: int) - - *Attributes* + *Parameters* ------------ - bot: Gwendolyn - The instance of Gwendolyn - database: pymongo.Client - The mongo database + 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") - def __init__(self, bot): - """Initialize the class.""" - self.bot = bot - self.database = bot.database + user_data = self.database["users"].find_one({"_id": user}) - def checkBalance(self, user: str): - """ - Get the account balance of a user. + if user_data is not None: + return user_data["money"] + else: + return 0 - *Parameters* - ------------ - user: str - The user to get the balance of. + async def sendBalance(self, ctx: interactions.SlashContext): + """ + Get your own account balance. - *Returns* - --------- - balance: int - The balance of the user's account. - """ - self.bot.log("checking "+user+"'s account balance") + *Parameters* + ------------ + ctx: interactions.SlashContext + The context of the command. + """ + await self.bot.defer(ctx) + response = self.checkBalance("#"+str(ctx.author.id)) + user_name = ctx.author.display_name + if response == 1: + new_message = f"{user_name} has {response} GwendoBuck" + else: + new_message = f"{user_name} has {response} GwendoBucks" + await ctx.send(new_message) - user_data = self.database["users"].find_one({"_id": user}) + # Adds money to the account of a user + def addMoney(self, user: str, amount: int): + """ + Add money to a user account. - if user_data is not None: - return user_data["money"] - else: - return 0 + *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") - async def sendBalance(self, ctx: interactions.SlashContext): - """ - Get your own account balance. + user_data = self.database["users"].find_one({"_id": user}) - *Parameters* - ------------ - ctx: interactions.SlashContext - The context of the command. - """ - await self.bot.defer(ctx) - response = self.checkBalance("#"+str(ctx.author.id)) - user_name = ctx.author.display_name - if response == 1: - new_message = f"{user_name} has {response} GwendoBuck" - else: - new_message = f"{user_name} has {response} GwendoBucks" - await ctx.send(new_message) + if user_data is not None: + updater = {"$inc": {"money": amount}} + self.database["users"].update_one({"_id": user}, updater) + else: + new_user = { + "_id": user, + "user name": self.bot.database_funcs.get_name(user), + "money": amount + } + self.database["users"].insert_one(new_user) - # Adds money to the account of a user - def addMoney(self, user: str, amount: int): - """ - Add money to a user account. + # Transfers money from one user to another + async def giveMoney(self, ctx: interactions.SlashContext, + user: discord.User, amount: int): + """ + Give someone else money from your 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") + *Parameters* + ------------ + ctx: interactions.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.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 + user_id = f"#{member.id}" + new_user = { + "_id": user_id, + "user name": username, + "money": 0 + } + self.bot.database["users"].insert_one(new_user) - user_data = self.database["users"].find_one({"_id": user}) + userid = f"#{ctx.author.id}" + user_data = self.database["users"].find_one({"_id": userid}) + targetUser = self.bot.database_funcs.get_id(username) - if user_data is not None: - updater = {"$inc": {"money": amount}} - self.database["users"].update_one({"_id": user}, updater) - else: - new_user = { - "_id": user, - "user name": self.bot.database_funcs.get_name(user), - "money": amount - } - self.database["users"].insert_one(new_user) - - # Transfers money from one user to another - async def giveMoney(self, ctx: interactions.SlashContext, - user: discord.User, amount: int): - """ - Give someone else money from your account. - - *Parameters* - ------------ - ctx: interactions.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.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 - user_id = f"#{member.id}" - new_user = { - "_id": user_id, - "user name": username, - "money": 0 - } - self.bot.database["users"].insert_one(new_user) - - userid = f"#{ctx.author.id}" - user_data = self.database["users"].find_one({"_id": userid}) - targetUser = self.bot.database_funcs.get_id(username) - - 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 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: - self.addMoney(f"#{ctx.author.id}", -1 * amount) - self.addMoney(targetUser, amount) - await ctx.send(f"Transferred {amount} GwendoBucks to {username}") + 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 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: + self.addMoney(f"#{ctx.author.id}", -1 * amount) + self.addMoney(targetUser, amount) + await ctx.send(f"Transferred {amount} GwendoBucks to {username}") diff --git a/gwendolyn_old/funcs/games/trivia.py b/gwendolyn_old/funcs/games/trivia.py index 9dad561..abdb386 100644 --- a/gwendolyn_old/funcs/games/trivia.py +++ b/gwendolyn_old/funcs/games/trivia.py @@ -3,8 +3,8 @@ Contains code for trivia games. *Classes* --------- - Trivia - Contains all the code for the trivia commands. + 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 @@ -15,191 +15,191 @@ from interactions import SlashContext # Used for type hints class Trivia(): - """ - Contains the code for trivia games. + """ + Contains the code for trivia games. - *Methods* + *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 + + def triviaStart(self, channel: str): + """ + Start 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 database. + + *Parameters* + ------------ + channel: str + The id of the channel to start the game in + + *Returns* --------- - triviaStart(channel: str) -> str, str, str - triviaAnswer(user: str, channel: str, command: str) -> str - triviaCountPoints(channel: str) - triviaParse(ctx: SlashContext, answer: str) + send_message: str + The message to return to the user. """ + triviaQuestions = self.bot.database["trivia questions"] + question = triviaQuestions.find_one({"_id": channel}) - def __init__(self, bot): - """Initialize the class.""" - self.bot = bot + self.bot.log(f"Trying to find a trivia question for {channel}") - def triviaStart(self, channel: str): - """ - Start a game of trivia. + 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()) - 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. + 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 = data["results"][0]["correct_answer"] + correctAnswer = answers.index(correctAnswer) + 97 - *Parameters* - ------------ - channel: str - The id of the channel to start the game in + newQuestion = { + "_id": channel, + "answer": str(chr(correctAnswer)), + "players": {} + } + triviaQuestions.insert_one(newQuestion) - *Returns* - --------- - send_message: str - The message to return to the user. - """ - triviaQuestions = self.bot.database["trivia questions"] - question = triviaQuestions.find_one({"_id": channel}) + replacements = { + "'": "\'", + """: "\"", + "“": "\"", + "”": "\"", + "é": "é" + } + question = data["results"][0]["question"] - self.bot.log(f"Trying to find a trivia question for {channel}") + for key, value in replacements.items(): + question = question.replace(key, value) - 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()) + for answer in answers: + for key, value in replacements.items(): + answer = answer.replace(key, value) - 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 = data["results"][0]["correct_answer"] - correctAnswer = answers.index(correctAnswer) + 97 + return question, answers, correctAnswer + else: + log_message = "There was already a trivia question for that channel" + self.bot.log(log_message) + return self.bot.long_strings["Trivia going on"], "", "" - newQuestion = { - "_id": channel, - "answer": str(chr(correctAnswer)), - "players": {} - } - triviaQuestions.insert_one(newQuestion) + def triviaAnswer(self, user: str, channel: str, command: str): + """ + Answer the current trivia question. - replacements = { - "'": "\'", - """: "\"", - "“": "\"", - "”": "\"", - "é": "é" - } - question = data["results"][0]["question"] + *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. - for key, value in replacements.items(): - question = question.replace(key, value) + *Returns* + --------- + send_message: str + The message to send if the function failed. + """ + triviaQuestions = self.bot.database["trivia questions"] + question = triviaQuestions.find_one({"_id": channel}) - for answer in answers: - for key, value in replacements.items(): - answer = answer.replace(key, value) + 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(f"{user} answered the question in {channel}") - return question, answers, correctAnswer - else: - log_message = "There was already a trivia question for that channel" - self.bot.log(log_message) - return self.bot.long_strings["Trivia going on"], "", "" + updater = {"$set": {f"players.{user}": command}} + triviaQuestions.update_one({"_id": channel}, updater) + return None - def triviaAnswer(self, user: str, channel: str, command: str): - """ - Answer the current trivia question. + def triviaCountPoints(self, channel: str): + """ + Add money to every winner's account. - *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. + *Parameters* + ------------ + channel: str + The id of the channel the game is in. + """ + triviaQuestions = self.bot.database["trivia questions"] + question = triviaQuestions.find_one({"_id": channel}) - *Returns* - --------- - send_message: str - The message to send if the function failed. - """ - triviaQuestions = self.bot.database["trivia questions"] - question = triviaQuestions.find_one({"_id": channel}) + self.bot.log("Counting points for question in "+channel) - 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(f"{user} answered the question in {channel}") + if question is not None: + for player, answer in question["players"].items(): + if answer == question["answer"]: + self.bot.money.addMoney(player, 1) + else: + self.bot.log("Couldn't find the questio") - updater = {"$set": {f"players.{user}": command}} - triviaQuestions.update_one({"_id": channel}, updater) - return None + return None - def triviaCountPoints(self, channel: str): - """ - Add money to every winner's account. + async def triviaParse(self, ctx: SlashContext, answer: str): + """ + Parse a trivia command. - *Parameters* - ------------ - channel: str - The id of the channel the game is in. - """ - triviaQuestions = self.bot.database["trivia questions"] - question = triviaQuestions.find_one({"_id": channel}) + *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(channelId) + if options != "": + results = "**"+question+"**\n" + for x, option in enumerate(options): + results += chr(x+97) + ") "+option+"\n" - self.bot.log("Counting points for question in "+channel) + await ctx.send(results) - if question is not None: - for player, answer in question["players"].items(): - if answer == question["answer"]: - self.bot.money.addMoney(player, 1) - else: - self.bot.log("Couldn't find the questio") + await asyncio.sleep(60) - return None + self.triviaCountPoints(channelId) - async def triviaParse(self, ctx: SlashContext, answer: str): - """ - Parse a trivia command. + delete_gameParams = ["trivia questions", channelId] + self.bot.database_funcs.delete_game(*delete_gameParams) - *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(channelId) - if options != "": - results = "**"+question+"**\n" - for x, option in enumerate(options): - results += chr(x+97) + ") "+option+"\n" + self.bot.log("Time's up for the trivia question", channelId) + 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) - await ctx.send(results) - - await asyncio.sleep(60) - - self.triviaCountPoints(channelId) - - delete_gameParams = ["trivia questions", channelId] - self.bot.database_funcs.delete_game(*delete_gameParams) - - self.bot.log("Time's up for the trivia question", channelId) - 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) - - elif answer in ["a", "b", "c", "d"]: - userId = f"#{ctx.author.id}" - response = self.triviaAnswer(userId, channelId, answer) - if response is None: - user_name = ctx.author.display_name - await ctx.send(f"{user_name} answered **{answer}**") - else: - await ctx.send(response) - else: - self.bot.log("I didn't understand that", channelId) - await ctx.send("I didn't understand that") + elif answer in ["a", "b", "c", "d"]: + userId = f"#{ctx.author.id}" + response = self.triviaAnswer(userId, channelId, answer) + if response is None: + user_name = ctx.author.display_name + await ctx.send(f"{user_name} answered **{answer}**") + else: + await ctx.send(response) + else: + self.bot.log("I didn't understand that", channelId) + await ctx.send("I didn't understand that") diff --git a/gwendolyn_old/funcs/games/wordle.py b/gwendolyn_old/funcs/games/wordle.py index 371bc35..3ba1004 100644 --- a/gwendolyn_old/funcs/games/wordle.py +++ b/gwendolyn_old/funcs/games/wordle.py @@ -25,177 +25,177 @@ PRESENT_COLOR = "#0f7aa8" COLORS = [INCORRECT_COLOR, PRESENT_COLOR, CORRECT_COLOR] class WordleGame(DatabaseGame): - """Implementation of wordle game.""" - def __init__(self, bot): - super().__init__(bot, "wordle", WordleDrawer) - 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, - "minDictionaryCount": 1, - "maxDictionaryCount": -1, - "limit": 1, - "api_key": api_key - } + """Implementation of wordle game.""" + def __init__(self, bot): + super().__init__(bot, "wordle", WordleDrawer) + 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, + "minDictionaryCount": 1, + "maxDictionaryCount": -1, + "limit": 1, + "api_key": api_key + } - async def start(self, ctx, letters: int): - if self._test_document(str(ctx.channel_id)): - await ctx.send("There is already a wordle game in this channel.") - self.bot.log("There was already a game going on") - return + async def start(self, ctx, letters: int): + if self._test_document(str(ctx.channel_id)): + await ctx.send("There is already a wordle game in this channel.") + self.bot.log("There was already a game going on") + return - params = self._APIPARAMS - params["minLength"] = letters - params["maxLength"] = letters - word = "-" - while "-" in word or "." in word: - response = requests.get(self._API_url, params=params) - if response.json() == []: - ctx.send("Could not find a word. Try again") - return - word = list(response.json()[0]["word"].upper()) + params = self._APIPARAMS + params["minLength"] = letters + params["maxLength"] = letters + word = "-" + while "-" in word or "." in word: + response = requests.get(self._API_url, params=params) + if response.json() == []: + ctx.send("Could not find a word. Try again") + return + word = list(response.json()[0]["word"].upper()) - self.bot.log(f"Found the word \"{''.join(word)}\"") - game = {"word": word, "guesses": [], "results": []} - await ctx.send("Starting a wordle game.") - await self._start_new(ctx.channel, game, delete=False) + self.bot.log(f"Found the word \"{''.join(word)}\"") + game = {"word": word, "guesses": [], "results": []} + await ctx.send("Starting a wordle game.") + await self._start_new(ctx.channel, game, delete=False) - def _get_result(self, guess: list[str], word: list[str]): - result = ["_" for _ in guess] - for i, letter in enumerate(guess): - if letter == word[i]: - result[i] = "*" + def _get_result(self, guess: list[str], word: list[str]): + result = ["_" for _ in guess] + for i, letter in enumerate(guess): + if letter == word[i]: + result[i] = "*" - for i, letter in enumerate(guess): - if letter in word and result[i] != "*": - same_letter = guess[:i].count(letter) - same_letter += len( - [ - l for j,l in - enumerate(guess[i:]) - if guess[i:][j] == letter and result[i:][j] == "*" - ] - ) - if same_letter < word.count(letter): - result[i] = "-" + for i, letter in enumerate(guess): + if letter in word and result[i] != "*": + same_letter = guess[:i].count(letter) + same_letter += len( + [ + l for j,l in + enumerate(guess[i:]) + if guess[i:][j] == letter and result[i:][j] == "*" + ] + ) + if same_letter < word.count(letter): + result[i] = "-" - return result + return result - async def guess(self, ctx, guess: str): - if not guess.isalpha(): - await ctx.send("You can only use letters in your guess") - return + async def guess(self, ctx, guess: str): + if not guess.isalpha(): + await ctx.send("You can only use letters in your guess") + return - guess = list(guess.upper()) - try: - game = self.access_document(str(ctx.channel_id)) - except GameNotInDatabase: - await ctx.send("No game in channel") - return + guess = list(guess.upper()) + try: + game = self.access_document(str(ctx.channel_id)) + except GameNotInDatabase: + await ctx.send("No game in channel") + return - if len(guess) != len(game['word']): - await ctx.send( - f"Your guess must be {len(game['word'])} letters long") - return + if len(guess) != len(game['word']): + await ctx.send( + f"Your guess must be {len(game['word'])} letters long") + return - await ctx.send(f"Guessed {''.join(guess)}") - result = self._get_result(guess, game['word']) + await ctx.send(f"Guessed {''.join(guess)}") + result = self._get_result(guess, game['word']) - updater = { - "$set": { - f"guesses.{len(game['guesses'])}": guess, - f"results.{len(game['guesses'])}": result - } - } - self._update_document(str(ctx.channel_id), updater) + updater = { + "$set": { + f"guesses.{len(game['guesses'])}": guess, + f"results.{len(game['guesses'])}": result + } + } + self._update_document(str(ctx.channel_id), updater) - if result == ["*" for _ in game['word']]: - await ctx.send("You guessed the word! Adding 15 GwendoBucks to your account") - self.bot.money.addMoney(f"#{ctx.author_id}", 15) - await self._end_game(ctx.channel) - elif len(game['guesses']) == 5: - await ctx.send(f"You used up all available guesses. The word was '{''.join(game['word'])}'") - await self._end_game(ctx.channel) - else: - print(len(game['guesses'])) - await self._delete_old_image(ctx.channel) - await self._send_image(ctx.channel, delete=False) + if result == ["*" for _ in game['word']]: + await ctx.send("You guessed the word! Adding 15 GwendoBucks to your account") + self.bot.money.addMoney(f"#{ctx.author_id}", 15) + await self._end_game(ctx.channel) + elif len(game['guesses']) == 5: + await ctx.send(f"You used up all available guesses. The word was '{''.join(game['word'])}'") + await self._end_game(ctx.channel) + else: + print(len(game['guesses'])) + await self._delete_old_image(ctx.channel) + await self._send_image(ctx.channel, delete=False) class WordleDrawer(BaseDrawer): - def __init__(self, bot, game: WordleGame): - super().__init__(bot, game) - self.default_image = None - self.default_color = BACKGROUND_COLOR - self.default_size = (0, 0) + def __init__(self, bot, game: WordleGame): + super().__init__(bot, game) + self.default_image = None + self.default_color = BACKGROUND_COLOR + self.default_size = (0, 0) - def _get_size(self, game: dict): - width = ( - (len(game['word']) * SQUARE_SIZE) + - ((len(game['word']) - 1) * SQUARE_PADDING) + - (2 * IMAGE_MARGIN) - ) - height = ( - (6 * SQUARE_SIZE) + - (5 * ROW_PADDING) + - (2 * IMAGE_MARGIN) - ) - size = (width, height) + def _get_size(self, game: dict): + width = ( + (len(game['word']) * SQUARE_SIZE) + + ((len(game['word']) - 1) * SQUARE_PADDING) + + (2 * IMAGE_MARGIN) + ) + height = ( + (6 * SQUARE_SIZE) + + (5 * ROW_PADDING) + + (2 * IMAGE_MARGIN) + ) + size = (width, height) - return size + return size - def _draw_row(self, row, drawer, font, word: str, colors: list[str] = None, - border_color: str = None): - if colors is None: - colors = [BACKGROUND_COLOR for _ in range(len(word))] + def _draw_row(self, row, drawer, font, word: str, colors: list[str] = None, + border_color: str = None): + if colors is None: + colors = [BACKGROUND_COLOR for _ in range(len(word))] - for i, letter in enumerate(word): - y_pos = IMAGE_MARGIN + (row * (SQUARE_SIZE + ROW_PADDING)) - x_pos = IMAGE_MARGIN + (i * (SQUARE_SIZE + SQUARE_PADDING)) - top_left = (x_pos, y_pos) - bottom_right = (x_pos + SQUARE_SIZE, y_pos + SQUARE_SIZE) + for i, letter in enumerate(word): + y_pos = IMAGE_MARGIN + (row * (SQUARE_SIZE + ROW_PADDING)) + x_pos = IMAGE_MARGIN + (i * (SQUARE_SIZE + SQUARE_PADDING)) + top_left = (x_pos, y_pos) + bottom_right = (x_pos + SQUARE_SIZE, y_pos + SQUARE_SIZE) - drawer.rounded_rectangle( - (top_left,bottom_right), - 10, - colors[i], - border_color, - 3 - ) + drawer.rounded_rectangle( + (top_left,bottom_right), + 10, + colors[i], + border_color, + 3 + ) - text_pos = ( - x_pos + (SQUARE_SIZE//2), - y_pos + int(SQUARE_SIZE * 0.6) - ) + text_pos = ( + x_pos + (SQUARE_SIZE//2), + y_pos + int(SQUARE_SIZE * 0.6) + ) - drawer.text(text_pos, letter, TEXT_COLOR, font=font, anchor="mm") + drawer.text(text_pos, letter, TEXT_COLOR, font=font, anchor="mm") - def _determine_colors(self, results): - return [COLORS[["_","-","*"].index(symbol)] for symbol in results] + def _determine_colors(self, results): + return [COLORS[["_","-","*"].index(symbol)] for symbol in results] - def _draw_image(self, game: dict, image: Image.Image): - drawer = ImageDraw.Draw(image) - font = self._get_font(FONT_SIZE) - for i, guess in enumerate(game['guesses']): - colors = self._determine_colors(game['results'][i]) - self._draw_row(i, drawer, font, guess, colors) + def _draw_image(self, game: dict, image: Image.Image): + drawer = ImageDraw.Draw(image) + font = self._get_font(FONT_SIZE) + for i, guess in enumerate(game['guesses']): + colors = self._determine_colors(game['results'][i]) + self._draw_row(i, drawer, font, guess, colors) - if len(game["guesses"]) < 6: - self._draw_row( - len(game['guesses']), - drawer, - font, - " "*len(game['word']), - border_color=WRITING_BORDER_COLOR - ) + if len(game["guesses"]) < 6: + self._draw_row( + len(game['guesses']), + drawer, + font, + " "*len(game['word']), + border_color=WRITING_BORDER_COLOR + ) - for i in range(5 - len(game['guesses'])): - self._draw_row( - len(game['guesses']) + i + 1, - drawer, - font, " "*len(game['word']), - border_color=INACTIVE_BORDER_COLOR - ) + for i in range(5 - len(game['guesses'])): + self._draw_row( + len(game['guesses']) + i + 1, + drawer, + font, " "*len(game['word']), + border_color=INACTIVE_BORDER_COLOR + ) - return super()._draw_image(game, image) + return super()._draw_image(game, image) diff --git a/gwendolyn_old/funcs/lookup/lookup_funcs.py b/gwendolyn_old/funcs/lookup/lookup_funcs.py index 83d4602..12ff8d2 100644 --- a/gwendolyn_old/funcs/lookup/lookup_funcs.py +++ b/gwendolyn_old/funcs/lookup/lookup_funcs.py @@ -5,217 +5,217 @@ import discord from gwendolyn_old.utils import cap STATS = [ - "strength", - "dexterity", - "constitution", - "intelligence", - "wisdom", - "charisma" + "strength", + "dexterity", + "constitution", + "intelligence", + "wisdom", + "charisma" ] def mod(statistic): - """Calculates D&D modifier.""" - modifier = math.floor((statistic-10)/2) - if modifier >= 0: - modifier = "+"+str(modifier) + """Calculates D&D modifier.""" + modifier = math.floor((statistic-10)/2) + if modifier >= 0: + modifier = "+"+str(modifier) - return modifier + return modifier class LookupFuncs(): - def __init__(self, bot): - self.bot = bot - self.saves = [ - "strength_save", - "dexterity_save", - "constitution_save", - "intelligence_save", - "wisdom_save", - "charisma_save" - ] - self.abilities = [ - "acrobatics", - "animal_handling", - "arcana", - "athletics", - "deception", - "history", - "insight", - "intimidation", - "investigation", - "medicine", - "nature", - "perception", - "performance", - "persuasion", - "religion", - "sleight_of_hand", - "stealth", - "survival" - ] + def __init__(self, bot): + self.bot = bot + self.saves = [ + "strength_save", + "dexterity_save", + "constitution_save", + "intelligence_save", + "wisdom_save", + "charisma_save" + ] + self.abilities = [ + "acrobatics", + "animal_handling", + "arcana", + "athletics", + "deception", + "history", + "insight", + "intimidation", + "investigation", + "medicine", + "nature", + "perception", + "performance", + "persuasion", + "religion", + "sleight_of_hand", + "stealth", + "survival" + ] - def _format_monster(self, monster): - # Looks at the information about the monster and - # returns that information in separate variables, - # allowing Gwendolyn to know where to separate - # the messages - types = monster["type"] - if monster["subtype"] != "": - types += " ("+monster["subtype"]+")" + def _format_monster(self, monster): + # Looks at the information about the monster and + # returns that information in separate variables, + # allowing Gwendolyn to know where to separate + # the messages + types = monster["type"] + if monster["subtype"] != "": + types += " ("+monster["subtype"]+")" - stats = [] - for stat in STATS: - value = monster[stat] - stats.append(f"**{cap(stat[:3])}:** {value} ({mod(value)})") + stats = [] + for stat in STATS: + value = monster[stat] + stats.append(f"**{cap(stat[:3])}:** {value} ({mod(value)})") - stats = "\t".join(stats[:3]) + "\n" + "\t".join(stats[3:]) + stats = "\t".join(stats[:3]) + "\n" + "\t".join(stats[3:]) - saving_throws = [] - for save in self.saves: - if save in monster: - value = monster[save] - if monster[save] >= 0: - saving_throws.append(f"{cap(save[:3])} +{value}") - else: - saving_throws.append(f"{cap(save[:3])} {value}") - - if saving_throws: - saving_throws = f"\n**Saving Throws:** {', '.join(saving_throws)}" + saving_throws = [] + for save in self.saves: + if save in monster: + value = monster[save] + if monster[save] >= 0: + saving_throws.append(f"{cap(save[:3])} +{value}") else: - saving_throws = "" + saving_throws.append(f"{cap(save[:3])} {value}") - skills = [] - for skill in self.abilities: - if skill in monster: - skill_name = cap(skill.replace("_"," ")) - if monster[skill] >= 0: - skills.append(f"{skill_name} +{monster[skill]}") - else: - skills.append(f"{skill_name} {monster[skill]}") + if saving_throws: + saving_throws = f"\n**Saving Throws:** {', '.join(saving_throws)}" + else: + saving_throws = "" - if skills: - skills = f"\n**Skills:** {', '.join(skills)}" + skills = [] + for skill in self.abilities: + if skill in monster: + skill_name = cap(skill.replace("_"," ")) + if monster[skill] >= 0: + skills.append(f"{skill_name} +{monster[skill]}") else: - skills = "" + skills.append(f"{skill_name} {monster[skill]}") - vulnerabilities = monster["damage_vulnerabilities"] - if vulnerabilities != "": - vulnerabilities = "\n**Damage Vulnerabilities** "+vulnerabilities + if skills: + skills = f"\n**Skills:** {', '.join(skills)}" + else: + skills = "" - resistances = monster["damage_resistances"] - if resistances != "": - resistances = "\n**Damage Resistances** "+resistances + vulnerabilities = monster["damage_vulnerabilities"] + if vulnerabilities != "": + vulnerabilities = "\n**Damage Vulnerabilities** "+vulnerabilities - immunities = monster["damage_immunities"] - if immunities != "": - immunities = "\n**Damage Immunities** "+immunities + resistances = monster["damage_resistances"] + if resistances != "": + resistances = "\n**Damage Resistances** "+resistances - c_immunities = monster["condition_immunities"] - if c_immunities != "": - c_immunities = "\n**Condition Immunities** "+c_immunities + immunities = monster["damage_immunities"] + if immunities != "": + immunities = "\n**Damage Immunities** "+immunities - special_abilities = "" - if "special_abilities" in monster: - for ability in monster["special_abilities"]: - special_abilities += "\n\n***"+ability["name"]+".*** "+ability["desc"] + c_immunities = monster["condition_immunities"] + if c_immunities != "": + c_immunities = "\n**Condition Immunities** "+c_immunities - act = "" - if "actions" in monster: - for action in monster["actions"]: - act += "\n\n***"+action["name"]+".*** "+action["desc"] + special_abilities = "" + if "special_abilities" in monster: + for ability in monster["special_abilities"]: + special_abilities += "\n\n***"+ability["name"]+".*** "+ability["desc"] - react = "" - if "reactions" in monster: - for reaction in monster["reactions"]: - react += "\n\n***"+reaction["name"]+".*** "+reaction["desc"] + act = "" + if "actions" in monster: + for action in monster["actions"]: + act += "\n\n***"+action["name"]+".*** "+action["desc"] - legendaryActions = "" - if "legendary_actions" in monster: - for action in monster["legendary_actions"]: - legendaryActions += "\n\n***"+action["name"]+".*** "+action["desc"] + react = "" + if "reactions" in monster: + for reaction in monster["reactions"]: + react += "\n\n***"+reaction["name"]+".*** "+reaction["desc"] - hit_dice = monster["hit_dice"] - dice_amount = int(monster["hit_dice"].replace("d"," ").split()[0]) - con_mod = math.floor((monster['constitution']-10)/2) - if con_mod < 0: - hit_dice += f" - {abs(con_mod) * dice_amount}" - elif con_mod > 0: - hit_dice += (f" + {con_mod * dice_amount}") + legendaryActions = "" + if "legendary_actions" in monster: + for action in monster["legendary_actions"]: + legendaryActions += "\n\n***"+action["name"]+".*** "+action["desc"] - new_part = "\n--------------------" + hit_dice = monster["hit_dice"] + dice_amount = int(monster["hit_dice"].replace("d"," ").split()[0]) + con_mod = math.floor((monster['constitution']-10)/2) + if con_mod < 0: + hit_dice += f" - {abs(con_mod) * dice_amount}" + elif con_mod > 0: + hit_dice += (f" + {con_mod * dice_amount}") - monster_type = f"*{monster['size']} {types}, {monster['alignment']}*" + new_part = "\n--------------------" - basic_info = "\n**Armor Class** "+str(monster["armor_class"])+"\n**Hit Points** "+str(monster["hit_points"])+" ("+hit_dice+")\n**Speed **"+monster["speed"]+new_part+"\n" + monster_type = f"*{monster['size']} {types}, {monster['alignment']}*" - info = (monster_type+new_part+basic_info+stats+new_part+saving_throws+skills+vulnerabilities+resistances+immunities+c_immunities+"\n**Senses** "+monster["senses"]+"\n**Languages** "+monster["languages"]+"\n**Challenge** "+monster["challenge_rating"]) + basic_info = "\n**Armor Class** "+str(monster["armor_class"])+"\n**Hit Points** "+str(monster["hit_points"])+" ("+hit_dice+")\n**Speed **"+monster["speed"]+new_part+"\n" - monster_info = [(info, monster['name']), - (special_abilities, "Special Abilities"), - (act, "Actions"), - (react, "Reactions"), - (legendaryActions, "Legendary Actions")] + info = (monster_type+new_part+basic_info+stats+new_part+saving_throws+skills+vulnerabilities+resistances+immunities+c_immunities+"\n**Senses** "+monster["senses"]+"\n**Languages** "+monster["languages"]+"\n**Challenge** "+monster["challenge_rating"]) - self.bot.log("Returning monster information") - return monster_info + monster_info = [(info, monster['name']), + (special_abilities, "Special Abilities"), + (act, "Actions"), + (react, "Reactions"), + (legendaryActions, "Legendary Actions")] - # Looks up a monster - async def monster_func(self, ctx, query): - query = cap(query) - self.bot.log("Looking up "+query) + self.bot.log("Returning monster information") + return monster_info - # 1-letter monsters don't exist - if len(query) < 2: - self.bot.log("Monster name too short") - await ctx.send("I don't know that monster...") - return + # Looks up a monster + async def monster_func(self, ctx, query): + query = cap(query) + self.bot.log("Looking up "+query) - # Opens "monsters.json" - monster_file_path = "gwendolyn/resources/lookup/monsters.json" - with open(monster_file_path,"r", encoding="utf-8") as file_pointer: - data = json.load(file_pointer) + # 1-letter monsters don't exist + if len(query) < 2: + self.bot.log("Monster name too short") + await ctx.send("I don't know that monster...") + return - for monster in data: - if "name" in monster and str(query) == monster["name"]: - self.bot.log("Found it!") + # Opens "monsters.json" + monster_file_path = "gwendolyn/resources/lookup/monsters.json" + with open(monster_file_path,"r", encoding="utf-8") as file_pointer: + data = json.load(file_pointer) - monster_info = self._format_monster(monster) + for monster in data: + if "name" in monster and str(query) == monster["name"]: + self.bot.log("Found it!") - # Sends the received information. Separates into separate messages if - # there is too much text - await ctx.send(f"Result for \"{query}\"") - for text, title in monster_info: - if text != "": - if len(text) < 2000: - em = discord.Embed(title = title, description = text, colour=0xDEADBF) - await ctx.channel.send(embed = em) - else: - index = text[:2000].rfind(".")+1 - em1 = discord.Embed(title = title, description = text[:index], colour=0xDEADBF) - await ctx.channel.send(embed = em1) - em2 = discord.Embed(title = "", description = text[index+1:], colour=0xDEADBF) - await ctx.channel.send(embed = em2) + monster_info = self._format_monster(monster) - break - else: - self.bot.log("Monster not in database") - await ctx.send("I don't know that monster...") + # Sends the received information. Separates into separate messages if + # there is too much text + await ctx.send(f"Result for \"{query}\"") + for text, title in monster_info: + if text != "": + if len(text) < 2000: + em = discord.Embed(title = title, description = text, colour=0xDEADBF) + await ctx.channel.send(embed = em) + else: + index = text[:2000].rfind(".")+1 + em1 = discord.Embed(title = title, description = text[:index], colour=0xDEADBF) + await ctx.channel.send(embed = em1) + em2 = discord.Embed(title = "", description = text[index+1:], colour=0xDEADBF) + await ctx.channel.send(embed = em2) - # Looks up a spell - async def spell_func(self, ctx, query): - query = cap(query) - self.bot.log("Looking up "+query) + break + else: + self.bot.log("Monster not in database") + await ctx.send("I don't know that monster...") - # Opens "spells.json" - data = json.load(open('gwendolyn/resources/lookup/spells.json', encoding = "utf8")) - if query in data: - self.bot.log("Returning spell information") - 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") - send_message = "I don't think that's a spell" + # Looks up a spell + async def spell_func(self, ctx, query): + query = cap(query) + self.bot.log("Looking up "+query) - if len(send_message) > 2000: - await ctx.send(send_message[:2000]) - await ctx.send(send_message[2000:]) - else: - await ctx.send(send_message) + # Opens "spells.json" + data = json.load(open('gwendolyn/resources/lookup/spells.json', encoding = "utf8")) + if query in data: + self.bot.log("Returning spell information") + 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") + send_message = "I don't think that's a spell" + + if len(send_message) > 2000: + await ctx.send(send_message[:2000]) + await ctx.send(send_message[2000:]) + else: + await ctx.send(send_message) diff --git a/gwendolyn_old/funcs/other/generators.py b/gwendolyn_old/funcs/other/generators.py index f593516..fae0266 100644 --- a/gwendolyn_old/funcs/other/generators.py +++ b/gwendolyn_old/funcs/other/generators.py @@ -1,94 +1,94 @@ import random class Generators(): - def __init__(self, bot): - self.bot = bot + def __init__(self, bot): + self.bot = bot - # Returns a list of all letter pairs in the text - def make_pairs(self, corpus): - for i in range(len(corpus)-1): - yield (corpus[i], corpus[i+1]) + # Returns a list of all letter pairs in the text + def make_pairs(self, corpus): + for i in range(len(corpus)-1): + yield (corpus[i], corpus[i+1]) - # Returns a list of all letter triplets in the text - def make_triplets(self, corpus): - for i in range(len(corpus)-2): - yield (corpus[i], corpus[i+1], corpus[i+2]) + # Returns a list of all letter triplets in the text + def make_triplets(self, corpus): + for i in range(len(corpus)-2): + yield (corpus[i], corpus[i+1], corpus[i+2]) - # Generates a random name - async def name_gen(self, ctx): - # Makes a list of all names from "names.txt" - with open("gwendolyn/resources/names.txt", "r", encoding='utf8') as file_pointer: - names = file_pointer.read() - corpus = list(names) + # Generates a random name + async def name_gen(self, ctx): + # Makes a list of all names from "names.txt" + with open("gwendolyn/resources/names.txt", "r", encoding='utf8') as file_pointer: + names = file_pointer.read() + corpus = list(names) - # Makes a list of pairs - pairs = self.make_pairs(corpus) - triplets = self.make_triplets(corpus) + # Makes a list of pairs + pairs = self.make_pairs(corpus) + triplets = self.make_triplets(corpus) - letter_dict = {} + letter_dict = {} - # Makes a dictionary of all letters that come after all other letters - for letter_1, letter_2 in pairs: - if letter_1 in letter_dict: - letter_dict[letter_1].append(letter_2) - else: - letter_dict[letter_1] = [letter_2] + # Makes a dictionary of all letters that come after all other letters + for letter_1, letter_2 in pairs: + if letter_1 in letter_dict: + letter_dict[letter_1].append(letter_2) + else: + letter_dict[letter_1] = [letter_2] - for letter_1, letter_2, letter_3 in triplets: - if letter_1+letter_2 in letter_dict: - letter_dict[letter_1+letter_2].append(letter_3) - else: - letter_dict[letter_1+letter_2] = [letter_3] + for letter_1, letter_2, letter_3 in triplets: + if letter_1+letter_2 in letter_dict: + letter_dict[letter_1+letter_2].append(letter_3) + else: + letter_dict[letter_1+letter_2] = [letter_3] - # Choses a random first letter - first_letter = random.choice(corpus) + # Choses a random first letter + first_letter = random.choice(corpus) - # Makes sure the first letter is not something a name can't start with. - while first_letter.islower() or first_letter == " " or first_letter == "-" or first_letter == "\n": - first_letter = random.choice(corpus) + # Makes sure the first letter is not something a name can't start with. + while first_letter.islower() or first_letter == " " or first_letter == "-" or first_letter == "\n": + first_letter = random.choice(corpus) - # Starts the name - chain = [first_letter] + # Starts the name + chain = [first_letter] - # Picks second letter - second_letter = random.choice(letter_dict[chain[-1]]) + # Picks second letter + second_letter = random.choice(letter_dict[chain[-1]]) - while second_letter == "\n": - second_letter = random.choice(letter_dict[chain[-1]]) + while second_letter == "\n": + second_letter = random.choice(letter_dict[chain[-1]]) - chain.append(second_letter) + chain.append(second_letter) - done = False + done = False - # Creates the name one letter at a time - while not done: - if random.randint(1,10) > 1: - try: - new_letter = random.choice(letter_dict[chain[-2]+chain[-1]]) - except KeyError(): - new_letter = random.choice(letter_dict[chain[-1]]) - else: - new_letter = random.choice(letter_dict[chain[-1]]) - chain.append(new_letter) - # Ends name if the name ends - if new_letter == "\n": - done = True - gen_name = "".join(chain) - self.bot.log("Generated "+gen_name[:-1]) + # Creates the name one letter at a time + while not done: + if random.randint(1,10) > 1: + try: + new_letter = random.choice(letter_dict[chain[-2]+chain[-1]]) + except KeyError(): + new_letter = random.choice(letter_dict[chain[-1]]) + else: + new_letter = random.choice(letter_dict[chain[-1]]) + chain.append(new_letter) + # Ends name if the name ends + if new_letter == "\n": + done = True + gen_name = "".join(chain) + self.bot.log("Generated "+gen_name[:-1]) - # Returns the name - await ctx.send(gen_name) + # Returns the name + await ctx.send(gen_name) - # Generates a random tavern name - async def tavern_gen(self, ctx): - # _lists first parts, second parts and third parts of tavern names - first_part = ["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"] - second_part = ["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"] - third_part = [" Tavern"," Inn","","","","","","","","",""] + # Generates a random tavern name + async def tavern_gen(self, ctx): + # _lists first parts, second parts and third parts of tavern names + first_part = ["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"] + second_part = ["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"] + third_part = [" Tavern"," Inn","","","","","","","","",""] - # Picks one of each - gen_tav = random.choice(first_part)+" "+random.choice(second_part)+random.choice(third_part) - self.bot.log("Generated "+gen_tav) + # Picks one of each + gen_tav = random.choice(first_part)+" "+random.choice(second_part)+random.choice(third_part) + self.bot.log("Generated "+gen_tav) - # Return the name - await ctx.send(gen_tav) + # Return the name + await ctx.send(gen_tav) diff --git a/gwendolyn_old/funcs/other/nerd_shit.py b/gwendolyn_old/funcs/other/nerd_shit.py index 4f4c856..a47a7ee 100644 --- a/gwendolyn_old/funcs/other/nerd_shit.py +++ b/gwendolyn_old/funcs/other/nerd_shit.py @@ -6,80 +6,80 @@ import wolframalpha from PIL import Image, ImageDraw, ImageFont class NerdShit(): - def __init__(self, bot): - """Runs misc commands.""" - self.bot = bot + def __init__(self, bot): + """Runs misc commands.""" + self.bot = bot - async def wolf_search(self,ctx,content): - await self.bot.defer(ctx) - font = ImageFont.truetype('gwendolyn/resources/fonts/times-new-roman.ttf', 20) - self.bot.log("Requesting data") - bot = wolframalpha.Client(self.bot.credentials["wolfram_alpha_key"]) - res = bot.query(content) + async def wolf_search(self,ctx,content): + await self.bot.defer(ctx) + font = ImageFont.truetype('gwendolyn/resources/fonts/times-new-roman.ttf', 20) + self.bot.log("Requesting data") + bot = wolframalpha.Client(self.bot.credentials["wolfram_alpha_key"]) + res = bot.query(content) - self.bot.log("Processing data") - titles = [] - pods = [] - if int(res.numpods) > 0: - for pod in res.pods: - titles += [pod.title] - for i, sub in enumerate(pod.subpods): - pods += [sub] - if i > 0: - titles += [""] + self.bot.log("Processing data") + titles = [] + pods = [] + if int(res.numpods) > 0: + for pod in res.pods: + titles += [pod.title] + for i, sub in enumerate(pod.subpods): + pods += [sub] + if i > 0: + titles += [""] - pod_chunks = [pods[x:x+2] for x in range(0, len(pods), 2)] - title_chunks = [titles[x:x+2] for x in range(0, len(titles), 2)] - await ctx.send(f"Response for \"{content}\"") + pod_chunks = [pods[x:x+2] for x in range(0, len(pods), 2)] + title_chunks = [titles[x:x+2] for x in range(0, len(titles), 2)] + await ctx.send(f"Response for \"{content}\"") - for i, chunk in enumerate(pod_chunks): - width = 0 - for title in title_chunks[i]: - width = max(width,font.getsize(title)[0]) - height = 5 - heights = [] - for count, pod in enumerate(chunk): - heights += [height] - width = max(width,int(pod.img['@width'])) - if title_chunks[i][count] == "": - place_for_text = 0 - else: - place_for_text = 30 - height += int(pod.img["@height"]) + 10 + place_for_text + for i, chunk in enumerate(pod_chunks): + width = 0 + for title in title_chunks[i]: + width = max(width,font.getsize(title)[0]) + height = 5 + heights = [] + for count, pod in enumerate(chunk): + heights += [height] + width = max(width,int(pod.img['@width'])) + if title_chunks[i][count] == "": + place_for_text = 0 + else: + place_for_text = 30 + height += int(pod.img["@height"]) + 10 + place_for_text - width += 10 - height += 5 - wolf_image = Image.new("RGB",(width,height),color=(255,255,255)) + width += 10 + height += 5 + wolf_image = Image.new("RGB",(width,height),color=(255,255,255)) - for count, pod in enumerate(chunk): - response = requests.get(pod.img["@src"]) - file = open("gwendolyn/resources/wolfTemp.png", "wb") - file.write(response.content) - file.close() - old_image = Image.open("gwendolyn/resources/wolfTemp.png") - old_size = old_image.size - if title_chunks[i][count] == "": - place_for_text = 0 - else: - place_for_text = 30 - new_size = (width,int(old_size[1]+10+place_for_text)) - new_image = Image.new("RGB",new_size,color=(255,255,255)) - new_image.paste(old_image, (int((int(old_size[0]+10)-old_size[0])/2),int(((new_size[1]-place_for_text)-old_size[1])/2)+place_for_text)) - if title_chunks[i][count] != "": - drawer = ImageDraw.Draw(new_image,"RGB") - drawer.text((5,7),title_chunks[i][count],font=font,fill=(150,150,150)) + for count, pod in enumerate(chunk): + response = requests.get(pod.img["@src"]) + file = open("gwendolyn/resources/wolfTemp.png", "wb") + file.write(response.content) + file.close() + old_image = Image.open("gwendolyn/resources/wolfTemp.png") + old_size = old_image.size + if title_chunks[i][count] == "": + place_for_text = 0 + else: + place_for_text = 30 + new_size = (width,int(old_size[1]+10+place_for_text)) + new_image = Image.new("RGB",new_size,color=(255,255,255)) + new_image.paste(old_image, (int((int(old_size[0]+10)-old_size[0])/2),int(((new_size[1]-place_for_text)-old_size[1])/2)+place_for_text)) + if title_chunks[i][count] != "": + drawer = ImageDraw.Draw(new_image,"RGB") + drawer.text((5,7),title_chunks[i][count],font=font,fill=(150,150,150)) - wolf_image.paste(new_image,(0,heights[count])) - new_image.close() - old_image.close() - count += 1 + wolf_image.paste(new_image,(0,heights[count])) + new_image.close() + old_image.close() + count += 1 - wolf_image.save("gwendolyn/resources/wolf.png") - wolf_image.close() - await ctx.channel.send(file = discord.File("gwendolyn/resources/wolf.png")) + wolf_image.save("gwendolyn/resources/wolf.png") + wolf_image.close() + await ctx.channel.send(file = discord.File("gwendolyn/resources/wolf.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") + 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") diff --git a/gwendolyn_old/funcs/other/other.py b/gwendolyn_old/funcs/other/other.py index 77333b4..deef8e7 100644 --- a/gwendolyn_old/funcs/other/other.py +++ b/gwendolyn_old/funcs/other/other.py @@ -17,176 +17,176 @@ fandom.set_lang("da") fandom.set_wiki("senkulpa") class MyStringifier(d20.MarkdownStringifier): - def _str_expression(self, node): - if node.comment is None: - result_text = "Result" - else: - result_text = node.comment.capitalize() + def _str_expression(self, node): + if node.comment is None: + result_text = "Result" + else: + result_text = node.comment.capitalize() - return f"**{result_text}**: {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.plex = Plex(self.bot) - self.nerd_shit = NerdShit(self.bot) - self.generators = Generators(self.bot) + def __init__(self, bot): + self.bot = bot + self.plex = Plex(self.bot) + self.nerd_shit = NerdShit(self.bot) + self.generators = Generators(self.bot) - # Picks a random movie and returns information about it - async def movie_func(self, ctx): - await self.bot.defer(ctx) + # Picks a random movie and returns information about it + async def movie_func(self, ctx): + await self.bot.defer(ctx) - self.bot.log("Creating IMDb object") - imdb_client = imdb.IMDb() + self.bot.log("Creating IMDb object") + imdb_client = imdb.IMDb() - self.bot.log("Picking a movie") - with open("gwendolyn/resources/movies.txt", "r") as file_pointer: - movie_list = file_pointer.read().split("\n") - movie_name = random.choice(movie_list) + self.bot.log("Picking a movie") + with open("gwendolyn/resources/movies.txt", "r") as file_pointer: + movie_list = file_pointer.read().split("\n") + movie_name = random.choice(movie_list) - self.bot.log(f"Searching for {movie_name}") - search_result = imdb_client.search_movie(movie_name) + self.bot.log(f"Searching for {movie_name}") + search_result = imdb_client.search_movie(movie_name) - self.bot.log("Getting the data") - movie = search_result[0] - imdb_client.update(movie) + self.bot.log("Getting the data") + movie = search_result[0] + imdb_client.update(movie) - self.bot.log("Successfully ran /movie") + self.bot.log("Successfully ran /movie") - title = movie["title"] - plot = movie['plot'][0].split("::")[0] - cover = movie['cover url'].replace("150","600").replace("101","404") - cast = ", ".join([i["name"] for i in movie['cast'][:5]]) - embed = discord.Embed(title=title, description=plot, color=0x24ec19) - embed.set_thumbnail(url=cover) - embed.add_field(name="Cast", value=cast,inline = True) - await ctx.send(embed = embed) + title = movie["title"] + plot = movie['plot'][0].split("::")[0] + cover = movie['cover url'].replace("150","600").replace("101","404") + cast = ", ".join([i["name"] for i in movie['cast'][:5]]) + embed = discord.Embed(title=title, description=plot, color=0x24ec19) + embed.set_thumbnail(url=cover) + embed.add_field(name="Cast", value=cast,inline = True) + await ctx.send(embed = embed) - # Responds with a greeting of a time-appropriate maner - async def hello_func(self, ctx): - def time_in_range(start, end, i): - # Return true if i is in the range [start, end] - if start <= end: - return start <= i <= end - else: - return start <= i or i <= end + # Responds with a greeting of a time-appropriate maner + async def hello_func(self, ctx): + def time_in_range(start, end, i): + # Return true if i is in the range [start, end] + if start <= end: + return start <= i <= end + else: + return start <= i or i <= end - author = ctx.author.display_name - now = datetime.datetime.now() - if time_in_range(now.replace(hour=5, minute=0, second=0, microsecond=0),now.replace(hour=10, minute=0, second=0, microsecond=0), now): - 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): - 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): - 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): - send_message = "Good night, "+str(author) - else: - send_message = "Hello, "+str(author) + 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): + 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): + 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): + 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): + send_message = "Good night, "+str(author) + else: + send_message = "Hello, "+str(author) - await ctx.send(send_message) + await ctx.send(send_message) - # Finds a random picture online - async def image_func(self, ctx): - # Picks a type of camera, which decides the naming scheme - cams = ("one","two","three","four") - cam = random.choice(cams) - self.bot.log("Chose cam type "+cam) - if cam == "one": + # Finds a random picture online + async def image_func(self, ctx): + # Picks a type of camera, which decides the naming scheme + cams = ("one","two","three","four") + cam = random.choice(cams) + self.bot.log("Chose cam type "+cam) + if cam == "one": - search = "img_" + ''.join( - [str(random.randint(0,9)) for _ in range(4)] - ) - elif cam == "two": - year = str(random.randint(2012,2016)) - month = str(random.randint(1,12)).zfill(2) - day = str(random.randint(1,29)).zfill(2) - search = f"IMG_{year}{month}{day}" - elif cam == "three": - search = f"IMAG_{str(random.randint(1,500)).zfill(4)}" - elif cam == "four": - search = "DSC_" + ''.join( - [str(random.randint(0,9)) for _ in range(4)] - ) + search = "img_" + ''.join( + [str(random.randint(0,9)) for _ in range(4)] + ) + elif cam == "two": + year = str(random.randint(2012,2016)) + month = str(random.randint(1,12)).zfill(2) + day = str(random.randint(1,29)).zfill(2) + search = f"IMG_{year}{month}{day}" + elif cam == "three": + search = f"IMAG_{str(random.randint(1,500)).zfill(4)}" + elif cam == "four": + search = "DSC_" + ''.join( + [str(random.randint(0,9)) for _ in range(4)] + ) - self.bot.log("Searching for "+search) + self.bot.log("Searching for "+search) - # Searches for the image and reads the resulting web page - page = urllib.request.urlopen("https://www.bing.com/images/search?q="+search+"&safesearch=off") - read = page.read() - tree = lxml.etree.HTML(read) - images = tree.xpath('//a[@class = "iusc"]/@m') + # Searches for the image and reads the resulting web page + page = urllib.request.urlopen("https://www.bing.com/images/search?q="+search+"&safesearch=off") + read = page.read() + tree = lxml.etree.HTML(read) + images = tree.xpath('//a[@class = "iusc"]/@m') - if len(images) == 0: - await ctx.send("Found no images") - else: - # Picks an image - number = random.randint(1,len(images))-1 - image = ast.literal_eval(str(images[number])) - image_url = image["murl"] + if len(images) == 0: + await ctx.send("Found no images") + else: + # Picks an image + number = random.randint(1,len(images))-1 + image = ast.literal_eval(str(images[number])) + image_url = image["murl"] - self.bot.log("Picked image number "+str(number)) + self.bot.log("Picked image number "+str(number)) - # Returns the image - self.bot.log("Successfully returned an image") + # Returns the image + self.bot.log("Successfully returned an image") - await ctx.send(image_url) + await ctx.send(image_url) - # Finds a page from the Senkulpa Wikia - async def find_wiki_page(self, ctx, search : str): - await self.bot.defer(ctx) - found_page = False + # Finds a page from the Senkulpa Wikia + async def find_wiki_page(self, ctx, search : str): + await self.bot.defer(ctx) + found_page = False - if search != "": - self.bot.log("Trying to find wiki page for "+search) - search_results = fandom.search(search) - if len(search_results) > 0: - found_page = True - search_result = search_results[0] - else: - self.bot.log("Couldn't find the page") - await ctx.send("Couldn't find page") - else: - found_page = True - self.bot.log("Searching for a random page") - search_result = fandom.random() + if search != "": + self.bot.log("Trying to find wiki page for "+search) + search_results = fandom.search(search) + if len(search_results) > 0: + found_page = True + search_result = search_results[0] + else: + self.bot.log("Couldn't find the page") + await ctx.send("Couldn't find page") + else: + found_page = True + self.bot.log("Searching for a random page") + search_result = fandom.random() - if found_page: - self.bot.log(f"Found page \"{search_result[0]}\"") - page = fandom.page(pageid = search_result[1]) - content = page.summary + if found_page: + self.bot.log(f"Found page \"{search_result[0]}\"") + page = fandom.page(pageid = search_result[1]) + content = page.summary - images = page.images - if len(images) > 0: - image = images[0] - else: - image = "" - self.bot.log("Sending the embedded message",str(ctx.channel_id)) - content += f"\n[Læs mere]({page.url})" - embed = discord.Embed(title = page.title, description = content, colour=0xDEADBF) - if image != "": - embed.set_thumbnail(url=image) + images = page.images + if len(images) > 0: + image = images[0] + else: + image = "" + self.bot.log("Sending the embedded message",str(ctx.channel_id)) + content += f"\n[Læs mere]({page.url})" + embed = discord.Embed(title = page.title, description = content, colour=0xDEADBF) + if image != "": + embed.set_thumbnail(url=image) - await ctx.send(embed = embed) + await ctx.send(embed = embed) - async def roll_dice(self, ctx, roll_string): - user = ctx.author.display_name - while len(roll_string) > 1 and roll_string[0] == " ": - roll_string = roll_string[1:] + async def roll_dice(self, ctx, roll_string): + user = ctx.author.display_name + while len(roll_string) > 1 and roll_string[0] == " ": + roll_string = roll_string[1:] - roll = d20.roll(roll_string, allow_comments=True, stringifier=MyStringifier()) - await ctx.send(f"{user} :game_die:\n{roll}") + roll = d20.roll(roll_string, allow_comments=True, stringifier=MyStringifier()) + await ctx.send(f"{user} :game_die:\n{roll}") - async def help_func(self, ctx, command): - if command == "": - with open("gwendolyn/resources/help/help.txt",encoding="utf-8") as file_pointer: - text = file_pointer.read() - embed = discord.Embed(title = "Help", description = text,colour = 0x59f442) - await ctx.send(embed = embed) - else: - self.bot.log(f"Looking for help-{command}.txt",str(ctx.channel_id)) - with open(f"gwendolyn/resources/help/help-{command}.txt",encoding="utf-8") as file_pointer: - text = file_pointer.read() - embed = discord.Embed(title = command.capitalize(), description = text,colour = 0x59f442) - await ctx.send(embed = embed) + async def help_func(self, ctx, command): + if command == "": + with open("gwendolyn/resources/help/help.txt",encoding="utf-8") as file_pointer: + text = file_pointer.read() + embed = discord.Embed(title = "Help", description = text,colour = 0x59f442) + await ctx.send(embed = embed) + else: + self.bot.log(f"Looking for help-{command}.txt",str(ctx.channel_id)) + with open(f"gwendolyn/resources/help/help-{command}.txt",encoding="utf-8") as file_pointer: + text = file_pointer.read() + embed = discord.Embed(title = command.capitalize(), description = text,colour = 0x59f442) + await ctx.send(embed = embed) diff --git a/gwendolyn_old/funcs/other/plex.py b/gwendolyn_old/funcs/other/plex.py index 5a156c2..38058dc 100644 --- a/gwendolyn_old/funcs/other/plex.py +++ b/gwendolyn_old/funcs/other/plex.py @@ -14,587 +14,587 @@ from interactions.model import ButtonStyle from gwendolyn_old.utils import encode_id 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"]] + """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/" + 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) + 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] + 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?**" + if len(movies) == 1: + message_title = "**Is it this movie?**" + else: + message_title = "**Is it any of these movies?**" - message_text = "" - imdb_ids = [] + 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) + 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}" + 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 + ) + + buttons = [] + if len(movies) == 1: + buttons.append(create_button( + style=ButtonStyle.green, + label="✓", + custom_id=encode_id(["plex", "movie", str(imdb_ids[0])]) + ) + ) + else: + for i in range(len(movies)): + buttons.append( + create_button( + style=ButtonStyle.blue, + label=str(i+1), + custom_id=encode_id(["plex", "movie", str(imdb_ids[i])]) + ) ) - embed = discord.Embed( - title=message_title, - description=message_text, - colour=0x00FF00 + buttons.append(create_button( + style=ButtonStyle.red, + label="X", + custom_id=encode_id(["plex", "movie", "x"]) + ) + ) + + action_rows = [] + for i in range(((len(buttons)-1)//5)+1): + action_rows.append( + create_actionrow( + *buttons[(i*5):(min(len(buttons),i*5+5))] + ) + ) + + await ctx.send(embed=embed, components=action_rows) + + + + + async def add_movie(self, message, imdb_id, edit_message = True): + """Add a movie to Plex server.""" + + if not edit_message: + await message.delete() + + if imdb_id == "X": + self.bot.log("Did not find what the user was searching for") + message_text = "Try searching for the IMDB id" + else: + self.bot.log("Trying to add movie "+str(imdb_id)) + + # Searches for the movie using the imdb id through Radarr + 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) + + # Makes the dict used for the post request + lookup_data = response.json() + post_data = { + "qualityProfileId": 1, + "rootFolderPath" : self.movie_path, + "monitored" : True, + "addOptions": {"searchForMovie": True} + } + for key in ["tmdbId","title","titleSlug","images","year"]: + post_data.update({key : lookup_data[key]}) + + # Makes the post request + response = requests.post( + url = self.radarr_url+"movie?apikey="+api_key, + json = post_data + ) + + # Deciphers the response + if response.status_code == 201: + self.bot.log("Added "+post_data["title"]+" to Plex") + message_text = "{} successfully added to Plex".format( + post_data["title"] + ) + elif response.status_code == 400: + self.bot.log("The movie was already on plex") + message_text = self.long_strings["Already on Plex"].format( + post_data['title'] + ) + else: + self.bot.log(str(response.status_code)+" "+response.reason) + message_text = "Something went wrong", + + if edit_message: + await message.edit( + embed = None, + content = message_text, + components = [] + ) + else: + await message.channel.send(message_text) + + 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_ids = [] + + 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_ids.append(show.movieID) + + self.bot.log( + f"Returning a list of {len(shows)} possible shows: {imdb_ids}" + ) + + embed = discord.Embed( + title=message_title, + description=message_text, + colour=0x00FF00 + ) + + buttons = [] + if len(shows) == 1: + buttons.append(create_button( + style=ButtonStyle.green, + label="✓", + custom_id=encode_id(["plex", "show", str(imdb_ids[0])]) + ) + ) + else: + for i in range(len(shows)): + buttons.append( + create_button( + style=ButtonStyle.blue, + label=str(i+1), + custom_id=encode_id(["plex", "show", str(imdb_ids[i])]) + ) ) - buttons = [] - if len(movies) == 1: - buttons.append(create_button( - style=ButtonStyle.green, - label="✓", - custom_id=encode_id(["plex", "movie", str(imdb_ids[0])]) - ) - ) - else: - for i in range(len(movies)): - buttons.append( - create_button( - style=ButtonStyle.blue, - label=str(i+1), - custom_id=encode_id(["plex", "movie", str(imdb_ids[i])]) - ) - ) + buttons.append(create_button( + style=ButtonStyle.red, + label="X", + custom_id=encode_id(["plex", "show", "x"]) + ) + ) - buttons.append(create_button( - style=ButtonStyle.red, - label="X", - custom_id=encode_id(["plex", "movie", "x"]) + action_rows = [] + for i in range(((len(buttons)-1)//5)+1): + action_rows.append( + create_actionrow( + *buttons[(i*5):(min(len(buttons),i*5+5))] + ) + ) + + + await ctx.send(embed=embed, components=action_rows) + + async def add_show(self, message, imdb_id, edit_message = True): + """Add the requested show to Plex.""" + if imdb_id == "X": + self.bot.log("Did not find what the user was searching for") + message_text = "Try searching for the IMDB id" + else: + self.bot.log("Trying to add show "+str(imdb_id)) + + # Finds the tvdb id + tvdb_api_url = "https://thetvdb.com/api/" + tvdb_method = "GetSeriesByRemoteID.php" + tvdb_request_url = f"{tvdb_api_url}{tvdb_method}" + tvdb_id = xmltodict.parse( + requests.get( + tvdb_request_url+f"?imdbid=tt{imdb_id}", + headers = {"ContentType" : "application/json"} + ).text + )['Data']['Series']['seriesid'] + + # Finds the rest of the information using Sonarr + api_key = self.credentials["sonarr_key"] + request_url = self.sonarr_url+"series/lookup?term=" + request_url += f"tvdb:{tvdb_id}" + request_url += "&apiKey="+api_key + response = requests.get(request_url) + + # Makes the dict used for the post request + 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]}) + + # Makes the post request + response = requests.post( + url= self.sonarr_url+"series?apikey="+api_key, + json = post_data + ) + + # Deciphers the response + if response.status_code == 201: + self.bot.log("Added a "+post_data["title"]+" to Plex") + message_text = post_data["title"]+" successfully added to Plex" + elif response.status_code == 400: + message_text = self.long_strings["Already on Plex"].format( + post_data['title'] + ) + else: + self.bot.log(str(response.status_code)+" "+response.reason) + message_text = "Something went wrong" + + if edit_message: + await message.edit( + embed = None, + content = message_text, + components = [] + ) + else: + await message.channel.send(message_text) + + 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 + ) + login_url = self.qbittorrent_url+"auth/login" + username = self.credentials["qbittorrent_username"] + password = self.credentials["qbittorrent_password"] + login_url += f"?username={username}&password={password}" + cookie = {"SID": requests.get(login_url).cookies.values()[0]} + + response = requests.get( + self.qbittorrent_url+"torrents/info", + cookies=cookie + ) + torrent_list = response.json() + + if len(torrent_list) > 0: + for torrent in torrent_list: + if torrent['category'] not in ["radarr", "tv-sonarr"]: + break + 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?apiKey="+self.credentials["radarr_key"] + ).json() + print( + self.radarr_url+"movie?apiKey="+self.credentials["radarr_key"] + ) + movie_queue = requests.get( + self.radarr_url+"queue?apiKey="+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 ) - action_rows = [] - for i in range(((len(buttons)-1)//5)+1): - action_rows.append( - create_actionrow( - *buttons[(i*5):(min(len(buttons),i*5+5))] - ) - ) + show_list = requests.get( + self.sonarr_url+"series?apiKey="+self.credentials["sonarr_key"] + ).json() - await ctx.send(embed=embed, components=action_rows) - - - - - async def add_movie(self, message, imdb_id, edit_message = True): - """Add a movie to Plex server.""" - - if not edit_message: - await message.delete() - - if imdb_id == "X": - self.bot.log("Did not find what the user was searching for") - message_text = "Try searching for the IMDB id" + for show in show_list: + if show["seasons"][0]["seasonNumber"] == 0: + seasons = show["seasons"][1:] else: - self.bot.log("Trying to add movie "+str(imdb_id)) + 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)" + ) - # Searches for the movie using the imdb id through Radarr - 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) + message.append("-"*title_width) - # Makes the dict used for the post request - lookup_data = response.json() - post_data = { - "qualityProfileId": 1, - "rootFolderPath" : self.movie_path, - "monitored" : True, - "addOptions": {"searchForMovie": True} - } - for key in ["tmdbId","title","titleSlug","images","year"]: - post_data.update({key : lookup_data[key]}) + message_text = "```"+"\n".join(message[1:])+"```" + if message_text == "``````": + message_text = self.long_strings["No torrents downloading"] + return message_text, all_downloaded - # Makes the post request - response = requests.post( - url = self.radarr_url+"movie?apikey="+api_key, - json = post_data - ) + 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:]) - # Deciphers the response - if response.status_code == 201: - self.bot.log("Added "+post_data["title"]+" to Plex") - message_text = "{} successfully added to Plex".format( - post_data["title"] - ) - elif response.status_code == 400: - self.bot.log("The movie was already on plex") - message_text = self.long_strings["Already on Plex"].format( - post_data['title'] - ) - else: - self.bot.log(str(response.status_code)+" "+response.reason) - message_text = "Something went wrong", + await self.bot.defer(ctx) - if edit_message: - await message.edit( - embed = None, - content = message_text, - components = [] - ) + # 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: - await message.channel.send(message_text) + arg_start = 1 + arg_stop = 2 - 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?**" + input_args.append(content[arg_start:arg_stop]) + if arg_stop is None: + content = "" else: - message_title = "**Is it any of these shows?**" + content = content[arg_stop:] + else: + valid_arguments = False - message_text = "" - imdb_ids = [] + 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 - 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_ids.append(show.movieID) + if len(input_args) != 0 or (not parameters[2] and parameters[3]): + valid_arguments = False - self.bot.log( - f"Returning a list of {len(shows)} possible shows: {imdb_ids}" + 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) ) - embed = discord.Embed( - title=message_title, - description=message_text, - colour=0x00FF00 - ) - - buttons = [] - if len(shows) == 1: - buttons.append(create_button( - style=ButtonStyle.green, - label="✓", - custom_id=encode_id(["plex", "show", str(imdb_ids[0])]) - ) + 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] ) - else: - for i in range(len(shows)): - buttons.append( - create_button( - style=ButtonStyle.blue, - label=str(i+1), - custom_id=encode_id(["plex", "show", str(imdb_ids[i])]) - ) - ) + self.bot.log("The message updated 20 times") - buttons.append(create_button( - style=ButtonStyle.red, - label="X", - custom_id=encode_id(["plex", "show", "x"]) - ) - ) - - action_rows = [] - for i in range(((len(buttons)-1)//5)+1): - action_rows.append( - create_actionrow( - *buttons[(i*5):(min(len(buttons),i*5+5))] - ) - ) - - - await ctx.send(embed=embed, components=action_rows) - - async def add_show(self, message, imdb_id, edit_message = True): - """Add the requested show to Plex.""" - if imdb_id == "X": - self.bot.log("Did not find what the user was searching for") - message_text = "Try searching for the IMDB id" - else: - self.bot.log("Trying to add show "+str(imdb_id)) - - # Finds the tvdb id - tvdb_api_url = "https://thetvdb.com/api/" - tvdb_method = "GetSeriesByRemoteID.php" - tvdb_request_url = f"{tvdb_api_url}{tvdb_method}" - tvdb_id = xmltodict.parse( - requests.get( - tvdb_request_url+f"?imdbid=tt{imdb_id}", - headers = {"ContentType" : "application/json"} - ).text - )['Data']['Series']['seriesid'] - - # Finds the rest of the information using Sonarr - api_key = self.credentials["sonarr_key"] - request_url = self.sonarr_url+"series/lookup?term=" - request_url += f"tvdb:{tvdb_id}" - request_url += "&apiKey="+api_key - response = requests.get(request_url) - - # Makes the dict used for the post request - 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]}) - - # Makes the post request - response = requests.post( - url= self.sonarr_url+"series?apikey="+api_key, - json = post_data - ) - - # Deciphers the response - if response.status_code == 201: - self.bot.log("Added a "+post_data["title"]+" to Plex") - message_text = post_data["title"]+" successfully added to Plex" - elif response.status_code == 400: - message_text = self.long_strings["Already on Plex"].format( - post_data['title'] - ) - else: - self.bot.log(str(response.status_code)+" "+response.reason) - message_text = "Something went wrong" - - if edit_message: - await message.edit( - embed = None, - content = message_text, - components = [] - ) - else: - await message.channel.send(message_text) - - 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 - ) - login_url = self.qbittorrent_url+"auth/login" - username = self.credentials["qbittorrent_username"] - password = self.credentials["qbittorrent_password"] - login_url += f"?username={username}&password={password}" - cookie = {"SID": requests.get(login_url).cookies.values()[0]} - - response = requests.get( - self.qbittorrent_url+"torrents/info", - cookies=cookie - ) - torrent_list = response.json() - - if len(torrent_list) > 0: - for torrent in torrent_list: - if torrent['category'] not in ["radarr", "tv-sonarr"]: - break - 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?apiKey="+self.credentials["radarr_key"] - ).json() - print( - self.radarr_url+"movie?apiKey="+self.credentials["radarr_key"] - ) - movie_queue = requests.get( - self.radarr_url+"queue?apiKey="+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?apiKey="+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) + await old_message.edit(content = message_text) diff --git a/gwendolyn_old/funcs/star_wars_funcs/star_wars.py b/gwendolyn_old/funcs/star_wars_funcs/star_wars.py index 43fb417..8332cbe 100644 --- a/gwendolyn_old/funcs/star_wars_funcs/star_wars.py +++ b/gwendolyn_old/funcs/star_wars_funcs/star_wars.py @@ -3,8 +3,8 @@ from .star_wars_roll import StarWarsRoll from .star_wars_destiny import StarWarsDestiny class StarWars(): - def __init__(self, bot): - self.bot = bot - self.character = StarWarsChar(self.bot) - self.roll = StarWarsRoll(self.bot) - self.destiny = StarWarsDestiny(self.bot) + def __init__(self, bot): + self.bot = bot + self.character = StarWarsChar(self.bot) + self.roll = StarWarsRoll(self.bot) + self.destiny = StarWarsDestiny(self.bot) diff --git a/gwendolyn_old/funcs/star_wars_funcs/star_wars_char.py b/gwendolyn_old/funcs/star_wars_funcs/star_wars_char.py index d3486bd..d9b2269 100644 --- a/gwendolyn_old/funcs/star_wars_funcs/star_wars_char.py +++ b/gwendolyn_old/funcs/star_wars_funcs/star_wars_char.py @@ -3,526 +3,526 @@ import string import discord class StarWarsChar(): - def __init__(self, bot): - self.bot = bot + def __init__(self, bot): + self.bot = bot - def get_char_name(self, user : str): - self.bot.log("Getting name for "+self.bot.database_funcs.get_name(user)+"'s character") - user_character = self.bot.database["starwars characters"].find_one({"_id":user}) + def get_char_name(self, user : str): + self.bot.log("Getting name for "+self.bot.database_funcs.get_name(user)+"'s character") + user_character = self.bot.database["starwars characters"].find_one({"_id":user}) - if user_character != None: - self.bot.log("Name is "+user_character["Name"]) - return user_character["Name"] - else: - self.bot.log("Just using "+self.bot.database_funcs.get_name(user)) - return self.bot.database_funcs.get_name(user) + if user_character != None: + self.bot.log("Name is "+user_character["Name"]) + return user_character["Name"] + else: + self.bot.log("Just using "+self.bot.database_funcs.get_name(user)) + return self.bot.database_funcs.get_name(user) - def set_up_dict(self, cmd : dict): - self.bot.log("Setting up a dictionary in a nice way") - if bool(cmd): - keys = list(cmd) - values = list(cmd.values()) - result = "" - if isinstance(values[0],dict): - return ", ".join(values) + def set_up_dict(self, cmd : dict): + self.bot.log("Setting up a dictionary in a nice way") + if bool(cmd): + keys = list(cmd) + values = list(cmd.values()) + result = "" + if isinstance(values[0],dict): + return ", ".join(values) + else: + for i, key in enumerate(keys): + if type(key) is list: + if i%3 != 2: + result += "**" + key + "**" + ": " + ", ".join(values[i]) + " " else: - for i, key in enumerate(keys): - if type(key) is list: - if i%3 != 2: - result += "**" + key + "**" + ": " + ", ".join(values[i]) + " " - else: - result += "**" + key + "**" + ": " + ", ".join(values[i]) + "\n" - else: - if i%3 != 2: - result += "**" + key + "**" + ": " + str(values[i]) + " " - else: - result += "**" + key + "**" + ": " + str(values[i]) + "\n" - self.bot.log("Returning a dictionary, but well formatted") - return result - else: - self.bot.log("Couldn't find anything") - return "There doesn't seem to be anything here..." - - def look_up(self, data : dict, key : str, cmd : str = ""): - if cmd == " ": - cmd = "" - elif cmd != "": - while cmd[0] == " ": - cmd = cmd[1:] - if cmd == "": - break - - self.bot.log("Looking up "+key) - if key in data: - self.bot.log(key+" exists") - if cmd == "": - if type(data[key]) is dict and key != "Weapons": - return self.set_up_dict(data[key]) - elif key == "Weapons": - self.bot.log("Does this even get used? I'm too scared to delete it") - if bool(data[key]): - self.bot.log("Found "+(", ".join(list(data[key])))) - return ", ".join(list(data[key])) - else: - self.bot.log("There is nothing here") - return "There doesn't seem to be anything here..." - else: - if str(data[key]) != "": - self.bot.log("Returning "+str(data[key])) - return data[key] - else: - self.bot.log("There was nothing there") - return "There doesn't seem to be anything here" - elif cmd[0] == '+': - self.bot.log("Trying to add to "+key) - try: - cmd = cmd[1:] - while cmd[0] == ' ': - cmd = cmd[1:] - except: - self.bot.log("Yeah, that fucked up") - return "Can't do that" - - if type(data[key]) is int: - try: - new_value = data[key] + int(cmd) - data[key] = new_value - self.bot.log("Added "+cmd+" to "+key) - return data - except: - self.bot.log("Couldn't add "+cmd+" to "+key) - return "Can't add that" - elif type(data[key]) is list: - try: - data[key].append(cmd) - self.bot.log("Added "+cmd+" to "+key) - return data - except: - self.bot.log("Couldn't add "+cmd+" to "+key) - return "Can't add that" - else: - self.bot.log("Yeah, I can't add that to "+key) - return "Can't add that" - - elif cmd[0] == '-': - self.bot.log("Trying to remove/subtract from "+key) - try: - cmd = cmd[1:] - while cmd[0] == ' ': - cmd = cmd[1:] - except: - self.bot.log("Yeah, that fucked up") - return "Can't do that" - - if type(data[key]) is int: - try: - new_value = data[key] - int(cmd) - data[key] = new_value - self.bot.log("Subtracted "+cmd+" from "+key) - return data - except: - self.bot.log("Couldn't subtract "+cmd+" from "+key) - return "Can't remove that" - elif type(data[key]) is list: - try: - data[key].remove(cmd) - self.bot.log("Removed "+cmd+" from "+key) - return data - except: - self.bot.log("Couldn't remove "+cmd+" from "+key) - return "Can't remove that" - else: - self.bot.log("Yeah, I can't remove/subtract that from "+key) - return "Can't remove that" + result += "**" + key + "**" + ": " + ", ".join(values[i]) + "\n" + else: + if i%3 != 2: + result += "**" + key + "**" + ": " + str(values[i]) + " " else: - while cmd[0] == ' ': - cmd = cmd[1:] - - if type(data[key]) is dict: - new_key = cmd.split(" ")[0] - cmd = cmd[len(new_key):] - if cmd != "": - while cmd[0] == " ": - cmd = cmd[1:] - if cmd == "": - break - self.bot.log("Looking up "+new_key+" in "+key) - look_up_result = self.look_up(data[key],new_key,cmd) - if type(look_up_result) is dict: - data[key] = look_up_result - return data - else: - return look_up_result - elif type(data[key]) != list: - self.bot.log("Trying to change "+key+" to "+cmd) - try: - cmd = type(data[key])(cmd) - data[key] = cmd - self.bot.log("Did that") - return data - except: - self.bot.log("No. That did not work") - return "Wrong data type" - else: - self.bot.log("Yeah, that didn't work") - return "Wrong data type" - else: - self.bot.log("Couldn't find "+key) - self.bot.log("Testing to see if it's a multi-word key") - cmd = key + " " + cmd - words = cmd.split(" ") - search = "" - i = 0 - while search not in data: - try: - search += " " + words[i] - i += 1 - except: - self.bot.log("It wasn't. "+search+" doesn't exist") - return search + " doesn't exist" - if search[0] == " ": - search = search[1:] - self.bot.log("Yeah! Did that! The key was "+search) - - cmd = cmd[len(search):] - if cmd != "": - while cmd[0] == " ": - cmd = cmd[1:] - if cmd == "": - break - - if cmd == "": - self.bot.log("Returning "+search) - return self.set_up_dict(data[search]) - else: - new_key = cmd.split(" ")[0] - cmd = cmd[len(new_key):] - if cmd != "": - while cmd[0] == " ": - cmd = cmd[1:] - if cmd == "": - break - look_up_result = self.look_up(data[search],new_key,cmd) - if type(look_up_result) is dict: - data[search] = look_up_result - return data - else: - return look_up_result - - def character_sheet(self,character : dict): - self.bot.log("Setting up a character sheet for "+character["Name"]) - divider = "--------------------\n" - name = character["Name"] - textf = "" - if character["Force-rating"] != 0: - textf = "\n**Force Rating**: "+str(character["Force-rating"]) - - text1 = "**Species**: "+character["Species"]+"\n**Career**: "+character["Career"]+"\n**Specialization Trees**: "+", ".join(character["Specialization-trees"])+textf+"\n**Soak**: "+str(character["Soak"]) - text2 = "\n\n**Wounds**: "+str(character["Wounds"])+"/"+str(character["Wound-threshold"])+"\n**Strain**: "+str(character["Strain"])+"/"+str(character["Strain-threshold"]) - text3 = self.set_up_dict(character["Characteristics"]) - text4 = self.set_up_dict(character["Skills"]) - text5 = "" - text6 = "" - text7 = "" - text8 = "" - - if bool(character["Talents"]): - text5 = "**Talents**: "+", ".join(list(character["Talents"]))+"\n\n" - - if bool(character["Force-powers"]): - text6 = "**Force Powers**: "+", ".join(list(character["Force-powers"]))+"\n\n" - - text7 = "**Equipment**: "+", ".join(character["Equipment"])+"\n**Credits**: "+str(character["Credits"])+"\n**Weapons**: "+", ".join(list(character["Weapons"]))+"\n"+divider - - if bool(character["Obligations"]): - text8 = "**Obligations**: "+",".join(list(character["Obligations"])) - - return name, text1+text2+"\n\n"+text3+divider+text4+"\n"+divider+text5+text6+text7+text8 - - def char_data(self,user : str,cmd : str): - user_character = self.bot.database["starwars characters"].find_one({"_id":user}) - - key = string.capwords(cmd.split(" ")[0]) - cmd = cmd[len(key):] - if cmd == " ": - cmd = "" - elif cmd != "": - while cmd[0] == " ": - cmd = cmd[1:] - if cmd == "": - break - - self.bot.log("Looking for "+self.bot.database_funcs.get_name(user)+"'s character") - if user_character != None: - self.bot.log("Found it! Looking for "+key+" in the data") - if key in user_character: - self.bot.log("Found it!") - if type(user_character[key]) is dict: - self.bot.log("It's a dictionary!") - if cmd == "": - self.bot.log("Retrieving data") - if key == "Weapons": - if bool(user_character[key]): - self.bot.log("Returning a list of weapons") - return ", ".join(list(user_character[key])) - else: - self.bot.log("The character doesn't have any weapons. Which is probably for the best. Like, who just walks around with weapons?") - return "There doesn't seem to be anything there..." - else: - return self.set_up_dict(user_character[key]) - elif cmd[0] == "+": - self.bot.log("Gonna add something!!!") - try: - cmd = cmd[1:] - while cmd[0] == ' ': - cmd = cmd[1:] - except: - self.bot.log("Nope. That didn't happen") - return "Can't do that" - - if (key == "Talents" or key == "Force-powers") and "," in cmd: - cmd = cmd.split(",") - while cmd[1][0] == " ": - cmd[1] = cmd[1][1:] - self.bot.log("Adding "+cmd[0]+" to "+key) - self.bot.database["starwars characters"].update_one({"_id":user}, - {"$set": {key+"."+cmd[0] : cmd[1]}}) - return cmd[0]+" added to "+key+" for " + user_character["Name"] - - elif key == "Obligations" and "," in cmd: - cmd = cmd.split(",") - while cmd[1][0] == " ": - cmd[1] = cmd[1][1:] - self.bot.log("Adding "+cmd[0]+" to "+key) - try: - self.bot.database["starwars characters"].update_one({"_id":user}, - {"$set": {key+"."+cmd[0] : int(cmd[1])}}) - except: - self.bot.log("Fucked that up") - return "Wrong data type" - return cmd[0]+" added to "+key+" for " + user_character["Name"] - - elif key == "Weapons": - with open("gwendolyn/resources/star_wars/starwarstemplates.json", "r") as file_pointer: - templates = json.load(file_pointer) - new_weapon = templates["Weapon"] - self.bot.log("Adding "+cmd+" to "+key) - self.bot.database["starwars characters"].update_one({"_id":user}, - {"$set": {key+"."+cmd : new_weapon}}) - - return cmd+" added to weapons for " + user_character["Name"] - - else: - self.bot.log("That's not happening") - return "Can't add that" - - elif cmd[0] == "-": - self.bot.log("Gonna subtract/remove something") - try: - cmd = cmd[1:] - while cmd[0] == ' ': - cmd = cmd[1:] - except: - self.bot.log("AAAAAAAAAAAA ") - return "Can't do that " - - if key == "Talents" or key == "Force-powers" or key == "Weapons" or key == "Obligations": - self.bot.log("Trying to remove "+cmd+" from "+key) - if cmd in user_character[key]: - self.bot.database["starwars characters"].update_one({"_id":user}, - {"$unset": {cmd}}) - self.bot.log("I did that") - return cmd+" removed from "+key+" from "+user_character["Name"] - else: - self.bot.log("Welp. I fucked that up") - return "Can't remove that" - else: - self.bot.log("Urgh!") - return "Can't remove that" - - else: - self.bot.log("Looking up "+cmd+" in "+key) - if key == "Talents" or key == "Force-powers": - new_key = cmd - new_cmd = "" - else: - new_key = string.capwords(cmd.split(" ")[0]) - new_cmd = cmd[len(new_key):] - - look_up_result = self.look_up(user_character[key],new_key,new_cmd) - - if type(look_up_result) is dict: - self.bot.database["starwars characters"].update_one({"_id":user}, - {"$set": {key : look_up_result}}) - return "Changed " + user_character["Name"] + "'s " + key - else: - return look_up_result - else: - if cmd == "": - self.bot.log("Retrieving data") - if type(user_character[key]) is list: - return key+":\n"+", ".join(user_character[key]) - else: - return user_character[key] - elif cmd[0] == '+': - self.bot.log("Adding") - try: - cmd = cmd[1:] - while cmd[0] == ' ': - cmd = cmd[1:] - except: - self.bot.log("Error message") - return "Can't do that" - - if type(user_character[key]) is int: - try: - self.bot.log("Adding "+cmd+" to "+key) - self.bot.database["starwars characters"].update_one({"_id":user}, - {"$inc": {key : int(cmd)}}) - return "Added " + cmd + " to " + user_character["Name"] + "'s " + key - except: - self.bot.log("BITCH SANDWICH") - return "Can't add that" - elif type(user_character[key]) is list: - try: - self.bot.log("Adding "+cmd+" to "+key) - self.bot.database["starwars characters"].update_one({"_id":user}, - {"$push": {key : cmd}}) - return "Added " + cmd + " to " + user_character["Name"] + "'s " + key - except: - self.bot.log("tstststststs") - return "Can't add that" - else: - self.bot.log("Help") - return "Can't add that" - elif cmd[0] == '-': - self.bot.log("Removing/subtracting") - try: - cmd = cmd[1:] - while cmd[0] == ' ': - cmd = cmd[1:] - except: - self.bot.log("lalalala ") - return "Can't do that" - - if type(user_character[key]) is int: - try: - self.bot.log("Subtracting "+cmd+" from "+key) - self.bot.database["starwars characters"].update_one({"_id":user}, - {"$inc": {key : -int(cmd)}}) - return "Subtracted " + cmd + " from " + user_character["Name"] + "'s " + key - except: - self.bot.log("Tried it. Didn't want to") - return "Can't remove that" - elif type(user_character[key]) is list: - try: - self.bot.log("removing "+cmd+" from "+key) - try: - self.bot.database["starwars characters"].update_one({"_id":user}, - {"$pull": {key : cmd}}) - except: - self.bot.log("They can only remove stuff that's actually in the list") - return "Not in list" - return "Removed " + cmd + " from " + user_character["Name"] + "'s " + key - except: - self.bot.log("nah") - return "Can't remove that" - else: - self.bot.log("nyope") - return "Can't remove that" - else: - self.bot.log("Changing "+key+" to "+cmd) - if type(user_character[key]) is int: - try: - self.bot.database["starwars characters"].update_one({"_id":user}, - {"$set": {key : int(cmd)}}) - except: - self.bot.log("I don't wanna tho") - return "Can't do that" - elif type(user_character[key]) is str: - self.bot.database["starwars characters"].update_one({"_id":user}, - {"$set": {key : cmd}}) - else: - self.bot.log("I don't wanna tho") - return "Can't do that" - return "Changed " + user_character["Name"] + "'s " + key +" to " + cmd - else: - self.bot.log(key+" isn't in there") - return "Couldn't find that data. Are you sure you spelled it correctly?" - else: - self.bot.log(user+" doesn't have a character") - return "You don't have a character. You can make one with /starwarscharacter" - - def replace_spaces(self,cmd : str): - with_spaces = ["Specialization Trees","Wound Threshold","Strain Threshold","Defense - Ranged","Defense - Melee","Force Rating","Core Worlds","Outer Rim","Piloting - Planetary","Piloting - Space","Ranged - Heavy","Ranged - Light","Lightsaber Characteristic","Critical Injuries","Force Powers"] - without_spaces = ["Specialization-trees","Wound-threshold","Strain-threshold","Defense-ranged","Defense-melee","Force-rating","Core-worlds","Outer-rim","Piloting-planetary","Piloting-space","Ranged-heavy","Ranged-light","Lightsaber-characteristic","Critical-injuries","Force-powers"] - - for i, value in enumerate(without_spaces): - cmd = cmd.replace(with_spaces[i],value) - - return cmd - - def replace_with_spaces(self,cmd : str): - with_spaces = ["Specialization Trees","Wound Threshold","Strain Threshold","Defense - Ranged","Defense - Melee","Force Rating","Core Worlds","Outer Rim","Piloting - Planetary","Piloting - Space","Ranged - Heavy","Ranged - light","Lightsaber Characteristic","Critical Injuries","Force Powers"] - without_spaces = ["Specialization-trees","Wound-threshold","Strain-threshold","Defense-ranged","Defense-melee","Force-rating","Core-worlds","Outer-rim","Piloting-planetary","Piloting-space","Ranged-heavy","Ranged-light","Lightsaber-characteristic","Critical-injuries","Force-powers"] - - for i, value in enumerate(without_spaces): - cmd = cmd.replace(value,with_spaces[i]) - - return cmd - - async def parse_char(self, ctx, parameters : str): - user = f"#{ctx.author.id}" - cmd = string.capwords(parameters.replace("+","+ ").replace("-","- ").replace(",",", ")) - return_embed = False - - cmd = self.replace_spaces(cmd) - - user_character = self.bot.database["starwars characters"].find_one({"_id":user}) - - if cmd == " ": - cmd = "" - elif cmd != "": - while cmd[0] == " ": - cmd = cmd[1:] - if cmd == "": - break - + result += "**" + key + "**" + ": " + str(values[i]) + "\n" + self.bot.log("Returning a dictionary, but well formatted") + return result + else: + self.bot.log("Couldn't find anything") + return "There doesn't seem to be anything here..." + def look_up(self, data : dict, key : str, cmd : str = ""): + if cmd == " ": + cmd = "" + elif cmd != "": + while cmd[0] == " ": + cmd = cmd[1:] if cmd == "": - if user_character != None: - title, text = self.character_sheet(user_character) - text = self.replace_with_spaces(text) - return_embed = True - else: - self.bot.log("Makin' a character for "+self.bot.database_funcs.get_name(user)) - with open("gwendolyn/resources/star_wars/starwarstemplates.json", "r") as file_pointer: - templates = json.load(file_pointer) - new_char = templates["Character"] - new_char["_id"] = user - self.bot.database["starwars characters"].insert_one(new_char) - await ctx.send("Character for " + self.bot.database_funcs.get_name(user) + " created") + break + + self.bot.log("Looking up "+key) + if key in data: + self.bot.log(key+" exists") + if cmd == "": + if type(data[key]) is dict and key != "Weapons": + return self.set_up_dict(data[key]) + elif key == "Weapons": + self.bot.log("Does this even get used? I'm too scared to delete it") + if bool(data[key]): + self.bot.log("Found "+(", ".join(list(data[key])))) + return ", ".join(list(data[key])) + else: + self.bot.log("There is nothing here") + return "There doesn't seem to be anything here..." else: - if cmd == "Purge": - 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.get_name(user) + " deleted") + if str(data[key]) != "": + self.bot.log("Returning "+str(data[key])) + return data[key] + else: + self.bot.log("There was nothing there") + return "There doesn't seem to be anything here" + elif cmd[0] == '+': + self.bot.log("Trying to add to "+key) + try: + cmd = cmd[1:] + while cmd[0] == ' ': + cmd = cmd[1:] + except: + self.bot.log("Yeah, that fucked up") + return "Can't do that" + + if type(data[key]) is int: + try: + new_value = data[key] + int(cmd) + data[key] = new_value + self.bot.log("Added "+cmd+" to "+key) + return data + except: + self.bot.log("Couldn't add "+cmd+" to "+key) + return "Can't add that" + elif type(data[key]) is list: + try: + data[key].append(cmd) + self.bot.log("Added "+cmd+" to "+key) + return data + except: + self.bot.log("Couldn't add "+cmd+" to "+key) + return "Can't add that" + else: + self.bot.log("Yeah, I can't add that to "+key) + return "Can't add that" + + elif cmd[0] == '-': + self.bot.log("Trying to remove/subtract from "+key) + try: + cmd = cmd[1:] + while cmd[0] == ' ': + cmd = cmd[1:] + except: + self.bot.log("Yeah, that fucked up") + return "Can't do that" + + if type(data[key]) is int: + try: + new_value = data[key] - int(cmd) + data[key] = new_value + self.bot.log("Subtracted "+cmd+" from "+key) + return data + except: + self.bot.log("Couldn't subtract "+cmd+" from "+key) + return "Can't remove that" + elif type(data[key]) is list: + try: + data[key].remove(cmd) + self.bot.log("Removed "+cmd+" from "+key) + return data + except: + self.bot.log("Couldn't remove "+cmd+" from "+key) + return "Can't remove that" + else: + self.bot.log("Yeah, I can't remove/subtract that from "+key) + return "Can't remove that" + else: + while cmd[0] == ' ': + cmd = cmd[1:] + + if type(data[key]) is dict: + new_key = cmd.split(" ")[0] + cmd = cmd[len(new_key):] + if cmd != "": + while cmd[0] == " ": + cmd = cmd[1:] + if cmd == "": + break + self.bot.log("Looking up "+new_key+" in "+key) + look_up_result = self.look_up(data[key],new_key,cmd) + if type(look_up_result) is dict: + data[key] = look_up_result + return data + else: + return look_up_result + elif type(data[key]) != list: + self.bot.log("Trying to change "+key+" to "+cmd) + try: + cmd = type(data[key])(cmd) + data[key] = cmd + self.bot.log("Did that") + return data + except: + self.bot.log("No. That did not work") + return "Wrong data type" + else: + self.bot.log("Yeah, that didn't work") + return "Wrong data type" + else: + self.bot.log("Couldn't find "+key) + self.bot.log("Testing to see if it's a multi-word key") + cmd = key + " " + cmd + words = cmd.split(" ") + search = "" + i = 0 + while search not in data: + try: + search += " " + words[i] + i += 1 + except: + self.bot.log("It wasn't. "+search+" doesn't exist") + return search + " doesn't exist" + if search[0] == " ": + search = search[1:] + self.bot.log("Yeah! Did that! The key was "+search) + + cmd = cmd[len(search):] + if cmd != "": + while cmd[0] == " ": + cmd = cmd[1:] + if cmd == "": + break + + if cmd == "": + self.bot.log("Returning "+search) + return self.set_up_dict(data[search]) + else: + new_key = cmd.split(" ")[0] + cmd = cmd[len(new_key):] + if cmd != "": + while cmd[0] == " ": + cmd = cmd[1:] + if cmd == "": + break + look_up_result = self.look_up(data[search],new_key,cmd) + if type(look_up_result) is dict: + data[search] = look_up_result + return data + else: + return look_up_result + + def character_sheet(self,character : dict): + self.bot.log("Setting up a character sheet for "+character["Name"]) + divider = "--------------------\n" + name = character["Name"] + textf = "" + if character["Force-rating"] != 0: + textf = "\n**Force Rating**: "+str(character["Force-rating"]) + + text1 = "**Species**: "+character["Species"]+"\n**Career**: "+character["Career"]+"\n**Specialization Trees**: "+", ".join(character["Specialization-trees"])+textf+"\n**Soak**: "+str(character["Soak"]) + text2 = "\n\n**Wounds**: "+str(character["Wounds"])+"/"+str(character["Wound-threshold"])+"\n**Strain**: "+str(character["Strain"])+"/"+str(character["Strain-threshold"]) + text3 = self.set_up_dict(character["Characteristics"]) + text4 = self.set_up_dict(character["Skills"]) + text5 = "" + text6 = "" + text7 = "" + text8 = "" + + if bool(character["Talents"]): + text5 = "**Talents**: "+", ".join(list(character["Talents"]))+"\n\n" + + if bool(character["Force-powers"]): + text6 = "**Force Powers**: "+", ".join(list(character["Force-powers"]))+"\n\n" + + text7 = "**Equipment**: "+", ".join(character["Equipment"])+"\n**Credits**: "+str(character["Credits"])+"\n**Weapons**: "+", ".join(list(character["Weapons"]))+"\n"+divider + + if bool(character["Obligations"]): + text8 = "**Obligations**: "+",".join(list(character["Obligations"])) + + return name, text1+text2+"\n\n"+text3+divider+text4+"\n"+divider+text5+text6+text7+text8 + + def char_data(self,user : str,cmd : str): + user_character = self.bot.database["starwars characters"].find_one({"_id":user}) + + key = string.capwords(cmd.split(" ")[0]) + cmd = cmd[len(key):] + if cmd == " ": + cmd = "" + elif cmd != "": + while cmd[0] == " ": + cmd = cmd[1:] + if cmd == "": + break + + self.bot.log("Looking for "+self.bot.database_funcs.get_name(user)+"'s character") + if user_character != None: + self.bot.log("Found it! Looking for "+key+" in the data") + if key in user_character: + self.bot.log("Found it!") + if type(user_character[key]) is dict: + self.bot.log("It's a dictionary!") + if cmd == "": + self.bot.log("Retrieving data") + if key == "Weapons": + if bool(user_character[key]): + self.bot.log("Returning a list of weapons") + return ", ".join(list(user_character[key])) + else: + self.bot.log("The character doesn't have any weapons. Which is probably for the best. Like, who just walks around with weapons?") + return "There doesn't seem to be anything there..." else: - await ctx.send(self.replace_with_spaces(str(self.char_data(user,cmd)))) + return self.set_up_dict(user_character[key]) + elif cmd[0] == "+": + self.bot.log("Gonna add something!!!") + try: + cmd = cmd[1:] + while cmd[0] == ' ': + cmd = cmd[1:] + except: + self.bot.log("Nope. That didn't happen") + return "Can't do that" - if return_embed: - embed = discord.Embed(title = title, description = text, colour=0xDEADBF) - await ctx.send(embed = embed) + if (key == "Talents" or key == "Force-powers") and "," in cmd: + cmd = cmd.split(",") + while cmd[1][0] == " ": + cmd[1] = cmd[1][1:] + self.bot.log("Adding "+cmd[0]+" to "+key) + self.bot.database["starwars characters"].update_one({"_id":user}, + {"$set": {key+"."+cmd[0] : cmd[1]}}) + return cmd[0]+" added to "+key+" for " + user_character["Name"] + + elif key == "Obligations" and "," in cmd: + cmd = cmd.split(",") + while cmd[1][0] == " ": + cmd[1] = cmd[1][1:] + self.bot.log("Adding "+cmd[0]+" to "+key) + try: + self.bot.database["starwars characters"].update_one({"_id":user}, + {"$set": {key+"."+cmd[0] : int(cmd[1])}}) + except: + self.bot.log("Fucked that up") + return "Wrong data type" + return cmd[0]+" added to "+key+" for " + user_character["Name"] + + elif key == "Weapons": + with open("gwendolyn/resources/star_wars/starwarstemplates.json", "r") as file_pointer: + templates = json.load(file_pointer) + new_weapon = templates["Weapon"] + self.bot.log("Adding "+cmd+" to "+key) + self.bot.database["starwars characters"].update_one({"_id":user}, + {"$set": {key+"."+cmd : new_weapon}}) + + return cmd+" added to weapons for " + user_character["Name"] + + else: + self.bot.log("That's not happening") + return "Can't add that" + + elif cmd[0] == "-": + self.bot.log("Gonna subtract/remove something") + try: + cmd = cmd[1:] + while cmd[0] == ' ': + cmd = cmd[1:] + except: + self.bot.log("AAAAAAAAAAAA ") + return "Can't do that " + + if key == "Talents" or key == "Force-powers" or key == "Weapons" or key == "Obligations": + self.bot.log("Trying to remove "+cmd+" from "+key) + if cmd in user_character[key]: + self.bot.database["starwars characters"].update_one({"_id":user}, + {"$unset": {cmd}}) + self.bot.log("I did that") + return cmd+" removed from "+key+" from "+user_character["Name"] + else: + self.bot.log("Welp. I fucked that up") + return "Can't remove that" + else: + self.bot.log("Urgh!") + return "Can't remove that" + + else: + self.bot.log("Looking up "+cmd+" in "+key) + if key == "Talents" or key == "Force-powers": + new_key = cmd + new_cmd = "" + else: + new_key = string.capwords(cmd.split(" ")[0]) + new_cmd = cmd[len(new_key):] + + look_up_result = self.look_up(user_character[key],new_key,new_cmd) + + if type(look_up_result) is dict: + self.bot.database["starwars characters"].update_one({"_id":user}, + {"$set": {key : look_up_result}}) + return "Changed " + user_character["Name"] + "'s " + key + else: + return look_up_result + else: + if cmd == "": + self.bot.log("Retrieving data") + if type(user_character[key]) is list: + return key+":\n"+", ".join(user_character[key]) + else: + return user_character[key] + elif cmd[0] == '+': + self.bot.log("Adding") + try: + cmd = cmd[1:] + while cmd[0] == ' ': + cmd = cmd[1:] + except: + self.bot.log("Error message") + return "Can't do that" + + if type(user_character[key]) is int: + try: + self.bot.log("Adding "+cmd+" to "+key) + self.bot.database["starwars characters"].update_one({"_id":user}, + {"$inc": {key : int(cmd)}}) + return "Added " + cmd + " to " + user_character["Name"] + "'s " + key + except: + self.bot.log("BITCH SANDWICH") + return "Can't add that" + elif type(user_character[key]) is list: + try: + self.bot.log("Adding "+cmd+" to "+key) + self.bot.database["starwars characters"].update_one({"_id":user}, + {"$push": {key : cmd}}) + return "Added " + cmd + " to " + user_character["Name"] + "'s " + key + except: + self.bot.log("tstststststs") + return "Can't add that" + else: + self.bot.log("Help") + return "Can't add that" + elif cmd[0] == '-': + self.bot.log("Removing/subtracting") + try: + cmd = cmd[1:] + while cmd[0] == ' ': + cmd = cmd[1:] + except: + self.bot.log("lalalala ") + return "Can't do that" + + if type(user_character[key]) is int: + try: + self.bot.log("Subtracting "+cmd+" from "+key) + self.bot.database["starwars characters"].update_one({"_id":user}, + {"$inc": {key : -int(cmd)}}) + return "Subtracted " + cmd + " from " + user_character["Name"] + "'s " + key + except: + self.bot.log("Tried it. Didn't want to") + return "Can't remove that" + elif type(user_character[key]) is list: + try: + self.bot.log("removing "+cmd+" from "+key) + try: + self.bot.database["starwars characters"].update_one({"_id":user}, + {"$pull": {key : cmd}}) + except: + self.bot.log("They can only remove stuff that's actually in the list") + return "Not in list" + return "Removed " + cmd + " from " + user_character["Name"] + "'s " + key + except: + self.bot.log("nah") + return "Can't remove that" + else: + self.bot.log("nyope") + return "Can't remove that" + else: + self.bot.log("Changing "+key+" to "+cmd) + if type(user_character[key]) is int: + try: + self.bot.database["starwars characters"].update_one({"_id":user}, + {"$set": {key : int(cmd)}}) + except: + self.bot.log("I don't wanna tho") + return "Can't do that" + elif type(user_character[key]) is str: + self.bot.database["starwars characters"].update_one({"_id":user}, + {"$set": {key : cmd}}) + else: + self.bot.log("I don't wanna tho") + return "Can't do that" + return "Changed " + user_character["Name"] + "'s " + key +" to " + cmd + else: + self.bot.log(key+" isn't in there") + return "Couldn't find that data. Are you sure you spelled it correctly?" + else: + self.bot.log(user+" doesn't have a character") + return "You don't have a character. You can make one with /starwarscharacter" + + def replace_spaces(self,cmd : str): + with_spaces = ["Specialization Trees","Wound Threshold","Strain Threshold","Defense - Ranged","Defense - Melee","Force Rating","Core Worlds","Outer Rim","Piloting - Planetary","Piloting - Space","Ranged - Heavy","Ranged - Light","Lightsaber Characteristic","Critical Injuries","Force Powers"] + without_spaces = ["Specialization-trees","Wound-threshold","Strain-threshold","Defense-ranged","Defense-melee","Force-rating","Core-worlds","Outer-rim","Piloting-planetary","Piloting-space","Ranged-heavy","Ranged-light","Lightsaber-characteristic","Critical-injuries","Force-powers"] + + for i, value in enumerate(without_spaces): + cmd = cmd.replace(with_spaces[i],value) + + return cmd + + def replace_with_spaces(self,cmd : str): + with_spaces = ["Specialization Trees","Wound Threshold","Strain Threshold","Defense - Ranged","Defense - Melee","Force Rating","Core Worlds","Outer Rim","Piloting - Planetary","Piloting - Space","Ranged - Heavy","Ranged - light","Lightsaber Characteristic","Critical Injuries","Force Powers"] + without_spaces = ["Specialization-trees","Wound-threshold","Strain-threshold","Defense-ranged","Defense-melee","Force-rating","Core-worlds","Outer-rim","Piloting-planetary","Piloting-space","Ranged-heavy","Ranged-light","Lightsaber-characteristic","Critical-injuries","Force-powers"] + + for i, value in enumerate(without_spaces): + cmd = cmd.replace(value,with_spaces[i]) + + return cmd + + async def parse_char(self, ctx, parameters : str): + user = f"#{ctx.author.id}" + cmd = string.capwords(parameters.replace("+","+ ").replace("-","- ").replace(",",", ")) + return_embed = False + + cmd = self.replace_spaces(cmd) + + user_character = self.bot.database["starwars characters"].find_one({"_id":user}) + + if cmd == " ": + cmd = "" + elif cmd != "": + while cmd[0] == " ": + cmd = cmd[1:] + if cmd == "": + break + + + if cmd == "": + if user_character != None: + title, text = self.character_sheet(user_character) + text = self.replace_with_spaces(text) + return_embed = True + else: + self.bot.log("Makin' a character for "+self.bot.database_funcs.get_name(user)) + with open("gwendolyn/resources/star_wars/starwarstemplates.json", "r") as file_pointer: + templates = json.load(file_pointer) + new_char = templates["Character"] + new_char["_id"] = user + self.bot.database["starwars characters"].insert_one(new_char) + 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.get_name(user)+"'s character") + 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.replace_with_spaces(str(self.char_data(user,cmd)))) + + if return_embed: + embed = discord.Embed(title = title, description = text, colour=0xDEADBF) + await ctx.send(embed = embed) - def lightsaber_char(self,user : str): - user_character = self.bot.database["starwars characters"].find_one({"_id":user}) + def lightsaber_char(self,user : str): + user_character = self.bot.database["starwars characters"].find_one({"_id":user}) - if user_character != None: - return user_character["Lightsaber-characteristic"] + if user_character != None: + return user_character["Lightsaber-characteristic"] - def user_has_char(self,user : str): - user_character = self.bot.database["starwars characters"].find_one({"_id":user}) + def user_has_char(self,user : str): + user_character = self.bot.database["starwars characters"].find_one({"_id":user}) - return user_character != None + return user_character != None diff --git a/gwendolyn_old/funcs/star_wars_funcs/star_wars_destiny.py b/gwendolyn_old/funcs/star_wars_funcs/star_wars_destiny.py index 09979d5..dfcdb4a 100644 --- a/gwendolyn_old/funcs/star_wars_funcs/star_wars_destiny.py +++ b/gwendolyn_old/funcs/star_wars_funcs/star_wars_destiny.py @@ -1,72 +1,72 @@ class StarWarsDestiny(): - def __init__(self, bot): - self.bot = bot + def __init__(self, bot): + self.bot = bot - def destiny_new(self, num : int): - self.bot.log("Creating a new destiny pool with "+str(num)+" players") - roll, dice_results = self.bot.star_wars.roll.roll(0,0,0,0,0,0,num) - roll = "".join(sorted(roll)) + def destiny_new(self, num : int): + self.bot.log("Creating a new destiny pool with "+str(num)+" players") + roll, dice_results = self.bot.star_wars.roll.roll(0,0,0,0,0,0,num) + roll = "".join(sorted(roll)) + with open("gwendolyn/resources/star_wars/destinyPoints.txt","wt") as file_pointer: + file_pointer.write(roll) + + return "Rolled for Destiny Points and got:\n"+self.bot.star_wars.roll.diceResultToEmoji(dice_results)+"\n"+self.bot.star_wars.roll.resultToEmoji(roll) + + def destiny_use(self, user : str): + with open("gwendolyn/resources/star_wars/destinyPoints.txt","rt") as file_pointer: + points = file_pointer.read() + + if user == "Nikolaj": + self.bot.log("Trying to use a dark side destiny point") + if 'B' in points: + points = points.replace("B","L",1) + points = "".join(sorted(points)) with open("gwendolyn/resources/star_wars/destinyPoints.txt","wt") as file_pointer: - file_pointer.write(roll) - - return "Rolled for Destiny Points and got:\n"+self.bot.star_wars.roll.diceResultToEmoji(dice_results)+"\n"+self.bot.star_wars.roll.resultToEmoji(roll) - - def destiny_use(self, user : str): - with open("gwendolyn/resources/star_wars/destinyPoints.txt","rt") as file_pointer: - points = file_pointer.read() - - if user == "Nikolaj": - self.bot.log("Trying to use a dark side destiny point") - if 'B' in points: - points = points.replace("B","L",1) - points = "".join(sorted(points)) - with open("gwendolyn/resources/star_wars/destinyPoints.txt","wt") as file_pointer: - file_pointer.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) - else: - self.bot.log("There were no dark side destiny points") - return "No dark side destiny points" - else: - self.bot.log("Trying to use a light side destiny point") - if 'L' in points: - points = points.replace("L","B",1) - points = "".join(sorted(points)) - with open("gwendolyn/resources/star_wars/destinyPoints.txt","wt") as file_pointer: - file_pointer.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) - else: - self.bot.log("There were no dark side destiny points") - return "No light side destiny points" - - async def parse_destiny(self, ctx, cmd : str): - user = f"#{ctx.author.id}" - if cmd != "": - while cmd[0] == ' ': - cmd = cmd[1:] - if cmd == "": - break + file_pointer.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) + else: + self.bot.log("There were no dark side destiny points") + return "No dark side destiny points" + else: + self.bot.log("Trying to use a light side destiny point") + if 'L' in points: + points = points.replace("L","B",1) + points = "".join(sorted(points)) + with open("gwendolyn/resources/star_wars/destinyPoints.txt","wt") as file_pointer: + file_pointer.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) + else: + self.bot.log("There were no dark side destiny points") + return "No light side destiny points" + async def parse_destiny(self, ctx, cmd : str): + user = f"#{ctx.author.id}" + if cmd != "": + while cmd[0] == ' ': + cmd = cmd[1:] if cmd == "": - self.bot.log("Retrieving destiny pool info") - with open("gwendolyn/resources/star_wars/destinyPoints.txt","rt") as file_pointer: - send_message = self.bot.star_wars.roll.resultToEmoji(file_pointer.read()) - else: - commands = cmd.upper().split(" ") - if commands[0] == "N": - if len(commands) > 1: - send_message = self.destiny_new(int(commands[1])) - else: - send_message = "You need to give an amount of players" - elif commands[0] == "U": - send_message = self.destiny_use(user) - else: - send_message = "I didn't quite understand that" + break - message_list = send_message.split("\n") - await ctx.send(message_list[0]) - if len(message_list) > 1: - for message_item in message_list[1:]: - await ctx.channel.send(message_item) + if cmd == "": + self.bot.log("Retrieving destiny pool info") + with open("gwendolyn/resources/star_wars/destinyPoints.txt","rt") as file_pointer: + send_message = self.bot.star_wars.roll.resultToEmoji(file_pointer.read()) + else: + commands = cmd.upper().split(" ") + if commands[0] == "N": + if len(commands) > 1: + send_message = self.destiny_new(int(commands[1])) + else: + send_message = "You need to give an amount of players" + elif commands[0] == "U": + send_message = self.destiny_use(user) + else: + send_message = "I didn't quite understand that" + + message_list = send_message.split("\n") + await ctx.send(message_list[0]) + if len(message_list) > 1: + for message_item in message_list[1:]: + await ctx.channel.send(message_item) diff --git a/gwendolyn_old/funcs/star_wars_funcs/star_wars_roll.py b/gwendolyn_old/funcs/star_wars_funcs/star_wars_roll.py index c110e5c..a36579f 100644 --- a/gwendolyn_old/funcs/star_wars_funcs/star_wars_roll.py +++ b/gwendolyn_old/funcs/star_wars_funcs/star_wars_roll.py @@ -4,388 +4,388 @@ import string import json with open("gwendolyn/resources/star_wars/starwarsskills.json", "r") as f: - skill_data = json.load(f) + skill_data = json.load(f) class StarWarsRoll(): - def __init__(self, bot): - self.bot = bot + def __init__(self, bot): + self.bot = bot - # Rolls the specified dice - def roll(self, abi : int = 1, prof : int = 0, dif : int = 3, cha : int = 0, boo : int = 0, setb : int = 0, force : int = 0): - result = "" - dice_result = [] - for _ in range(abi): - choice = random.choice(["","S","S","SS","A","A","SA","AA"]) - result += choice - dice_result.append("abi"+choice) + # Rolls the specified dice + def roll(self, abi : int = 1, prof : int = 0, dif : int = 3, cha : int = 0, boo : int = 0, setb : int = 0, force : int = 0): + result = "" + dice_result = [] + for _ in range(abi): + choice = random.choice(["","S","S","SS","A","A","SA","AA"]) + result += choice + dice_result.append("abi"+choice) - for _ in range(prof): - choice = random.choice(["","S","S","SS","SS","A","SA","SA","SA","AA","AA","R"]) - result += choice - dice_result.append("prof"+choice) + for _ in range(prof): + choice = random.choice(["","S","S","SS","SS","A","SA","SA","SA","AA","AA","R"]) + result += choice + dice_result.append("prof"+choice) - for _ in range(dif): - choice = random.choice(["","F","FF","H","H","H","HH","FH"]) - result += choice - dice_result.append("dif"+choice) + for _ in range(dif): + choice = random.choice(["","F","FF","H","H","H","HH","FH"]) + result += choice + dice_result.append("dif"+choice) - for _ in range(cha): - choice = random.choice(["","F","F","FF","FF","H","H","FH","FH","HH","HH","D"]) - result += choice - dice_result.append("cha"+choice) + for _ in range(cha): + choice = random.choice(["","F","F","FF","FF","H","H","FH","FH","HH","HH","D"]) + result += choice + dice_result.append("cha"+choice) - for _ in range(boo): - choice = random.choice(["","","S","SA","AA","A"]) - result += choice - dice_result.append("boo"+choice) + for _ in range(boo): + choice = random.choice(["","","S","SA","AA","A"]) + result += choice + dice_result.append("boo"+choice) - for _ in range(setb): - choice = random.choice(["","","F","F","H","H"]) - result += choice - dice_result.append("setb"+choice) + for _ in range(setb): + choice = random.choice(["","","F","F","H","H"]) + result += choice + dice_result.append("setb"+choice) - for _ in range (force): - choice = random.choice(["B","B","B","B","B","B","BB","L","L","LL","LL","LL"]) - result += choice - dice_result.append("force"+choice) + for _ in range (force): + choice = random.choice(["B","B","B","B","B","B","BB","L","L","LL","LL","LL"]) + result += choice + dice_result.append("force"+choice) - return result, dice_result + return result, dice_result - # Lets dice cancel each other out - def simplify(self, result : str): - self.bot.log("Simplifying "+result) - simp = "" - success = (result.count('S') + result.count('R')) - (result.count('F') + result.count('D')) - advantage = result.count('A') - result.count('H') - result = re.sub("S|A|F|H","",result) + # Lets dice cancel each other out + def simplify(self, result : str): + self.bot.log("Simplifying "+result) + simp = "" + success = (result.count('S') + result.count('R')) - (result.count('F') + result.count('D')) + advantage = result.count('A') - result.count('H') + result = re.sub("S|A|F|H","",result) - if success > 0: - for _ in range(success): - simp += "S" - elif success < 0: - for _ in range(abs(success)): - simp += "F" + if success > 0: + for _ in range(success): + simp += "S" + elif success < 0: + for _ in range(abs(success)): + simp += "F" - if advantage > 0: - for _ in range(advantage): - simp += "A" - elif advantage < 0: - for _ in range(abs(advantage)): - simp += "H" + if advantage > 0: + for _ in range(advantage): + simp += "A" + elif advantage < 0: + for _ in range(abs(advantage)): + simp += "H" - simp += result + simp += result - return simp + return simp - # Returns emoji that symbolize the dice results - def dice_result_to_emoji(self, dice_results : list): - emoji = "" - for result in dice_results: - if result == "abiA": - emoji += "<:abil1a:695267684476125264> " - elif result == "abiSA": - emoji += "<:abil1a1s:695267684484513842> " - elif result == "abiS": - emoji += "<:abil1s:695267684514005013> " - elif result == "abiAA": - emoji += "<:abil2a:695267684547428352> " - elif result == "abiSS": - emoji += "<:abil2s:695267684761206914> " - elif result == "abi": - emoji += "<:abilbla:695267684660674602> " + # Returns emoji that symbolize the dice results + def dice_result_to_emoji(self, dice_results : list): + emoji = "" + for result in dice_results: + if result == "abiA": + emoji += "<:abil1a:695267684476125264> " + elif result == "abiSA": + emoji += "<:abil1a1s:695267684484513842> " + elif result == "abiS": + emoji += "<:abil1s:695267684514005013> " + elif result == "abiAA": + emoji += "<:abil2a:695267684547428352> " + elif result == "abiSS": + emoji += "<:abil2s:695267684761206914> " + elif result == "abi": + emoji += "<:abilbla:695267684660674602> " - elif result == "profA": - emoji += "<:prof1a:695267685361123338> " - elif result == "profSA": - emoji += "<:prof1a1s:695267685067653140> " - elif result == "profR": - emoji += "<:prof1r:695267685067522088> " - elif result == "profS": - emoji += "<:prof1s:695267684899881012> " - elif result == "profAA": - emoji += "<:prof2a:695267684996218982> " - elif result == "profSS": - emoji += "<:prof2s:695267684878647327> " - elif result == "prof": - emoji += "<:profbla:695267684698292235> " + elif result == "profA": + emoji += "<:prof1a:695267685361123338> " + elif result == "profSA": + emoji += "<:prof1a1s:695267685067653140> " + elif result == "profR": + emoji += "<:prof1r:695267685067522088> " + elif result == "profS": + emoji += "<:prof1s:695267684899881012> " + elif result == "profAA": + emoji += "<:prof2a:695267684996218982> " + elif result == "profSS": + emoji += "<:prof2s:695267684878647327> " + elif result == "prof": + emoji += "<:profbla:695267684698292235> " - elif result == "difF": - emoji += "<:dif1f:695267684924915804> " - elif result == "difH": - emoji += "<:dif1h:695267684908138506> " - elif result == "difFH": - emoji += "<:dif1h1f:695267684908269678> " - elif result == "difFF": - emoji += "<:dif2f:695267684924784680> " - elif result == "difHH": - emoji += "<:dif2h:695267685071585340> " - elif result == "dif": - emoji += "<:difbla:695267685000544276> " + elif result == "difF": + emoji += "<:dif1f:695267684924915804> " + elif result == "difH": + emoji += "<:dif1h:695267684908138506> " + elif result == "difFH": + emoji += "<:dif1h1f:695267684908269678> " + elif result == "difFF": + emoji += "<:dif2f:695267684924784680> " + elif result == "difHH": + emoji += "<:dif2h:695267685071585340> " + elif result == "dif": + emoji += "<:difbla:695267685000544276> " - elif result == "chaD": - emoji += "<:cha1d:695267684962533447> " - elif result == "chaF": - emoji += "<:cha1f:695267684601954346> " - elif result == "chaH": - emoji += "<:cha1h:695267685046681620> " - elif result == "chaFH": - emoji += "<:cha1h1f:695267685063327784> " - elif result == "chaFF": - emoji += "<:cha2f:695267684832641097> " - elif result == "chaHH": - emoji += "<:cha2h:695267684631183381> " - elif result == "cha": - emoji += "<:chabla:695267684895686787> " + elif result == "chaD": + emoji += "<:cha1d:695267684962533447> " + elif result == "chaF": + emoji += "<:cha1f:695267684601954346> " + elif result == "chaH": + emoji += "<:cha1h:695267685046681620> " + elif result == "chaFH": + emoji += "<:cha1h1f:695267685063327784> " + elif result == "chaFF": + emoji += "<:cha2f:695267684832641097> " + elif result == "chaHH": + emoji += "<:cha2h:695267684631183381> " + elif result == "cha": + emoji += "<:chabla:695267684895686787> " - elif result == "booA": - emoji += "<:boo1a:695267684975116329> " - elif result == "booSA": - emoji += "<:boo1a1s:695267684970922024> " - elif result == "booS": - emoji += "<:boo1s:695267684979441714> " - elif result == "booAA": - emoji += "<:boo2a:695267685100945488> " - elif result == "boo": - emoji += "<:boobla:695267684757012550> " + elif result == "booA": + emoji += "<:boo1a:695267684975116329> " + elif result == "booSA": + emoji += "<:boo1a1s:695267684970922024> " + elif result == "booS": + emoji += "<:boo1s:695267684979441714> " + elif result == "booAA": + emoji += "<:boo2a:695267685100945488> " + elif result == "boo": + emoji += "<:boobla:695267684757012550> " - elif result == "setbF": - emoji += "<:set1f:695267685054939197> " - elif result == "setbH": - emoji += "<:set1h:695267685147082802> " - elif result == "setb": - emoji += "<:setbla:695267685151408169> " + elif result == "setbF": + emoji += "<:set1f:695267685054939197> " + elif result == "setbH": + emoji += "<:set1h:695267685147082802> " + elif result == "setb": + emoji += "<:setbla:695267685151408169> " - elif result == "forceB": - emoji += "<:for1b:695267684593434677> " - elif result == "forceL": - emoji += "<:for1l:695267684606148640> " - elif result == "forceBB": - emoji += "<:for2b:695267684903944303> " - elif result == "forceLL": - emoji += "<:for2l:695267684992024626> " + elif result == "forceB": + emoji += "<:for1b:695267684593434677> " + elif result == "forceL": + emoji += "<:for1l:695267684606148640> " + elif result == "forceBB": + emoji += "<:for2b:695267684903944303> " + elif result == "forceLL": + emoji += "<:for2l:695267684992024626> " - return emoji + return emoji - # Returns emoji that symbolize the results of the dice rolls - def result_to_emoji(self, result : str): - emoji = "" - for char in result: - if char == 'S': - emoji += "<:success:826026925280854026> " - elif char == 'A': - emoji += "<:advantage:826026925515604009> " - elif char == 'R': - emoji += "<:triumph:826026925319127070> " - elif char == 'F': - emoji += "<:failure:826026925288980511> " - elif char == 'H': - emoji += "<:threat:826026925280985108> " - elif char == 'D': - emoji += "<:despair:826026925272203294> " - elif char == 'L': - emoji += "<:light:826026925059211295>" - elif char == 'B': - emoji += "<:dark:826026925289373717>" + # Returns emoji that symbolize the results of the dice rolls + def result_to_emoji(self, result : str): + emoji = "" + for char in result: + if char == 'S': + emoji += "<:success:826026925280854026> " + elif char == 'A': + emoji += "<:advantage:826026925515604009> " + elif char == 'R': + emoji += "<:triumph:826026925319127070> " + elif char == 'F': + emoji += "<:failure:826026925288980511> " + elif char == 'H': + emoji += "<:threat:826026925280985108> " + elif char == 'D': + emoji += "<:despair:826026925272203294> " + elif char == 'L': + emoji += "<:light:826026925059211295>" + elif char == 'B': + emoji += "<:dark:826026925289373717>" - return emoji + return emoji - # Converts emoji into letters - def emoji_to_result(self, emoji : str): - result = "" - for char in emoji: - if char == "<:light:691010089905029171>": - emoji += 'L' - if char == "<:dark:691010101901000852>": - emoji += 'B' + # Converts emoji into letters + def emoji_to_result(self, emoji : str): + result = "" + for char in emoji: + if char == "<:light:691010089905029171>": + emoji += 'L' + if char == "<:dark:691010101901000852>": + emoji += 'B' - return result + return result - # Returns emoji that symbolize the dice - def dice_to_emoji(self, dice : list): - emoji = "" + # Returns emoji that symbolize the dice + def dice_to_emoji(self, dice : list): + emoji = "" - for _ in range(dice[0]): - emoji += "<:ability:690974213397282826> " - for _ in range(dice[1]): - emoji += "<:proficiency:690973435354153071> " - for _ in range(dice[2]): - emoji += "<:difficulty:690973992470708296> " - for _ in range(dice[3]): - emoji += "<:challenge:690973419906400306> " - for _ in range(dice[4]): - emoji += "<:boost:690972178216386561> " - for _ in range(dice[5]): - emoji += "<:setback:690972157890658415> " - for _ in range(dice[6]): - emoji += "<:force:690973451883774013> " + for _ in range(dice[0]): + emoji += "<:ability:690974213397282826> " + for _ in range(dice[1]): + emoji += "<:proficiency:690973435354153071> " + for _ in range(dice[2]): + emoji += "<:difficulty:690973992470708296> " + for _ in range(dice[3]): + emoji += "<:challenge:690973419906400306> " + for _ in range(dice[4]): + emoji += "<:boost:690972178216386561> " + for _ in range(dice[5]): + emoji += "<:setback:690972157890658415> " + for _ in range(dice[6]): + emoji += "<:force:690973451883774013> " - return emoji + return emoji - # Rolls for obligation - def obligation_roll(self): - self.bot.log("Rolling for obligation") - data = self.bot.database["starwarscharacters"] + # Rolls for obligation + def obligation_roll(self): + self.bot.log("Rolling for obligation") + data = self.bot.database["starwarscharacters"] - table = [] + table = [] - for character in data: - for obligation in data[character]["Obligations"]: - for _ in range(data[character]["Obligations"][obligation]): - table.append(data[character]["Name"]+", "+obligation) + for character in data: + for obligation in data[character]["Obligations"]: + for _ in range(data[character]["Obligations"][obligation]): + table.append(data[character]["Name"]+", "+obligation) - while len(table) < 100: - table.append("Nothing") + while len(table) < 100: + table.append("Nothing") - return random.choice(table) + return random.choice(table) - # Rolls for critical injury - async def crit_roll(self, ctx, addington : int): - difficulty_die = "<:difficulty:690973992470708296>" - setback_die = "<:setback:690972157890658415>" - boost_die = "<:boost:690972178216386561>" - roll = random.randint(1,100) + addington - injuries = [ - "**Minor nick**: The target suffers 1 strain, "+difficulty_die] * 5 + [ - "**Slowed down**: The target can only act during the last allied initiative slot this turn, "+difficulty_die] * 5 + [ - "**Sudden Jolt**: The target drops whatever is in hand, "+difficulty_die] * 5 + [ - "**Distracted**: The target cannot perform a Free maneuver during his next turn, "+difficulty_die] * 5 + [ - "**Off-Balance**: The target adds "+setback_die+" to his next skill check, "+difficulty_die] * 5 + [ - "**Discouraging Wound**: Flip one light side Destiny point to a dark side Destiny point (reverse if NPC), "+difficulty_die] * 5 + [ - "**Stunned**: The target is staggered until the end of his next turn, "+difficulty_die] * 5 + [ - "**Stinger**: Increase the difficulty of next check by one, "+difficulty_die] * 5 + [ - "**Bowled Over**: The target is knocked prone and suffers 1 strain, "+difficulty_die+difficulty_die] * 5 + [ - "**Head Ringer**: The target increases the difficulty of all Intellect and Cunning checks by one until the end of the encounter, "+difficulty_die+difficulty_die] * 5 + [ - "**Fearsome Wound**: The target increases the difficulty of all Presence and Willpower checks by one until the end of the encounter, "+difficulty_die+difficulty_die] * 5 + [ - "**Agonizing Wound**: The target increases the difficulty of all Brawn and Agility checks by one until the end of the encounter, "+difficulty_die+difficulty_die] * 5 + [ - "**Slightly Dazed**: The target is disoriented until the end of the encounter, "+difficulty_die+difficulty_die] * 5 + [ - "**Scattered Senses**: The target removes all "+boost_die+" from skill checks until the end of the encounter, "+difficulty_die+difficulty_die] * 5 + [ - "**Hamstrung**: The target loses his free maneuver until the end of the encounter, "+difficulty_die+difficulty_die] * 5 + [ - "**Overpowered**: The target leaves himself open, and the attacker may immediately attempt another free attack agains him, using the exact same pool as the original, "+difficulty_die+difficulty_die] * 5 + [ - "**Winded**: Until the end of the encounter, the target cannot voluntarily suffer strain to activate any abilities or gain additional maneuvers, "+difficulty_die+difficulty_die] * 5 + [ - "**Compromised**: Incerase difficulty of all skill checks by one until the end of the encounter, "+difficulty_die+difficulty_die] * 5 + [ - "**At the brink**: The target suffers 1 strain each time he performs an action, "+difficulty_die+difficulty_die+difficulty_die] * 5 + [ - "**Crippled**: One of the target's limbs (selected by the GM) is crippled until healed or replaced. Increase difficulty of all checks that require use of that limb by one, "+difficulty_die+difficulty_die+difficulty_die] * 5 + [ - "**Maimed**: One of the target's limbs (selected by the GM) is permanently lost. Unless the target has a cybernetic replacement, the target cannot perform actions that would require the use of that limb. All other actions gain "+setback_die+", "+difficulty_die+difficulty_die+difficulty_die] * 5 + [ - "HI"] * 5 + [ - "**Temporarily Lame**: Until this critical injury is healed, the target cannot perform more than one maneuver during his turn, "+difficulty_die+difficulty_die+difficulty_die] * 5 + [ - "**Blinded**: The target can no longer see. Upgrade the difficulty of all checks twice. Upgrade the difficulty of perception checks three times, "+difficulty_die+difficulty_die+difficulty_die] * 5 + [ - "**Knocked Senseless**: The target is staggered for the remainder of the encounter, "+difficulty_die+difficulty_die+difficulty_die] * 5 + [ - "GI"] * 5 + [ - "**Bleeding Out**: Every round, the target suffers 1 wound and 1 strain at the beginning of his turn. For every five wounds he suffers beyond his wound threshold, he suffers one additional critical injury. (If he suffers this one again, roll again), "+difficulty_die+difficulty_die+difficulty_die+difficulty_die] * 10 + [ - "**The End is Nigh**: The target will die after the last initiative slot during the next round, "+difficulty_die+difficulty_die+difficulty_die+difficulty_die] * 10 + [ - "**Dead**: U B Dead :("] + # Rolls for critical injury + async def crit_roll(self, ctx, addington : int): + difficulty_die = "<:difficulty:690973992470708296>" + setback_die = "<:setback:690972157890658415>" + boost_die = "<:boost:690972178216386561>" + roll = random.randint(1,100) + addington + injuries = [ + "**Minor nick**: The target suffers 1 strain, "+difficulty_die] * 5 + [ + "**Slowed down**: The target can only act during the last allied initiative slot this turn, "+difficulty_die] * 5 + [ + "**Sudden Jolt**: The target drops whatever is in hand, "+difficulty_die] * 5 + [ + "**Distracted**: The target cannot perform a Free maneuver during his next turn, "+difficulty_die] * 5 + [ + "**Off-Balance**: The target adds "+setback_die+" to his next skill check, "+difficulty_die] * 5 + [ + "**Discouraging Wound**: Flip one light side Destiny point to a dark side Destiny point (reverse if NPC), "+difficulty_die] * 5 + [ + "**Stunned**: The target is staggered until the end of his next turn, "+difficulty_die] * 5 + [ + "**Stinger**: Increase the difficulty of next check by one, "+difficulty_die] * 5 + [ + "**Bowled Over**: The target is knocked prone and suffers 1 strain, "+difficulty_die+difficulty_die] * 5 + [ + "**Head Ringer**: The target increases the difficulty of all Intellect and Cunning checks by one until the end of the encounter, "+difficulty_die+difficulty_die] * 5 + [ + "**Fearsome Wound**: The target increases the difficulty of all Presence and Willpower checks by one until the end of the encounter, "+difficulty_die+difficulty_die] * 5 + [ + "**Agonizing Wound**: The target increases the difficulty of all Brawn and Agility checks by one until the end of the encounter, "+difficulty_die+difficulty_die] * 5 + [ + "**Slightly Dazed**: The target is disoriented until the end of the encounter, "+difficulty_die+difficulty_die] * 5 + [ + "**Scattered Senses**: The target removes all "+boost_die+" from skill checks until the end of the encounter, "+difficulty_die+difficulty_die] * 5 + [ + "**Hamstrung**: The target loses his free maneuver until the end of the encounter, "+difficulty_die+difficulty_die] * 5 + [ + "**Overpowered**: The target leaves himself open, and the attacker may immediately attempt another free attack agains him, using the exact same pool as the original, "+difficulty_die+difficulty_die] * 5 + [ + "**Winded**: Until the end of the encounter, the target cannot voluntarily suffer strain to activate any abilities or gain additional maneuvers, "+difficulty_die+difficulty_die] * 5 + [ + "**Compromised**: Incerase difficulty of all skill checks by one until the end of the encounter, "+difficulty_die+difficulty_die] * 5 + [ + "**At the brink**: The target suffers 1 strain each time he performs an action, "+difficulty_die+difficulty_die+difficulty_die] * 5 + [ + "**Crippled**: One of the target's limbs (selected by the GM) is crippled until healed or replaced. Increase difficulty of all checks that require use of that limb by one, "+difficulty_die+difficulty_die+difficulty_die] * 5 + [ + "**Maimed**: One of the target's limbs (selected by the GM) is permanently lost. Unless the target has a cybernetic replacement, the target cannot perform actions that would require the use of that limb. All other actions gain "+setback_die+", "+difficulty_die+difficulty_die+difficulty_die] * 5 + [ + "HI"] * 5 + [ + "**Temporarily Lame**: Until this critical injury is healed, the target cannot perform more than one maneuver during his turn, "+difficulty_die+difficulty_die+difficulty_die] * 5 + [ + "**Blinded**: The target can no longer see. Upgrade the difficulty of all checks twice. Upgrade the difficulty of perception checks three times, "+difficulty_die+difficulty_die+difficulty_die] * 5 + [ + "**Knocked Senseless**: The target is staggered for the remainder of the encounter, "+difficulty_die+difficulty_die+difficulty_die] * 5 + [ + "GI"] * 5 + [ + "**Bleeding Out**: Every round, the target suffers 1 wound and 1 strain at the beginning of his turn. For every five wounds he suffers beyond his wound threshold, he suffers one additional critical injury. (If he suffers this one again, roll again), "+difficulty_die+difficulty_die+difficulty_die+difficulty_die] * 10 + [ + "**The End is Nigh**: The target will die after the last initiative slot during the next round, "+difficulty_die+difficulty_die+difficulty_die+difficulty_die] * 10 + [ + "**Dead**: U B Dead :("] - if roll >= len(injuries): - results = injuries[-1] + if roll >= len(injuries): + results = injuries[-1] + else: + results = injuries[roll] + + if results == "HI": + characteristic = random.choice(["brawn"] * 3 + ["agility"] * 3 + ["intellect", "cunning", "presence"]) + results = "**Horrific Injury**: Until this criticil injury is healed, treat the target's "+characteristic+" as if it's one lower, "+difficulty_die+difficulty_die+difficulty_die + + if results == "GI": + characteristic = random.choice(["brawn"] * 3 + ["agility"] * 3 + ["intellect", "cunning", "presence"]) + results = "**Gruesome Injury**: The target's "+characteristic+" is permanently one lower, "+difficulty_die+difficulty_die+difficulty_die+difficulty_die + + send_message = "Roll: "+str(roll)+"\nInjury:\n"+results + + message_list = send_message.split("\n") + await ctx.send(message_list[0]) + if len(message_list) > 1: + for message_item in message_list[1:]: + await ctx.channel.send(message_item) + + # Parses the command into something the other functions understand + async def parse_roll(self, ctx, cmd : str = ""): + user = f"#{ctx.author.id}" + cmd = re.sub(' +',' ',cmd.upper()) + " " + if cmd[0] == " ": + cmd = cmd[1:] + cmd = self.bot.star_wars.character.replaceSpaces(string.capwords(cmd)) + commands = cmd.split(" ") + valid_command = False + + if commands[0] == "": + roll_parameters = [1,0,3,0,0,0,0] + else: + roll_parameters = [0,0,0,0,0,0,0] + + if string.capwords(commands[0]) == "Obligations": + send_message = self.obligation_roll() + + 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") + skill_level = 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") + char_level = self.bot.star_wars.character.char_data(user,"Characteristics " + self.bot.star_wars.character.lightsaberChar(user)) else: - results = injuries[roll] + char_level = self.bot.star_wars.character.char_data(user,"Characteristics " + skill_data[string.capwords(commands[0])]) - if results == "HI": - characteristic = random.choice(["brawn"] * 3 + ["agility"] * 3 + ["intellect", "cunning", "presence"]) - results = "**Horrific Injury**: Until this criticil injury is healed, treat the target's "+characteristic+" as if it's one lower, "+difficulty_die+difficulty_die+difficulty_die + ability_dice = abs(char_level-skill_level) + proficiency_dice = min(skill_level,char_level) - if results == "GI": - characteristic = random.choice(["brawn"] * 3 + ["agility"] * 3 + ["intellect", "cunning", "presence"]) - results = "**Gruesome Injury**: The target's "+characteristic+" is permanently one lower, "+difficulty_die+difficulty_die+difficulty_die+difficulty_die + commands = [str(ability_dice)] + [str(proficiency_dice)] + commands[1:] + self.bot.log("Converted skill to dice") + valid_command = True + else: + self.bot.log("Okay, no they don't i guess") + send_message = "You don't have a user. You can make one with /starwarscharacter" - send_message = "Roll: "+str(roll)+"\nInjury:\n"+results + 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": + send_message = "Did you mean \"Ranged - Heavy\" or \"Ranged - Light\"" + else: + send_message = "Did you mean \"Piloting - Planetary\" or \"Piloting - Space\"" + else: + valid_command = True - message_list = send_message.split("\n") - await ctx.send(message_list[0]) - if len(message_list) > 1: - for message_item in message_list[1:]: - await ctx.channel.send(message_item) + if valid_command: + self.bot.log("Converting commands to dice") + for i, command in enumerate(commands): + if command != "": + command = command.upper() + if command[0] == "A": + roll_parameters[0] = int(command.replace("A","")) + elif command[0] == "P": + roll_parameters[1] = int(command.replace("P","")) + elif command[0] == "D": + roll_parameters[2] = int(command.replace("D","")) + elif command[0] == "C": + roll_parameters[3] = int(command.replace("C","")) + elif command[0] == "B": + roll_parameters[4] = int(command.replace("B","")) + elif command[0] == "S": + roll_parameters[5] = int(command.replace("S","")) + elif command[0] == "F": + roll_parameters[6] = int(command.replace("F","")) + else: + roll_parameters[i] = int(command) - # Parses the command into something the other functions understand - async def parse_roll(self, ctx, cmd : str = ""): - user = f"#{ctx.author.id}" - cmd = re.sub(' +',' ',cmd.upper()) + " " - if cmd[0] == " ": - cmd = cmd[1:] - cmd = self.bot.star_wars.character.replaceSpaces(string.capwords(cmd)) - commands = cmd.split(" ") - valid_command = False + self.bot.log("Rolling "+str(roll_parameters)) + roll_results, dice_results = self.roll(roll_parameters[0],roll_parameters[1],roll_parameters[2],roll_parameters[3],roll_parameters[4],roll_parameters[5],roll_parameters[6]) - if commands[0] == "": - roll_parameters = [1,0,3,0,0,0,0] + simplified = self.simplify(roll_results) + + name = self.bot.star_wars.character.getChar_name(user) + + self.bot.log("Returns results and simplified results") + + if simplified == "": + send_message = name + " rolls: " + "\n" + self.dice_result_to_emoji(dice_results) + "\nEverything cancels out!" + else: + send_message = name + " rolls: " + "\n" + self.dice_result_to_emoji(dice_results) + "\n" + self.result_to_emoji(simplified) + + message_list = send_message.split("\n") + await ctx.send(message_list[0]) + if len(message_list) > 1: + for message_item in message_list[1:]: + if message_item == "": + self.bot.log("Tried to send empty message") else: - roll_parameters = [0,0,0,0,0,0,0] - - if string.capwords(commands[0]) == "Obligations": - send_message = self.obligation_roll() - - 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") - skill_level = 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") - char_level = self.bot.star_wars.character.char_data(user,"Characteristics " + self.bot.star_wars.character.lightsaberChar(user)) - else: - char_level = self.bot.star_wars.character.char_data(user,"Characteristics " + skill_data[string.capwords(commands[0])]) - - ability_dice = abs(char_level-skill_level) - proficiency_dice = min(skill_level,char_level) - - commands = [str(ability_dice)] + [str(proficiency_dice)] + commands[1:] - self.bot.log("Converted skill to dice") - valid_command = True - else: - self.bot.log("Okay, no they don't i guess") - 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": - send_message = "Did you mean \"Ranged - Heavy\" or \"Ranged - Light\"" - else: - send_message = "Did you mean \"Piloting - Planetary\" or \"Piloting - Space\"" - else: - valid_command = True - - if valid_command: - self.bot.log("Converting commands to dice") - for i, command in enumerate(commands): - if command != "": - command = command.upper() - if command[0] == "A": - roll_parameters[0] = int(command.replace("A","")) - elif command[0] == "P": - roll_parameters[1] = int(command.replace("P","")) - elif command[0] == "D": - roll_parameters[2] = int(command.replace("D","")) - elif command[0] == "C": - roll_parameters[3] = int(command.replace("C","")) - elif command[0] == "B": - roll_parameters[4] = int(command.replace("B","")) - elif command[0] == "S": - roll_parameters[5] = int(command.replace("S","")) - elif command[0] == "F": - roll_parameters[6] = int(command.replace("F","")) - else: - roll_parameters[i] = int(command) - - self.bot.log("Rolling "+str(roll_parameters)) - roll_results, dice_results = self.roll(roll_parameters[0],roll_parameters[1],roll_parameters[2],roll_parameters[3],roll_parameters[4],roll_parameters[5],roll_parameters[6]) - - simplified = self.simplify(roll_results) - - name = self.bot.star_wars.character.getChar_name(user) - - self.bot.log("Returns results and simplified results") - - if simplified == "": - send_message = name + " rolls: " + "\n" + self.dice_result_to_emoji(dice_results) + "\nEverything cancels out!" - else: - send_message = name + " rolls: " + "\n" + self.dice_result_to_emoji(dice_results) + "\n" + self.result_to_emoji(simplified) - - message_list = send_message.split("\n") - await ctx.send(message_list[0]) - if len(message_list) > 1: - for message_item in message_list[1:]: - if message_item == "": - self.bot.log("Tried to send empty message") - else: - await ctx.channel.send(message_item) + await ctx.channel.send(message_item) diff --git a/gwendolyn_old/gwendolyn_client.py b/gwendolyn_old/gwendolyn_client.py index 7a6792b..d267347 100644 --- a/gwendolyn_old/gwendolyn_client.py +++ b/gwendolyn_old/gwendolyn_client.py @@ -3,7 +3,7 @@ Contains the Gwendolyn class, a subclass of the discord command bot. *Classes* --------- - Gwendolyn(discord.ext.commands.Bot) + Gwendolyn(discord.ext.commands.Bot) """ import os # Used for loading cogs in Gwendolyn.addCogs import discord # Used for discord.Intents and discord.Status @@ -14,119 +14,119 @@ from discord.ext import commands # Used to inherit from commands.bot from pymongo import MongoClient # Used for database management from gwendolyn_old.funcs import Money, StarWars, Games, Other, LookupFuncs from gwendolyn_old.utils import (get_options, get_credentials, log_this, - DatabaseFuncs, EventHandler, ErrorHandler, - long_strings) + DatabaseFuncs, EventHandler, ErrorHandler, + long_strings) class Gwendolyn(commands.Bot): + """ + A multifunctional Discord bot. + + *Methods* + --------- + log(messages: Union[str, list], channel: str = "", + level: int = 20) + stop(ctx: interactions.SlashContext) + defer(ctx: interactions.SlashContext) + """ + # pylint: disable=too-many-instance-attributes + + def __init__(self): + """Initialize the bot.""" + intents = discord.Intents.default() + intents.members = True + initiation_parameters = { + "command_prefix": " ", + "case_insensitive": True, + "intents": intents, + "status": discord.Status.dnd + } + super().__init__(**initiation_parameters) + + self._add_clients_and_options() + self._add_util_classes() + self._add_function_containers() + self._add_cogs() + + def _add_clients_and_options(self): + """Add all the client, option and credentials objects.""" + self.long_strings = long_strings() + self.options = get_options() + self.credentials = get_credentials() + 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"]: + self.log("Testing mode") + self.database = database_clint["Gwendolyn-Test"] + else: + self.database = database_clint["Gwendolyn"] + + def _add_util_classes(self): + """Add all the classes used as utility.""" + 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 = interactions.SlashCommand(self, **slash_parameters) + + def _add_function_containers(self): + """Add all the function containers used for commands.""" + self.star_wars = StarWars(self) + self.other = Other(self) + self.lookup_funcs = LookupFuncs(self) + self.games = Games(self) + self.money = Money(self) + + def _add_cogs(self): + """Load cogs.""" + for filename in os.listdir("./gwendolyn/cogs"): + if filename.endswith(".py"): + self.load_extension(f"gwendolyn.cogs.{filename[:-3]}") + + def log(self, messages, channel: str = "", level: int = 20): # pylint:disable=no-self-use + """Log a message. Described in utils/util_functions.py.""" + log_this(messages, channel, level) + + async def stop(self, ctx: interactions.SlashContext): """ - A multifunctional Discord bot. + Stop the bot, and stop running games. - *Methods* - --------- - log(messages: Union[str, list], channel: str = "", - level: int = 20) - stop(ctx: interactions.SlashContext) - defer(ctx: interactions.SlashContext) + Only stops the bot if the user in ctx.author is one of the + admins given in options.txt. + + *parameters* + ------------ + ctx: interactions.SlashContext + The context of the "/stop" slash command. """ - # pylint: disable=too-many-instance-attributes + if f"#{ctx.author.id}" in self.options["admins"]: + await ctx.send("Pulling git repo and restarting...") - def __init__(self): - """Initialize the bot.""" - intents = discord.Intents.default() - intents.members = True - initiation_parameters = { - "command_prefix": " ", - "case_insensitive": True, - "intents": intents, - "status": discord.Status.dnd - } - super().__init__(**initiation_parameters) + await self.change_presence(status=discord.Status.offline) - self._add_clients_and_options() - self._add_util_classes() - self._add_function_containers() - self._add_cogs() + self.database_funcs.wipe_games() + if not self.options["testing"]: + git_client = git.cmd.Git("") + git_client.pull() - def _add_clients_and_options(self): - """Add all the client, option and credentials objects.""" - self.long_strings = long_strings() - self.options = get_options() - self.credentials = get_credentials() - 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) + self.log("Logging out", level=25) + await self.close() + else: + 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}") - if self.options["testing"]: - self.log("Testing mode") - self.database = database_clint["Gwendolyn-Test"] - else: - self.database = database_clint["Gwendolyn"] - - def _add_util_classes(self): - """Add all the classes used as utility.""" - 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 = interactions.SlashCommand(self, **slash_parameters) - - def _add_function_containers(self): - """Add all the function containers used for commands.""" - self.star_wars = StarWars(self) - self.other = Other(self) - self.lookup_funcs = LookupFuncs(self) - self.games = Games(self) - self.money = Money(self) - - def _add_cogs(self): - """Load cogs.""" - for filename in os.listdir("./gwendolyn/cogs"): - if filename.endswith(".py"): - self.load_extension(f"gwendolyn.cogs.{filename[:-3]}") - - def log(self, messages, channel: str = "", level: int = 20): # pylint:disable=no-self-use - """Log a message. Described in utils/util_functions.py.""" - log_this(messages, channel, level) - - async def stop(self, ctx: interactions.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: interactions.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) - - 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() - else: - 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: interactions.SlashContext): - """Send a "Gwendolyn is thinking" message to the user.""" - try: - await ctx.defer() - except interactions.error.AlreadyResponded: - self.log("defer failed") + async def defer(self, ctx: interactions.SlashContext): + """Send a "Gwendolyn is thinking" message to the user.""" + try: + await ctx.defer() + except interactions.error.AlreadyResponded: + self.log("defer failed") diff --git a/gwendolyn_old/resources/long_strings.json b/gwendolyn_old/resources/long_strings.json index c9c5e42..e443374 100644 --- a/gwendolyn_old/resources/long_strings.json +++ b/gwendolyn_old/resources/long_strings.json @@ -1,27 +1,27 @@ { - "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 the buttons or \"/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", - "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." + "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 the buttons or \"/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", + "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_old/resources/slash_parameters.json b/gwendolyn_old/resources/slash_parameters.json index 9011ff5..a6f4259 100644 --- a/gwendolyn_old/resources/slash_parameters.json +++ b/gwendolyn_old/resources/slash_parameters.json @@ -1,401 +1,401 @@ { - "add_movie" : { - "name" : "add_movie", - "description" : "Request a movie for Plex", - "options" : [ - { - "name" : "movie", - "description" : "The movie to request", - "type" : 3, - "required" : "true" - } - ] - }, - "add_show" : { - "name" : "add_show", - "description" : "Request a show for Plex", - "options" : [ - { - "name" : "show", - "description" : "The show to request", - "type" : 3, - "required" : "true" - } - ] - }, - "balance" : { - "name" : "balance", - "description" : "See your balance of GwendoBucks" - }, - "blackjack_bet" : { - "base" : "blackjack", - "name" : "bet", - "description" : "Enter the current blackjack game with a bet", - "options" : [ - { - "name" : "bet", - "description" : "Your bet", - "type" : 4, - "required" : "true" - } - ] - }, - "blackjack_cards" : { - "base" : "blackjack", - "name" : "cards", - "description" : "Get a count of the cards used in blackjack games" - }, - "blackjack_hilo" : { - "base" : "blackjack", - "name" : "hilo", - "description" : "Get the current hi-lo value for the cards used in blackjack games" - }, - "blackjack_shuffle" : { - "base" : "blackjack", - "name" : "shuffle", - "description" : "Shuffle the cards used in blackjack games" - }, - "blackjack_start" : { - "base" : "blackjack", - "name" : "start", - "description" : "Start a game of blackjack" - }, - "connect_four_start_gwendolyn" : { - "base" : "connect_four", - "subcommand_group" : "start", - "name" : "Gwendolyn", - "description" : "Start a game of connect four against Gwendolyn", - "options" : [ - { - "name" : "difficulty", - "description" : "The difficulty of Gwendolyn's AI", - "type" : 4, - "required" : "false" - } - ] - }, - "connect_four_start_user" : { - "base" : "connect_four", - "subcommand_group" : "start", - "name" : "user", - "description" : "Start a game of connect four against another user", - "options" : [ - { - "name" : "user", - "description" : "The user to start a game against", - "type" : 6, - "required" : "true" - } - ] - }, - "downloading" : { - "name" : "downloading", - "description" : "See current downloads for Plex", - "options" : [ - { - "name" : "parameters", - "description" : "Parameters for the command", - "type" : 3, - "required" : "false" - } - ] - }, - "game" : { - "name" : "game", - "description" : "Set the 'playing' text for Gwendolyn", - "options" : [ - { - "name" : "game_text", - "description" : "The game to set the 'playing' text to", - "type" : 3, - "required" : "true" - } - ] - }, - "give" : { - "name" : "give", - "description" : "Give GwendoBucks to another user", - "options" : [ - { - "name" : "user", - "description" : "The user you're sending GwendoBucks to", - "type" : 6, - "required" : "true" - }, - { - "name" : "amount", - "description" : "The number of GwendoBucks you're sending", - "type" : 4, - "required" : "true" - } - ] - }, - "hangman" : { - "name" : "hangman", - "description" : "Start a game of hangman" - }, - "hello" : { - "name" : "hello", - "description" : "Greet Gwendolyn" - }, - "help" : { - "name" : "help", - "description" : "Get help with a command", - "options" : [ - { - "name" : "command", - "description" : "The command you want help with", - "type" : 3, - "required" : "false" - } - ] - }, - "hex_place" : { - "base" : "hex", - "name" : "place", - "description" : "Place a piece on the hex board", - "options" : [ - { - "name" : "coordinates", - "description" : "The coordinates to place the piece at", - "type" : 3, - "required" : "true" - } - ] - }, - "hex_start_gwendolyn" : { - "base" : "hex", - "subcommand_group" : "start", - "name" : "Gwendolyn", - "description" : "Start a game of hex against Gwendolyn", - "options" : [ - { - "name" : "difficulty", - "description" : "The difficulty of Gwendolyn's AI", - "type" : 4, - "required" : "false" - } - ] - }, - "hex_start_user" : { - "base" : "hex", - "subcommand_group" : "start", - "name" : "user", - "description" : "Start a game of hex against another user", - "options" : [ - { - "name" : "user", - "description" : "The user to start a game against", - "type" : 6, - "required" : "true" - } - ] - }, - "hex_surrender" : { - "base" : "hex", - "name" : "surrender", - "description" : "Surrender the game of hex" - }, - "hex_swap" : { - "base" : "hex", - "name" : "swap", - "description" : "Perform a hex swap" - }, - "hex_undo" : { - "base" : "hex", - "name" : "undo", - "description" : "Undo your last hex move" - }, - "image" : { - "name" : "image", - "description" : "Get a random image from Bing" - }, - "monster" : { - "name" : "monster", - "description" : "Look up a monster", - "options" : [ - { - "name" : "query", - "description" : "The monster to look up", - "type" : 3, - "required" : "true" - } - ] - }, - "movie" : { + "add_movie" : { + "name" : "add_movie", + "description" : "Request a movie for Plex", + "options" : [ + { "name" : "movie", - "description" : "Get the name and information of a random movie" - }, - "name" : { - "name" : "name", - "description" : "Generate a random name" - }, - "ping" : { - "name" : "ping", - "description" : "Get the Gwendolyn's latency to the server" - }, - "roll" : { - "name" : "roll", - "description" : "Roll rpg dice", - "options" : [ - { - "name" : "dice", - "description" : "The dice to roll", - "type" : 3, - "required" : "false" - } + "description" : "The movie to request", + "type" : 3, + "required" : "true" + } + ] + }, + "add_show" : { + "name" : "add_show", + "description" : "Request a show for Plex", + "options" : [ + { + "name" : "show", + "description" : "The show to request", + "type" : 3, + "required" : "true" + } + ] + }, + "balance" : { + "name" : "balance", + "description" : "See your balance of GwendoBucks" + }, + "blackjack_bet" : { + "base" : "blackjack", + "name" : "bet", + "description" : "Enter the current blackjack game with a bet", + "options" : [ + { + "name" : "bet", + "description" : "Your bet", + "type" : 4, + "required" : "true" + } + ] + }, + "blackjack_cards" : { + "base" : "blackjack", + "name" : "cards", + "description" : "Get a count of the cards used in blackjack games" + }, + "blackjack_hilo" : { + "base" : "blackjack", + "name" : "hilo", + "description" : "Get the current hi-lo value for the cards used in blackjack games" + }, + "blackjack_shuffle" : { + "base" : "blackjack", + "name" : "shuffle", + "description" : "Shuffle the cards used in blackjack games" + }, + "blackjack_start" : { + "base" : "blackjack", + "name" : "start", + "description" : "Start a game of blackjack" + }, + "connect_four_start_gwendolyn" : { + "base" : "connect_four", + "subcommand_group" : "start", + "name" : "Gwendolyn", + "description" : "Start a game of connect four against Gwendolyn", + "options" : [ + { + "name" : "difficulty", + "description" : "The difficulty of Gwendolyn's AI", + "type" : 4, + "required" : "false" + } + ] + }, + "connect_four_start_user" : { + "base" : "connect_four", + "subcommand_group" : "start", + "name" : "user", + "description" : "Start a game of connect four against another user", + "options" : [ + { + "name" : "user", + "description" : "The user to start a game against", + "type" : 6, + "required" : "true" + } + ] + }, + "downloading" : { + "name" : "downloading", + "description" : "See current downloads for Plex", + "options" : [ + { + "name" : "parameters", + "description" : "Parameters for the command", + "type" : 3, + "required" : "false" + } + ] + }, + "game" : { + "name" : "game", + "description" : "Set the 'playing' text for Gwendolyn", + "options" : [ + { + "name" : "game_text", + "description" : "The game to set the 'playing' text to", + "type" : 3, + "required" : "true" + } + ] + }, + "give" : { + "name" : "give", + "description" : "Give GwendoBucks to another user", + "options" : [ + { + "name" : "user", + "description" : "The user you're sending GwendoBucks to", + "type" : 6, + "required" : "true" + }, + { + "name" : "amount", + "description" : "The number of GwendoBucks you're sending", + "type" : 4, + "required" : "true" + } + ] + }, + "hangman" : { + "name" : "hangman", + "description" : "Start a game of hangman" + }, + "hello" : { + "name" : "hello", + "description" : "Greet Gwendolyn" + }, + "help" : { + "name" : "help", + "description" : "Get help with a command", + "options" : [ + { + "name" : "command", + "description" : "The command you want help with", + "type" : 3, + "required" : "false" + } + ] + }, + "hex_place" : { + "base" : "hex", + "name" : "place", + "description" : "Place a piece on the hex board", + "options" : [ + { + "name" : "coordinates", + "description" : "The coordinates to place the piece at", + "type" : 3, + "required" : "true" + } + ] + }, + "hex_start_gwendolyn" : { + "base" : "hex", + "subcommand_group" : "start", + "name" : "Gwendolyn", + "description" : "Start a game of hex against Gwendolyn", + "options" : [ + { + "name" : "difficulty", + "description" : "The difficulty of Gwendolyn's AI", + "type" : 4, + "required" : "false" + } + ] + }, + "hex_start_user" : { + "base" : "hex", + "subcommand_group" : "start", + "name" : "user", + "description" : "Start a game of hex against another user", + "options" : [ + { + "name" : "user", + "description" : "The user to start a game against", + "type" : 6, + "required" : "true" + } + ] + }, + "hex_surrender" : { + "base" : "hex", + "name" : "surrender", + "description" : "Surrender the game of hex" + }, + "hex_swap" : { + "base" : "hex", + "name" : "swap", + "description" : "Perform a hex swap" + }, + "hex_undo" : { + "base" : "hex", + "name" : "undo", + "description" : "Undo your last hex move" + }, + "image" : { + "name" : "image", + "description" : "Get a random image from Bing" + }, + "monster" : { + "name" : "monster", + "description" : "Look up a monster", + "options" : [ + { + "name" : "query", + "description" : "The monster to look up", + "type" : 3, + "required" : "true" + } + ] + }, + "movie" : { + "name" : "movie", + "description" : "Get the name and information of a random movie" + }, + "name" : { + "name" : "name", + "description" : "Generate a random name" + }, + "ping" : { + "name" : "ping", + "description" : "Get the Gwendolyn's latency to the server" + }, + "roll" : { + "name" : "roll", + "description" : "Roll rpg dice", + "options" : [ + { + "name" : "dice", + "description" : "The dice to roll", + "type" : 3, + "required" : "false" + } + ] + }, + "spell" : { + "name" : "spell", + "description" : "Look up a spell", + "options" : [ + { + "name" : "query", + "description" : "The spell to look up", + "type" : 3, + "required" : "true" + } + ] + }, + "star_wars_character" : { + "name" : "star_wars_character", + "description" : "Manage your Star Wars character sheet", + "options" : [ + { + "name" : "parameters", + "description" : "The parameters for the command", + "type" : 3, + "required" : "false" + } + ] + }, + "star_wars_crit" : { + "name" : "star_wars_crit", + "description" : "Roll a Star Wars critical injury", + "options" : [ + { + "name" : "severity", + "description" : "The severity of the hit", + "type" : 4, + "required" : "true" + } + ] + }, + "star_wars_destiny" : { + "name" : "star_wars_destiny", + "description" : "Use and see Star Wars Destiny points", + "options" : [ + { + "name" : "parameters", + "description" : "The parameters for the command", + "type" : 3, + "required" : "false" + } + ] + }, + "star_wars_roll" : { + "name" : "star_wars_roll", + "description" : "Roll Star Wars dice", + "options" : [ + { + "name" : "dice", + "description" : "The dice, or ability, to roll", + "type" : 3, + "required" : "false" + } + ] + }, + "stop" : { + "name" : "stop", + "description" : "Restart Gwendolyn" + }, + "tavern" : { + "name" : "tavern", + "description" : "Generate a random tavern" + }, + "thank" : { + "name" : "thank", + "description" : "Thank Gwendolyn for her service" + }, + "trivia" : { + "name" : "trivia", + "description" : "Play a game of trivia", + "options" : [ + { + "name" : "answer", + "description" : "Your answer to the trivia question", + "type" : 3, + "required" : "false", + "choices" : [ + { + "name" : "a", + "value" : "a" + }, + { + "name" : "b", + "value" : "b" + }, + { + "name" : "c", + "value" : "c" + }, + { + "name" : "d", + "value" : "d" + } ] - }, - "spell" : { - "name" : "spell", - "description" : "Look up a spell", - "options" : [ - { - "name" : "query", - "description" : "The spell to look up", - "type" : 3, - "required" : "true" - } - ] - }, - "star_wars_character" : { - "name" : "star_wars_character", - "description" : "Manage your Star Wars character sheet", - "options" : [ - { - "name" : "parameters", - "description" : "The parameters for the command", - "type" : 3, - "required" : "false" - } - ] - }, - "star_wars_crit" : { - "name" : "star_wars_crit", - "description" : "Roll a Star Wars critical injury", - "options" : [ - { - "name" : "severity", - "description" : "The severity of the hit", - "type" : 4, - "required" : "true" - } - ] - }, - "star_wars_destiny" : { - "name" : "star_wars_destiny", - "description" : "Use and see Star Wars Destiny points", - "options" : [ - { - "name" : "parameters", - "description" : "The parameters for the command", - "type" : 3, - "required" : "false" - } - ] - }, - "star_wars_roll" : { - "name" : "star_wars_roll", - "description" : "Roll Star Wars dice", - "options" : [ - { - "name" : "dice", - "description" : "The dice, or ability, to roll", - "type" : 3, - "required" : "false" - } - ] - }, - "stop" : { - "name" : "stop", - "description" : "Restart Gwendolyn" - }, - "tavern" : { - "name" : "tavern", - "description" : "Generate a random tavern" - }, - "thank" : { - "name" : "thank", - "description" : "Thank Gwendolyn for her service" - }, - "trivia" : { - "name" : "trivia", - "description" : "Play a game of trivia", - "options" : [ - { - "name" : "answer", - "description" : "Your answer to the trivia question", - "type" : 3, - "required" : "false", - "choices" : [ - { - "name" : "a", - "value" : "a" - }, - { - "name" : "b", - "value" : "b" - }, - { - "name" : "c", - "value" : "c" - }, - { - "name" : "d", - "value" : "d" - } - ] - } - ] - }, - "wiki" : { - "name" : "wiki", - "description" : "Searches for and gets the info for a wiki page", - "options" : [ - { - "name" : "page", - "description" : "The page to find", - "type" : 3, - "required" : "false" - } - ] - }, - "wolf" : { - "name" : "wolf", - "description" : "Performs a search on Wolfram Alpha", - "options" : [ - { - "name" : "query", - "description" : "What to search for on Wolfram Alpha", - "type" : 3, - "required" : "true" - } - ] - }, - "wordle_start": { - "base": "wordle", - "name" : "start", - "description": "Start a game of wordle", - "options": [ - { - "name": "letters", - "description" : "How many letters the word should be", - "type": 4, - "required": "false" - } - ] - }, - "wordle_guess": { - "base": "wordle", - "name" : "guess", - "description": "Guess a word in wordle", - "options": [ - { - "name": "guess", - "description" : "Your guess", - "type": 3, - "required": "true" - } - ] - } + } + ] + }, + "wiki" : { + "name" : "wiki", + "description" : "Searches for and gets the info for a wiki page", + "options" : [ + { + "name" : "page", + "description" : "The page to find", + "type" : 3, + "required" : "false" + } + ] + }, + "wolf" : { + "name" : "wolf", + "description" : "Performs a search on Wolfram Alpha", + "options" : [ + { + "name" : "query", + "description" : "What to search for on Wolfram Alpha", + "type" : 3, + "required" : "true" + } + ] + }, + "wordle_start": { + "base": "wordle", + "name" : "start", + "description": "Start a game of wordle", + "options": [ + { + "name": "letters", + "description" : "How many letters the word should be", + "type": 4, + "required": "false" + } + ] + }, + "wordle_guess": { + "base": "wordle", + "name" : "guess", + "description": "Guess a word in wordle", + "options": [ + { + "name": "guess", + "description" : "Your guess", + "type": 3, + "required": "true" + } + ] + } } \ No newline at end of file diff --git a/gwendolyn_old/resources/star_wars/starwarsskills.json b/gwendolyn_old/resources/star_wars/starwarsskills.json index 698cafe..61f2b8a 100644 --- a/gwendolyn_old/resources/star_wars/starwarsskills.json +++ b/gwendolyn_old/resources/star_wars/starwarsskills.json @@ -1,36 +1,36 @@ { - "Astrogation" : "Intellect", - "Computers" : "Intellect", - "Cool" : "Presence", - "Vigilance" : "Willpower", - "Mechanics" : "Intellect", - "Melee" : "Brawn", - "Perception" : "Cunning", - "Piloting-space" : "Agility", - "Ranged-heavy" : "Agility", - "Ranged-light" : "Agility", - "Athletics" : "Brawn", - "Coercion" : "Willpower", - "Coordination" : "Agility", - "Charm" : "Presence", - "Medicine" : "Intellect", - "Negotiation" : "Presence", - "Piloting-planetary" : "Agility", - "Stealth" : "Agility", - "skulduggery" : "Cunning", - "Brawl" : "Brawn", - "Discipline" : "Willpower", - "Gunnery" : "Agility", - "Core-worlds" : "Intellect", - "Outer-rim" : "Intellect", - "Underworld" : "Intellect", - "Leadership" : "Presence", - "Lore" : "Intellect", - "Resilience" : "Brawn", - "Streetwise" : "Cunning", - "Survival" : "Cunning", - "Xenology" : "Intellect", - "Lightsaber" : "Brawn", - "Education" : "Intellect", - "Deception" : "Cunning" + "Astrogation" : "Intellect", + "Computers" : "Intellect", + "Cool" : "Presence", + "Vigilance" : "Willpower", + "Mechanics" : "Intellect", + "Melee" : "Brawn", + "Perception" : "Cunning", + "Piloting-space" : "Agility", + "Ranged-heavy" : "Agility", + "Ranged-light" : "Agility", + "Athletics" : "Brawn", + "Coercion" : "Willpower", + "Coordination" : "Agility", + "Charm" : "Presence", + "Medicine" : "Intellect", + "Negotiation" : "Presence", + "Piloting-planetary" : "Agility", + "Stealth" : "Agility", + "skulduggery" : "Cunning", + "Brawl" : "Brawn", + "Discipline" : "Willpower", + "Gunnery" : "Agility", + "Core-worlds" : "Intellect", + "Outer-rim" : "Intellect", + "Underworld" : "Intellect", + "Leadership" : "Presence", + "Lore" : "Intellect", + "Resilience" : "Brawn", + "Streetwise" : "Cunning", + "Survival" : "Cunning", + "Xenology" : "Intellect", + "Lightsaber" : "Brawn", + "Education" : "Intellect", + "Deception" : "Cunning" } \ No newline at end of file diff --git a/gwendolyn_old/resources/star_wars/starwarstemplates.json b/gwendolyn_old/resources/star_wars/starwarstemplates.json index 00773a9..aec6f52 100644 --- a/gwendolyn_old/resources/star_wars/starwarstemplates.json +++ b/gwendolyn_old/resources/star_wars/starwarstemplates.json @@ -1,82 +1,82 @@ { - "Character": { - "Name": "New Character", - "Species": "", - "Career": "", - "Specialization-trees": [], - "Soak": 0, - "Wound-threshold": 0, - "Wounds": 0, - "Strain-threshold": 0, - "Strain": 0, - "Defense-ranged": 0, - "Defense-melee": 0, - "Force-rating": 0, - "Characteristics": { - "Brawn": 2, - "Agility": 2, - "Intellect": 2, - "Cunning": 2, - "Willpower": 2, - "Presence": 2 - }, - "Skills": { - "Astrogation": 0, - "Athletics": 0, - "Brawl": 0, - "Charm": 0, - "Coercion": 0, - "Computers": 0, - "Cool": 0, - "Coordination": 0, - "Core-worlds": 0, - "Discipline": 0, - "Deception": 0, - "Education": 0, - "Gunnery": 0, - "Leadership": 0, - "Lightsaber": 0, - "Lore": 0, - "Mechanics": 0, - "Medicine": 0, - "Melee": 0, - "Negotiation": 0, - "Outer-rim": 0, - "Perception": 0, - "Piloting-planetary": 0, - "Piloting-space": 0, - "Ranged-heavy": 0, - "Ranged-light": 0, - "Resilience": 0, - "skulduggery": 0, - "Stealth": 0, - "Streetwise": 0, - "Survival": 0, - "Underworld": 0, - "Vigilance": 0, - "Xenology": 0 - }, - "Lightsaber-characteristic": "Brawn", - "Obligations": {}, - "Morality": { - "Weakness": "", - "Strength": "", - "Conflict": "", - "Morality": "" - }, - "Credits": 0, - "Equipment": [], - "Armor": "", - "Critical-injuries": {}, - "Weapons": {}, - "Talents": {}, - "Force-powers": {} + "Character": { + "Name": "New Character", + "Species": "", + "Career": "", + "Specialization-trees": [], + "Soak": 0, + "Wound-threshold": 0, + "Wounds": 0, + "Strain-threshold": 0, + "Strain": 0, + "Defense-ranged": 0, + "Defense-melee": 0, + "Force-rating": 0, + "Characteristics": { + "Brawn": 2, + "Agility": 2, + "Intellect": 2, + "Cunning": 2, + "Willpower": 2, + "Presence": 2 }, - "Weapon": { - "Skill" : "", - "Damage" : 0, - "Range" : "", - "Crit" : 0, - "Special" : [] - } + "Skills": { + "Astrogation": 0, + "Athletics": 0, + "Brawl": 0, + "Charm": 0, + "Coercion": 0, + "Computers": 0, + "Cool": 0, + "Coordination": 0, + "Core-worlds": 0, + "Discipline": 0, + "Deception": 0, + "Education": 0, + "Gunnery": 0, + "Leadership": 0, + "Lightsaber": 0, + "Lore": 0, + "Mechanics": 0, + "Medicine": 0, + "Melee": 0, + "Negotiation": 0, + "Outer-rim": 0, + "Perception": 0, + "Piloting-planetary": 0, + "Piloting-space": 0, + "Ranged-heavy": 0, + "Ranged-light": 0, + "Resilience": 0, + "skulduggery": 0, + "Stealth": 0, + "Streetwise": 0, + "Survival": 0, + "Underworld": 0, + "Vigilance": 0, + "Xenology": 0 + }, + "Lightsaber-characteristic": "Brawn", + "Obligations": {}, + "Morality": { + "Weakness": "", + "Strength": "", + "Conflict": "", + "Morality": "" + }, + "Credits": 0, + "Equipment": [], + "Armor": "", + "Critical-injuries": {}, + "Weapons": {}, + "Talents": {}, + "Force-powers": {} + }, + "Weapon": { + "Skill" : "", + "Damage" : 0, + "Range" : "", + "Crit" : 0, + "Special" : [] + } } \ No newline at end of file diff --git a/gwendolyn_old/resources/starting_files.json b/gwendolyn_old/resources/starting_files.json index d370a78..fe8791f 100644 --- a/gwendolyn_old/resources/starting_files.json +++ b/gwendolyn_old/resources/starting_files.json @@ -1,74 +1,74 @@ { - "json":{ - "gwendolyn/resources/lookup/spells.json" : { - "Fireball" : { - "casting_time" : "1 action", - "components" : "V, S, M (a tiny ball of bat guano and sulfur)", - "description" : "A bright streak flashes from your pointing finger to a point you choose within range and then blossoms with a low roar into an explosion of flame. Each creature in a 20-foot-radius sphere centered on that point must make a Dexterity saving throw. A target takes 8d6 fire damage on a failed save, or half as much damage on a successful one. The fire spreads around corners. It ignites flammable objects in the area that aren’t being worn or carried. At Higher Levels. When you cast this spell using a spell slot of 4th level or higher, the damage increases by 1d6 for each slot level above 3rd.", - "duration" : "Instantaneous", - "level" : "3rd", - "range" : "150 feet", - "school" : "Evocation", - "ritual" : false - } - }, - "gwendolyn/resources/lookup/monsters.json" : [ - { - "name": "Bandit", - "size": "Medium", - "type": "humanoid", - "subtype": "any race", - "alignment": "any non-lawful alignment", - "armor_class": 12, - "hit_points": 11, - "hit_dice": "2d8", - "speed": "30 ft.", - "strength": 11, - "dexterity": 12, - "constitution": 12, - "intelligence": 10, - "wisdom": 10, - "charisma": 10, - "damage_vulnerabilities": "", - "damage_resistances": "", - "damage_immunities": "", - "condition_immunities": "", - "senses": "passive Perception 10", - "languages": "any one language (usually Common)", - "challenge_rating": "1/8", - "actions": [ - { - "name": "Scimitar", - "desc": "Melee Weapon Attack: +3 to hit, reach 5 ft., one target. Hit: 4 (1d6 + 1) slashing damage.", - "attack_bonus": 3, - "damage_dice": "1d6", - "damage_bonus": 1 - }, - { - "name": "Light Crossbow", - "desc": "Ranged Weapon Attack: +3 to hit, range 80 ft./320 ft., one target. Hit: 5 (1d8 + 1) piercing damage.", - "attack_bonus": 3, - "damage_dice": "1d8", - "damage_bonus": 1 - } - ] - } + "json":{ + "gwendolyn/resources/lookup/spells.json" : { + "Fireball" : { + "casting_time" : "1 action", + "components" : "V, S, M (a tiny ball of bat guano and sulfur)", + "description" : "A bright streak flashes from your pointing finger to a point you choose within range and then blossoms with a low roar into an explosion of flame. Each creature in a 20-foot-radius sphere centered on that point must make a Dexterity saving throw. A target takes 8d6 fire damage on a failed save, or half as much damage on a successful one. The fire spreads around corners. It ignites flammable objects in the area that aren’t being worn or carried. At Higher Levels. When you cast this spell using a spell slot of 4th level or higher, the damage increases by 1d6 for each slot level above 3rd.", + "duration" : "Instantaneous", + "level" : "3rd", + "range" : "150 feet", + "school" : "Evocation", + "ritual" : false + } + }, + "gwendolyn/resources/lookup/monsters.json" : [ + { + "name": "Bandit", + "size": "Medium", + "type": "humanoid", + "subtype": "any race", + "alignment": "any non-lawful alignment", + "armor_class": 12, + "hit_points": 11, + "hit_dice": "2d8", + "speed": "30 ft.", + "strength": 11, + "dexterity": 12, + "constitution": 12, + "intelligence": 10, + "wisdom": 10, + "charisma": 10, + "damage_vulnerabilities": "", + "damage_resistances": "", + "damage_immunities": "", + "condition_immunities": "", + "senses": "passive Perception 10", + "languages": "any one language (usually Common)", + "challenge_rating": "1/8", + "actions": [ + { + "name": "Scimitar", + "desc": "Melee Weapon Attack: +3 to hit, reach 5 ft., one target. Hit: 4 (1d6 + 1) slashing damage.", + "attack_bonus": 3, + "damage_dice": "1d6", + "damage_bonus": 1 + }, + { + "name": "Light Crossbow", + "desc": "Ranged Weapon Attack: +3 to hit, range 80 ft./320 ft., one target. Hit: 5 (1d8 + 1) piercing damage.", + "attack_bonus": 3, + "damage_dice": "1d8", + "damage_bonus": 1 + } ] - }, - "txt": { - "gwendolyn/resources/star_wars/destinyPoints.txt": "", - "gwendolyn/resources/movies.txt": "The Room", - "gwendolyn/resources/names.txt": "Gandalf\n", - "credentials.txt" : "Bot token: TOKEN\nWordnik API Key: KEY\nMongoDB user: USERNAME\nMongoDB password: PASSWORD\nWolframAlpha AppID: APPID\nRadarr API key: KEY\nSonarr API key: KEY\nqBittorrent username: USER\nqBittorrent password: PASSWORD", - "options.txt" : "Testing: True\nTesting guild ids:\nAdmins:" - }, - "folder" : [ - "gwendolyn/resources/lookup", - "gwendolyn/resources/games/blackjack_tables", - "gwendolyn/resources/games/connect_four_boards", - "gwendolyn/resources/games/hex_boards", - "gwendolyn/resources/games/hangman_boards", - "gwendolyn/resources/plex", - "gwendolyn/resources/games/old_images" + } ] + }, + "txt": { + "gwendolyn/resources/star_wars/destinyPoints.txt": "", + "gwendolyn/resources/movies.txt": "The Room", + "gwendolyn/resources/names.txt": "Gandalf\n", + "credentials.txt" : "Bot token: TOKEN\nWordnik API Key: KEY\nMongoDB user: USERNAME\nMongoDB password: PASSWORD\nWolframAlpha AppID: APPID\nRadarr API key: KEY\nSonarr API key: KEY\nqBittorrent username: USER\nqBittorrent password: PASSWORD", + "options.txt" : "Testing: True\nTesting guild ids:\nAdmins:" + }, + "folder" : [ + "gwendolyn/resources/lookup", + "gwendolyn/resources/games/blackjack_tables", + "gwendolyn/resources/games/connect_four_boards", + "gwendolyn/resources/games/hex_boards", + "gwendolyn/resources/games/hangman_boards", + "gwendolyn/resources/plex", + "gwendolyn/resources/games/old_images" + ] } \ No newline at end of file diff --git a/gwendolyn_old/utils/__init__.py b/gwendolyn_old/utils/__init__.py index c8353c0..36df7f7 100644 --- a/gwendolyn_old/utils/__init__.py +++ b/gwendolyn_old/utils/__init__.py @@ -1,12 +1,12 @@ """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"] + "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, encode_id, - decode_id) + replace_multiple, emoji_to_command, long_strings, + sanitize, get_options, get_credentials, encode_id, + decode_id) diff --git a/gwendolyn_old/utils/event_handlers.py b/gwendolyn_old/utils/event_handlers.py index 5884faf..88a8acf 100644 --- a/gwendolyn_old/utils/event_handlers.py +++ b/gwendolyn_old/utils/event_handlers.py @@ -3,15 +3,15 @@ Classes used to handle bot events and errors. *Classes* --------- - EventHandler - ErrorHandler + EventHandler + ErrorHandler """ import traceback # Used to get the traceback of errors import discord # Used to init discord.Game and discord.Status, as well - # as compare errors to discord errors and as typehints + # as compare errors to discord errors and as typehints from discord.ext import commands # Used to compare errors with command - # errors + # errors from interactions import SlashContext, ComponentContext from gwendolyn_old.utils.util_functions import decode_id @@ -19,88 +19,88 @@ from gwendolyn_old.exceptions import InvalidInteraction class EventHandler(): - """ - Handles bot events. + """ + Handles bot events. - *Methods* - --------- - on_ready() - on_slash_command(ctx: interactions.SlashContext) - on_reaction_add(ctx: interactions.SlashContext) - """ + *Methods* + --------- + on_ready() + on_slash_command(ctx: interactions.SlashContext) + on_reaction_add(ctx: interactions.SlashContext) + """ - def __init__(self, bot): - """Initialize the handler.""" - self.bot = bot + def __init__(self, bot): + """Initialize the handler.""" + self.bot = bot - async def on_ready(self): - """Log and sets status when it logs in.""" - await self.bot.database_funcs.imdb_commands() - name = self.bot.user.name - userid = str(self.bot.user.id) - logged_in_message = f"Logged in as {name}, {userid}" - self.bot.log(logged_in_message, level=25) - game = discord.Game("Use /help for commands") + async def on_ready(self): + """Log and sets status when it logs in.""" + await self.bot.database_funcs.imdb_commands() + name = self.bot.user.name + userid = str(self.bot.user.id) + logged_in_message = f"Logged in as {name}, {userid}" + self.bot.log(logged_in_message, level=25) + game = discord.Game("Use /help for commands") - online_status = discord.Status.online - await self.bot.change_presence(activity=game, status=online_status) + 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.""" - if ctx.subcommand_name is not None: - subcommand = f" {ctx.subcommand_name} " - else: - subcommand = " " + 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: + subcommand = " " - if ctx.subcommand_group is not None: - sub_command_group = f"{ctx.subcommand_group} " - else: - sub_command_group = "" + if ctx.subcommand_group is not None: + sub_command_group = f"{ctx.subcommand_group} " + else: + sub_command_group = "" - args = " ".join([str(i) for i in ctx.args]) - 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) + args = " ".join([str(i) for i in ctx.args]) + 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_component(self, ctx: ComponentContext): - """Handle component interaction.""" - info = decode_id(ctx.custom_id) - self.bot.log(f"Component action with info {info}") - channel = ctx.channel - author = str(ctx.author_id) + async def on_component(self, ctx: ComponentContext): + """Handle component interaction.""" + info = decode_id(ctx.custom_id) + self.bot.log(f"Component action with info {info}") + channel = ctx.channel + author = str(ctx.author_id) - if info[0].lower() == "plex": - if info[1].lower() == "movie": - await self.bot.other.plex.add_movie( - ctx.origin_message, - info[2], - not isinstance(channel, discord.DMChannel) - ) - return + if info[0].lower() == "plex": + if info[1].lower() == "movie": + await self.bot.other.plex.add_movie( + ctx.origin_message, + info[2], + not isinstance(channel, discord.DMChannel) + ) + return - elif info[1].lower() == "show": - await self.bot.other.plex.add_show( - ctx.origin_message, - info[2], - not isinstance(channel, discord.DMChannel) - ) - else: - raise InvalidInteraction(ctx.custom_id, info) + elif info[1].lower() == "show": + await self.bot.other.plex.add_show( + ctx.origin_message, + info[2], + not isinstance(channel, discord.DMChannel) + ) + else: + raise InvalidInteraction(ctx.custom_id, info) - elif info[0].lower() == "hangman" and author == info[2]: - if info[1].lower() == "guess": - await self.bot.games.hangman.guess(ctx, *info[3:]) - elif info[1].lower() == "end": - await self.bot.games.hangman.stop(ctx, *info[3:]) - else: - raise InvalidInteraction(ctx.custom_id, info) - elif info[0].lower() == "connectfour": - connect_four = self.bot.games.connect_four - if info[1].lower() == "place" and author == info[2]: - await connect_four.place_piece( - ctx, - info[3], - int(info[4]), + elif info[0].lower() == "hangman" and author == info[2]: + if info[1].lower() == "guess": + await self.bot.games.hangman.guess(ctx, *info[3:]) + elif info[1].lower() == "end": + await self.bot.games.hangman.stop(ctx, *info[3:]) + else: + raise InvalidInteraction(ctx.custom_id, info) + elif info[0].lower() == "connectfour": + connect_four = self.bot.games.connect_four + if info[1].lower() == "place" and author == info[2]: + await connect_four.place_piece( + ctx, + info[3], + int(info[4]), [int(info[5]), int(info[6])], int(info[7]), ctx.author_id, diff --git a/gwendolyn_old/utils/helper_classes.py b/gwendolyn_old/utils/helper_classes.py index cb13179..e026815 100644 --- a/gwendolyn_old/utils/helper_classes.py +++ b/gwendolyn_old/utils/helper_classes.py @@ -3,7 +3,7 @@ Contains classes used for utilities. *Classes* --------- - DatabaseFuncs() + DatabaseFuncs() """ import os # Used to test if files exist import time # Used to test how long it's been since commands were synced @@ -13,161 +13,161 @@ import discord # Used for type hints class DatabaseFuncs(): - """ - Manages database functions. + """ + Manages database functions. - *Methods* + *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 + 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* --------- - 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 - imdb_commands() + 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}) - def __init__(self, bot): - """Initialize the class.""" - self.bot = bot + 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 - def get_name(self, user_id: str): - """ - Get the name of a user you have the # id of. + return return_name - *Parameters: - ------------ - user_id: str - The id of the user you want the name of. The format is - "#" + str(discord.User.id) + def get_id(self, user_name: str): + """ + Get the id of a user you have the username of. - *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}) + *Parameters: + ------------ + user_name: str + The name of the user you want the id of. - 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 + *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) - return return_name + if user is not None: + return_id = user["_id"] + else: + self.bot.log("Couldn't find user "+user_name) + return_id = None - def get_id(self, user_name: str): - """ - Get the id of a user you have the username of. + return return_id - *Parameters: - ------------ - user_name: str - The name of the user you want the id of. + def delete_game(self, game_type: str, channel: str): + """ + Remove a game from the database. - *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) + *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}) - if user is not None: - return_id = user["_id"] - else: - self.bot.log("Couldn't find user "+user_name) - return_id = None + def wipe_games(self): + """Delete all running games and pull from git.""" + game_types = [ + "trivia questions", + "blackjack games", + "connect 4 games" + "hex games", + "wordle games" + ] + for game_type in game_types: + self.bot.database[game_type].delete_many({}) - return return_id + def connect_four_reaction_test(self, message: discord.Message, + user: discord.User): + """ + Test if the given message is the current connect four game. - def delete_game(self, game_type: str, channel: str): - """ - Remove a game from the database. + Also tests if the given user is the one who's turn it is. - *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}) + *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) - def wipe_games(self): - """Delete all running games and pull from git.""" - game_types = [ - "trivia questions", - "blackjack games", - "connect 4 games" - "hex games", - "wordle games" - ] - for game_type in game_types: - self.bot.database[game_type].delete_many({}) + 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", encoding="utf-8") as file_pointer: + old_image = int(file_pointer.read()) + else: + old_image = 0 - def connect_four_reaction_test(self, message: discord.Message, - user: discord.User): - """ - Test if the given message is the current connect four game. + 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 - Also tests if the given user is the one who's turn it is. + return valid_reaction - *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", encoding="utf-8") 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 - - 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) + 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/main.py b/main.py index 76ecc41..f2e64d6 100644 --- a/main.py +++ b/main.py @@ -4,13 +4,13 @@ from gwendolyn import Gwendolyn from gwendolyn.utils import make_files def main(): - """Starts the bot""" + """Starts the bot""" - make_files() + make_files() - # Creates the Bot - bot = Gwendolyn() - bot.start() + # Creates the Bot + bot = Gwendolyn() + bot.start() if __name__ == "__main__": - main() + main()