diff --git a/.gitignore b/.gitignore index affb8ee..65a36ed 100644 --- a/.gitignore +++ b/.gitignore @@ -157,6 +157,7 @@ gwendolyn/resources/star_wars/destinyPoints.txt gwendolyn/resources/plex/ gwendolyn/resources/games/hilo/ gwendolyn/resources/games/blackjack_tables/ +gwendolyn/resources/games/images/ gwendolyn/resources/games/old_images/ gwendolyn/resources/games/connect_four_boards/ gwendolyn/resources/games/hex_boards/ diff --git a/gwendolyn/cogs/game_cog.py b/gwendolyn/cogs/game_cog.py index 4388dd4..c038560 100644 --- a/gwendolyn/cogs/game_cog.py +++ b/gwendolyn/cogs/game_cog.py @@ -52,26 +52,6 @@ class BlackjackCog(commands.Cog): """Enter the game of blackjack with a bet.""" await self.bot.games.blackjack.enter_game(ctx, bet) - @cog_ext.cog_subcommand(**params["blackjack_stand"]) - async def blackjack_stand(self, ctx, hand=""): - """Stand on your hand in blackjack.""" - await self.bot.games.blackjack.stand(ctx, hand) - - @cog_ext.cog_subcommand(**params["blackjack_hit"]) - async def blackjack_hit(self, ctx, hand=0): - """Hit on your hand in blackjack.""" - await self.bot.games.blackjack.hit(ctx, hand) - - @cog_ext.cog_subcommand(**params["blackjack_double"]) - async def blackjack_double(self, ctx, hand=0): - """Double in blackjack.""" - await self.bot.games.blackjack.double(ctx, hand) - - @cog_ext.cog_subcommand(**params["blackjack_split"]) - async def blackjack_split(self, ctx, hand=0): - """Split your hand in blackjack.""" - await self.bot.games.blackjack.split(ctx, hand) - @cog_ext.cog_subcommand(**params["blackjack_hilo"]) async def blackjack_hilo(self, ctx): """Get the hilo value for the deck in blackjack.""" diff --git a/gwendolyn/cogs/lookup_cog.py b/gwendolyn/cogs/lookup_cog.py index 77c1114..fa5de7f 100644 --- a/gwendolyn/cogs/lookup_cog.py +++ b/gwendolyn/cogs/lookup_cog.py @@ -18,13 +18,13 @@ class LookupCog(commands.Cog): @cog_ext.cog_slash(**params["spell"]) async def spell(self, ctx, query): """Look up a spell.""" - await self.bot.lookup_funcs.spellFunc(ctx, query) + 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.monsterFunc(ctx, query) + await self.bot.lookup_funcs.monster_func(ctx, query) def setup(bot): diff --git a/gwendolyn/cogs/misc_cog.py b/gwendolyn/cogs/misc_cog.py index 3379b39..379eba0 100644 --- a/gwendolyn/cogs/misc_cog.py +++ b/gwendolyn/cogs/misc_cog.py @@ -1,6 +1,7 @@ """Contains the MiscCog, which deals with miscellaneous commands.""" from discord.ext import commands # Has the cog class from discord_slash import cog_ext # Used for slash commands +from discord_slash.context import SlashContext from gwendolyn.utils import get_params # pylint: disable=import-error @@ -19,77 +20,77 @@ class MiscCog(commands.Cog): self.nerd_shit = bot.other.nerd_shit @cog_ext.cog_slash(**params["ping"]) - async def ping(self, ctx): + 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): + 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, command=""): + async def help_command(self, ctx: SlashContext, command=""): """Get help for commands.""" await self.bot.other.helpFunc(ctx, command) @cog_ext.cog_slash(**params["thank"]) - async def thank(self, ctx): + 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): + async def hello(self, ctx: SlashContext): """Greet the bot.""" await self.bot.other.helloFunc(ctx) @cog_ext.cog_slash(**params["roll"]) - async def roll(self, ctx, dice="1d20"): + async def roll(self, ctx: SlashContext, dice="1d20"): """Roll dice.""" await self.bot.other.rollDice(ctx, dice) @cog_ext.cog_slash(**params["image"]) - async def image(self, ctx): + async def image(self, ctx: SlashContext): """Get a random image from Bing.""" await self.bot.other.imageFunc(ctx) @cog_ext.cog_slash(**params["movie"]) - async def movie(self, ctx): + async def movie(self, ctx: SlashContext): """Get a random movie from the Plex server.""" await self.bot.other.movieFunc(ctx) @cog_ext.cog_slash(**params["name"]) - async def name(self, ctx): + async def name(self, ctx: SlashContext): """Generate a random name.""" await self.generators.nameGen(ctx) @cog_ext.cog_slash(**params["tavern"]) - async def tavern(self, ctx): + async def tavern(self, ctx: SlashContext): """Generate a random tavern name.""" await self.generators.tavernGen(ctx) @cog_ext.cog_slash(**params["wiki"]) - async def wiki(self, ctx, page=""): + async def wiki(self, ctx: SlashContext, wiki_page=""): """Get a page on a fandom wiki.""" await self.bot.other.findWikiPage(ctx, page) @cog_ext.cog_slash(**params["add_movie"]) - async def add_movie(self, ctx, 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, 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, parameters="-d"): + 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, query): + async def wolf(self, ctx: SlashContext, query): """Perform a search on Wolfram Alpha.""" await self.nerd_shit.wolfSearch(ctx, query) diff --git a/gwendolyn/exceptions.py b/gwendolyn/exceptions.py new file mode 100644 index 0000000..c7340ee --- /dev/null +++ b/gwendolyn/exceptions.py @@ -0,0 +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) + +class InvalidInteraction(Exception): + def __init__(self, custom_id: str, decoded: str): + self.message = f"{custom_id = }, {decoded = }" + super().__init__(self.message) diff --git a/gwendolyn/funcs/games/blackjack.py b/gwendolyn/funcs/games/blackjack.py index 57c053a..1a22ce9 100644 --- a/gwendolyn/funcs/games/blackjack.py +++ b/gwendolyn/funcs/games/blackjack.py @@ -8,50 +8,100 @@ Runs commands, game logic and imaging for blackjack games. DrawBlackjack Draws images of the blackjack table. """ -import random # Used to shuffle the blackjack cards import math # Used for flooring decimal numbers import datetime # Used to generate the game id import asyncio # Used for sleeping -from shutil import copyfile -import discord # Used for discord.file -import discord_slash # Used for typehints -from PIL import Image, ImageDraw, ImageFont +from discord_slash.context import InteractionContext as IntCont # Used for +# typehints +from discord_slash.context import ComponentContext +from discord.abc import Messageable +from PIL import Image from gwendolyn.utils import replace_multiple +from .game_base import CardGame, CardDrawer -class Blackjack(): +WHITE = (255, 255, 255) +BLACK = (0, 0, 0) +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: discord_slash.context.SlashContext, - hand_number: int = 0) - double(ctx: discord_slash.context.SlashContext, - hand_number: int = 0) - stand(ctx: discord_slash.context.SlashContext, - hand_number: int = 0) - split(ctx: discord_slash.context.SlashContext, - hand_number: int = 0) - enter_game(ctx: discord_slash.context.SlashContext, bet: int) - start(ctx: discord_slash.context.SlashContext) - hilo(ctx: discord_slash.context.SlashContext) - shuffle(ctx: discord_slash.context.SlashContext) - cards(ctx: discord_slash.context.SlashContext) + 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.""" - self.bot = bot - self.draw = DrawBlackjack(bot) - self.decks = 4 - self.long_strings = self.bot.long_strings + super().__init__(bot, "blackjack", DrawBlackjack, 4) + default_buttons = ["Hit", "Stand", "Double", "Split"] + self.default_buttons = [(i, [i, "0"], 1) for i in default_buttons] - def _blackjack_shuffle(self, channel: str): + 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. + 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. @@ -61,26 +111,12 @@ class Blackjack(): channel: str The id of the channel where the cards will be used. """ - self.bot.log("Shuffling the blackjack deck") - - deck_path = "gwendolyn/resources/games/deck_of_cards.txt" - with open(deck_path, "r") as file_pointer: - deck = file_pointer.read() - - all_decks = deck.split("\n") * self.decks - random.shuffle(all_decks) - - blackjack_cards = self.bot.database["blackjack cards"] - cards = {"_id": channel} - card_updater = {"$set": {"_id": channel, "cards": all_decks}} - blackjack_cards.update_one(cards, card_updater, upsert=True) + super()._shuffle_cards(channel) # Creates hilo file self.bot.log(f"creating hilo doc for {channel}") - data = 0 - blackjack_hilo = self.bot.database["hilo"] - hilo_updater = {"$set": {"_id": channel, "hilo": data}} - blackjack_hilo.update_one({"_id": channel}, hilo_updater, upsert=True) + hilo_updater = {"$set": {"_id": channel, "hilo": 0}} + self._update_document(channel, hilo_updater, "hilo") def _calc_hand_value(self, hand: list): """ @@ -105,46 +141,21 @@ class Blackjack(): for card in hand: card_value = replace_multiple(card[0], ["0", "k", "q", "j"], "10") if card_value == "a": - length = len(values) - for i in range(length): - values.append(values[i] + 11) - values[i] += 1 + values = [i + 1 for i in values] + [i + 11 for i in values] else: - values = [int(i)+int(card_value) for i in values] + values = [i+int(card_value) for i in values] - values.sort() - - hand_value = values[0] - for value in values: - if value <= 21: - hand_value = value + 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): - """ - 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") - - blackjack_cards = self.bot.database["blackjack cards"] - drawn_card = blackjack_cards.find_one({"_id": channel})["cards"][0] - blackjack_cards.update_one({"_id": channel}, {"$pop": {"cards": -1}}) - value = self._calc_hand_value([drawn_card]) - - blackjack_hilo = self.bot.database["hilo"] - - if value <= 6: - blackjack_hilo.update_one({"_id": channel}, {"$inc": {"hilo": 1}}) - elif value >= 10: - blackjack_hilo.update_one({"_id": channel}, {"$inc": {"hilo": -1}}) + 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 @@ -162,23 +173,19 @@ class Blackjack(): done: bool Whether the dealer is done drawing cards. """ - game = self.bot.database["blackjack games"].find_one({"_id": channel}) - - done = False + game = self.access_document(channel) dealer_hand = game["dealer hand"] - blackjack_games = self.bot.database["blackjack games"] - if self._calc_hand_value(dealer_hand) < 17: dealer_hand.append(self._draw_card(channel)) dealer_updater = {"$set": {"dealer hand": dealer_hand}} - blackjack_games.update_one({"_id": channel}, dealer_updater) + self._update_document(channel, dealer_updater) + done = False else: done = True if self._calc_hand_value(dealer_hand) > 21: - dealer_updater = {"$set": {"dealer busted": True}} - blackjack_games.update_one({"_id": channel}, dealer_updater) + self._update_document(channel, {"$set": {"dealer busted": True}}) return done @@ -201,60 +208,52 @@ class Blackjack(): If the game has finished. """ self.bot.log("Continuing blackjack game") - game = self.bot.database["blackjack games"].find_one({"_id": channel}) - - done = False - - blackjack_games = self.bot.database["blackjack games"] - blackjack_games.update_one({"_id": channel}, {"$inc": {"round": 1}}) + 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 - message = self.long_strings["Blackjack all players standing"] + done = False if game["all standing"]: self.bot.log("All are standing") - done = self._dealer_draw(channel) message = "The dealer draws a card." - blackjack_games.find_one({"_id": channel}) - self.bot.log("Testing if all are standing") - for user in game["user hands"]: - user_hand = game["user hands"][user] - test_parameters = [user_hand, all_standing, pre_all_standing, True] - standing_test = (self._test_if_standing(*test_parameters)) - new_user, all_standing, pre_all_standing = standing_test - hand_updater = {"$set": {"user hands."+user: new_user}} - blackjack_games.update_one({"_id": channel}, hand_updater) + 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: - game_updater = {"$set": {"all standing": True}} - blackjack_games.update_one({"_id": channel}, game_updater) + self._update_document(channel, {"$set": {"all standing": True}}) - self.draw.draw_image(channel) + if done: + message = "The dealer is done drawing cards" - if all_standing: - if not done: - return message, True, done - else: - return "The dealer is done drawing cards", True, done - elif pre_all_standing: + 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 = self.long_strings["Blackjack commands"] + send_message = send_message.format("") - 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 - return send_message, False, done - - def _test_if_standing(self, hand: dict, all_standing: bool, - pre_all_standing: bool, top_level: bool): + def _test_if_standing(self, user_hands: list): """ Test if a player is standing on all their hands. @@ -263,16 +262,7 @@ class Blackjack(): *Parameters* ------------ hand: dict - The hand to test and reset. - all_standing: bool - Is set to True at the top level. If it's false, the - player is not standing on one of the previously tested - hands. - pre_all_standing: bool - Is set to True at the top level. - 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. + The hands to test and reset. *Returns* --------- @@ -286,60 +276,26 @@ class Blackjack(): the newly drawn card. """ # If the user has not hit, they are by definition standing. - if not hand["hit"]: - hand["standing"] = True + all_standing = True + pre_all_standing = True - if not hand["standing"]: - all_standing = False - - if self._calc_hand_value(hand["hand"]) >= 21 or hand["doubled"]: + for hand in user_hands: + if not hand["hit"]: hand["standing"] = True - else: - pre_all_standing = False - hand["hit"] = False + if not hand["standing"]: + all_standing = False - if top_level: - if hand["split"] >= 1: - test_hand = hand["other hand"] - test_parameters = [ - test_hand, - all_standing, - pre_all_standing, - False - ] - standing_test = (self._test_if_standing(*test_parameters)) - hand["other hand"] = standing_test[0] - all_standing = standing_test[1] - pre_all_standing = standing_test[2] - if hand["split"] >= 2: - test_hand = hand["third hand"] - test_parameters = [ - test_hand, - all_standing, - pre_all_standing, - False - ] - standing_test = (self._test_if_standing(*test_parameters)) - hand["third hand"] = standing_test[0] - all_standing = standing_test[1] - pre_all_standing = standing_test[2] - if hand["split"] >= 3: - test_hand = hand["fourth hand"] - test_parameters = [ - test_hand, - all_standing, - pre_all_standing, - False - ] - standing_test = (self._test_if_standing(*test_parameters)) - hand["fourth hand"] = standing_test[0] - all_standing = standing_test[1] - pre_all_standing = standing_test[2] + if self._calc_hand_value(hand["hand"]) >= 21 or hand["doubled"]: + hand["standing"] = True + else: + pre_all_standing = False - return hand, all_standing, pre_all_standing + hand["hit"] = False - def _blackjack_finish(self, channel: str): + return user_hands, all_standing, pre_all_standing + + async def _blackjack_finish(self, channel: Messageable): """ Generate the winnings message after the blackjack game ends. @@ -355,51 +311,37 @@ class Blackjack(): """ final_winnings = "*Final Winnings:*\n" - game = self.bot.database["blackjack games"].find_one({"_id": channel}) - - dealer_value = self._calc_hand_value(game["dealer hand"]) - dealer_blackjack = game["dealer blackjack"] - dealer_busted = game["dealer busted"] + game = self.access_document(str(channel.id)) for user in game["user hands"]: - calc_winnings_parameters = [ + winnings, net_winnings, reason = self._calc_winning( game["user hands"][user], - dealer_value, - True, - dealer_blackjack, - dealer_busted - ] - winnings_calc = (self._calc_winning(*calc_winnings_parameters)) - winnings, net_winnings, reason = winnings_calc + 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: - if winnings == -1: - final_winnings += "{} lost 1 GwendoBuck {}\n".format( - user_name, - reason - ) - else: - money_lost = -1 * winnings - winnings_text = f"{user_name} lost {money_lost} GwendoBucks" - winnings_text += f" {reason}\n" - final_winnings += winnings_text + final_winnings += f"{user_name} lost" else: - if winnings == 1: - final_winnings += f"{user_name} won 1 GwendoBuck {reason}\n" - else: - winnings_text = f"{user_name} won {winnings} GwendoBucks" - winnings_text += f" {reason}\n" - final_winnings += winnings_text + 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) - self.bot.database["blackjack games"].delete_one({"_id": channel}) + await self._end_game(channel) return final_winnings - def _calc_winning(self, hand: dict, dealer_value: int, top_level: bool, + 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. @@ -431,152 +373,32 @@ class Blackjack(): """ self.bot.log("Calculating winnings") reason = "" - bet = hand["bet"] - winnings = -1 * bet + winnings = 0 net_winnings = 0 - hand_value = self._calc_hand_value(hand["hand"]) - if hand["blackjack"] and not dealer_blackjack: - reason += "(blackjack)" - winnings += math.floor(2.5 * bet) - net_winnings += math.floor(2.5 * bet) - elif dealer_blackjack: - reason += "(dealer blackjack)" - elif hand["busted"]: - reason += "(busted)" - else: - if dealer_busted: - reason = "(dealer busted)" - winnings += 2 * bet - net_winnings += 2 * bet - elif hand_value > dealer_value: - winnings += 2 * bet - net_winnings += 2 * bet - reason = "(highest value)" - elif hand_value == dealer_value: - reason = "(pushed)" - winnings += bet - net_winnings += bet - else: - reason = "(highest value)" + for hand in user_hands: + winnings += -1 * hand["bet"] + hand_value = self._calc_hand_value(hand["hand"]) - if top_level: - if hand["split"] >= 1: - calc_winnings_parameters = [ - hand["other hand"], - dealer_value, - False, - dealer_blackjack, - dealer_busted - ] - winnings_calc = self._calc_winning(*calc_winnings_parameters) - winnings_temp, net_winnings_temp, reason_temp = winnings_calc - winnings += winnings_temp - net_winnings += net_winnings_temp - reason += reason_temp - if hand["split"] >= 2: - calc_winnings_parameters = [ - hand["third hand"], - dealer_value, - False, - dealer_blackjack, - dealer_busted - ] - winnings_calc = self._calc_winning(*calc_winnings_parameters) - winnings_temp, net_winnings_temp, reason_temp = winnings_calc - winnings += winnings_temp - net_winnings += net_winnings_temp - reason += reason_temp - if hand["split"] >= 3: - calc_winnings_parameters = [ - hand["fourth hand"], - dealer_value, - False, - dealer_blackjack, - dealer_busted - ] - winnings_calc = self._calc_winning(*calc_winnings_parameters) - winnings_temp, net_winnings_temp, reason_temp = winnings_calc - winnings += winnings_temp - net_winnings += net_winnings_temp - reason += reason_temp + 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 - def _get_hand_number(self, user: dict, hand_number: int): - """ - Get the hand with the given number. - - *Parameters* - ------------ - user: dict - The full hand dict of the user. - hand_number: int - The number of the hand to get. - - *Returns* - --------- - hand: dict - The hand. - hand_number: int - The same as hand_number, except if the user hasn't - split. If the user hasn't split, returns 0. - """ - hand = None - - if user["split"] == 0: - hand = user - hand_number = 0 - else: - if hand_number != 0: - if hand_number == 1: - hand = user - elif hand_number == 2: - hand = user["other hand"] - elif hand_number == 3: - hand = user["third hand"] - elif hand_number == 4: - hand = user["fourth hand"] - - return hand, hand_number - - def _is_round_done(self, 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. - """ - round_done = True - - for person in game["user hands"].values(): - if (not person["hit"]) and (not person["standing"]): - round_done = False - - if person["split"] > 0: - if not person["other hand"]["hit"]: - if not person["other hand"]["standing"]: - round_done = False - - if person["split"] > 1: - if not person["third hand"]["hit"]: - if not person["third hand"]["standing"]: - round_done = False - - if person["split"] > 2: - if not person["fourth hand"]["hit"]: - if not person["fourth hand"]["standing"]: - round_done = False - - return round_done - async def _blackjack_loop(self, channel, game_round: int, game_id: str): """ Run blackjack logic and continue if enough time passes. @@ -590,136 +412,99 @@ class Blackjack(): game_id: str The ID of the game. """ - self.bot.log("Loop "+str(game_round), str(channel.id)) + self.bot.log(f"Loop {game_round}", str(channel.id)) - old_images_path = "gwendolyn/resources/games/old_images/" - old_image_path = old_images_path + f"blackjack{channel.id}" - with open(old_image_path, "r") as file_pointer: - old_image = await channel.fetch_message(int(file_pointer.read())) - - continue_data = (self._blackjack_continue(str(channel.id))) - new_message, all_standing, game_done = continue_data + 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 old_image.delete() - tables_path = "gwendolyn/resources/games/blackjack_tables/" - file_path = f"{tables_path}blackjack_table{channel.id}.png" - old_image = await channel.send(file=discord.File(file_path)) - with open(old_image_path, "w") as file_pointer: - file_pointer.write(str(old_image.id)) + await self._delete_old_image(channel) + buttons = None if all_standing else self.default_buttons + await self._send_image(channel, buttons) - if all_standing: - await asyncio.sleep(5) - else: - await asyncio.sleep(120) + await asyncio.sleep([120,5][all_standing]) - blackjack_games = self.bot.database["blackjack games"] - game = blackjack_games.find_one({"_id": str(channel.id)}) + if not self._test_document(str(channel.id)): + self.bot.log(f"Ending loop on round {game_round}", str(channel.id)) + return - if game is None: - right_round = False - else: - real_round = game["round"] or -1 - real_id = game["game_id"] or -1 - right_round = game_round == real_round and game_id == real_id + 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 right_round: - if not game_done: - log_message = f"Loop {game_round} starting a new blackjack loop" - self.bot.log(log_message, str(channel.id)) - await self._blackjack_loop(channel, game_round+1, game_id) - else: - new_message = self._blackjack_finish(str(channel.id)) - await channel.send(new_message) - else: - log_message = f"Ending loop on round {game_round}" + if not game_done: + log_message = f"Loop {game_round} starting a new blackjack loop" self.bot.log(log_message, str(channel.id)) + await self._blackjack_loop(channel, game_round+1, game_id) + else: + await channel.send(await self._blackjack_finish(channel)) - async def hit(self, ctx: discord_slash.context.SlashContext, - hand_number: int = 0): + 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: discord_slash.context.SlashContext + ctx: IntCont The context of the command. - hand_number: int = 0 + 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}" - round_done = False - blackjack_games = self.bot.database["blackjack games"] - game = blackjack_games.find_one({"_id": channel}) + game = self.access_document(channel) + if not await self._test_command(game, user, "hit", hand_number, ctx): + return - if user in game["user hands"]: + hand = game["user hands"][user][max(hand_number-1,0)] + hand["hand"].append(self._draw_card(channel)) + hand["hit"] = True - user_hands = game["user hands"][user] - hand, hand_number = self._get_hand_number(user_hands, hand_number) + hand_value = self._calc_hand_value(hand["hand"]) - if hand is None: - log_message = "They didn't specify a hand" - send_message = "You need to specify a hand" - elif game["round"] <= 0: - log_message = "They tried to hit on the 0th round" - send_message = "You can't hit before you see your cards" - elif hand["hit"]: - log_message = "They've already hit this round" - send_message = "You've already hit this round" - elif hand["standing"]: - log_message = "They're already standing" - send_message = "You can't hit when you're standing" - else: - hand["hand"].append(self._draw_card(channel)) - hand["hit"] = True + if hand_value > 21: + hand["busted"] = True - hand_value = self._calc_hand_value(hand["hand"]) + hand_path = f"user hands.{user}.{max(hand_number-1,0)}" - if hand_value > 21: - hand["busted"] = True + self._update_document(channel, {"$set": {hand_path: hand}}) + game = self.access_document(channel) - if hand_number == 2: - hand_path = f"user hands.{user}.other hand" - elif hand_number == 3: - hand_path = f"user hands.{user}.third hand" - elif hand_number == 4: - hand_path = f"user hands.{user}.fourth hand" - else: - hand_path = f"user hands.{user}" + await ctx.send(f"{ctx.author.display_name} hit") + self.bot.log("They succeeded") - game_updater = {"$set": {hand_path: hand}} - blackjack_games.update_one({"_id": channel}, game_updater) - game = blackjack_games.find_one({"_id": channel}) - round_done = self._is_round_done(game) - - send_message = f"{ctx.author.display_name} hit" - log_message = "They succeeded" - else: - log_message = "They tried to hit without being in the game" - send_message = "You have to enter the game before you can hit" - - await ctx.send(send_message) - self.bot.log(log_message) - - if round_done: + 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) + await self._blackjack_loop( + ctx.channel, game["round"]+1, game_id + ) - async def double(self, ctx: discord_slash.context.SlashContext, - hand_number: int = 0): + async def _double(self, ctx: IntCont, hand_number: int = 0): """ Double a hand. *Parameters* ------------ - ctx: discord_slash.context.SlashContext + ctx: IntCont The context of the command. hand_number: int = 0 The number of the hand to double. @@ -727,86 +512,59 @@ class Blackjack(): await self.bot.defer(ctx) channel = str(ctx.channel_id) user = f"#{ctx.author.id}" - round_done = False - blackjack_games = self.bot.database["blackjack games"] - game = blackjack_games.find_one({"_id": channel}) + game = self.access_document(channel) - if user in game["user hands"]: - hand_parameters = [game["user hands"][user], hand_number] - hand, hand_number = self._get_hand_number(*hand_parameters) + balance = self.bot.money.checkBalance(user) - if hand is None: - log_message = "They didn't specify a hand" - send_message = "You need to specify a hand" - elif game["round"] <= 0: - log_message = "They tried to hit on the 0th round" - send_message = "You can't hit before you see your cards" - elif hand["hit"]: - log_message = "They've already hit this round" - send_message = "You've already hit this round" - elif hand["standing"]: - log_message = "They're already standing" - send_message = "You can't hit when you're standing" - elif len(hand["hand"]) != 2: - log_message = "They tried to double after round 1" - send_message = "You can only double on the first round" - elif self.bot.money.checkBalance(user) < hand["bet"]: - log_message = "They tried to double without being in the game" - send_message = "You can't double when you're not in the game" - else: - bet = hand["bet"] - self.bot.money.addMoney(user, -1 * bet) + if not await self._test_command(game, user, "double", hand_number, ctx): + return - hand["hand"].append(self._draw_card(channel)) - hand["hit"] = True - hand["doubled"] = True - hand["bet"] += bet - - hand_value = self._calc_hand_value(hand["hand"]) - - if hand_value > 21: - hand["busted"] = True - - if hand_number == 2: - hand_path = f"user hands.{user}.other hand" - elif hand_number == 3: - hand_path = f"user hands.{user}.third hand" - elif hand_number == 4: - hand_path = f"user hands.{user}.fourth hand" - else: - hand_path = f"user hands.{user}" - - game_updater = {"$set": {hand_path: hand}} - blackjack_games.update_one({"_id": channel}, game_updater) - - game = blackjack_games.find_one({"_id": channel}) - round_done = self._is_round_done(game) - - send_message = self.long_strings["Blackjack double"] - user_name = self.bot.database_funcs.get_name(user) - send_message = send_message.format(bet, user_name) - log_message = "They succeeded" + 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: - log_message = "They tried to double without being in the game" - send_message = "You can't double when you're not in the game" + hand = game["user hands"][user][max(hand_number-1,0)] + self.bot.money.addMoney(user, -1 * hand["bet"]) - await ctx.send(send_message) - self.bot.log(log_message) + hand["hand"].append(self._draw_card(channel)) + hand["hit"] = True + hand["doubled"] = True + hand["bet"] += hand["bet"] - if round_done: - game_id = game["game_id"] - self.bot.log("Double calling self._blackjack_loop()", channel) - await self._blackjack_loop(ctx.channel, game["round"]+1, game_id) + if self._calc_hand_value(hand["hand"]) > 21: + hand["busted"] = True - async def stand(self, ctx: discord_slash.context.SlashContext, - hand_number: int = 0): + 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: discord_slash.context.SlashContext + ctx: IntCont The context of the command. hand_number: int = 0 The number of the hand to stand on. @@ -814,67 +572,37 @@ class Blackjack(): await self.bot.defer(ctx) channel = str(ctx.channel_id) user = f"#{ctx.author.id}" - round_done = False - blackjack_games = self.bot.database["blackjack games"] - game = blackjack_games.find_one({"_id": channel}) + game = self.access_document(channel) - if game is not None and user in game["user hands"]: - hand_parameters = [game["user hands"][user], hand_number] - hand, hand_number = self._get_hand_number(*hand_parameters) + if not await self._test_command(game, user, "stand", hand_number, ctx): + return - if hand is None: - send_message = "You need to specify which hand" - log_message = "They didn't specify a hand" - elif game["round"] <= 0: - send_message = "You can't stand before you see your cards" - log_message = "They tried to stand on round 0" - elif hand["hit"]: - send_message = "You've already hit this round" - log_message = "They'd already hit this round" - elif hand["standing"]: - send_message = "You're already standing" - log_message = "They're already standing" - else: - hand["standing"] = True + hand = game["user hands"][user][max(hand_number-1,0)] + hand["standing"] = True - if hand_number == 2: - hand_path = f"user hands.{user}.other hand" - elif hand_number == 3: - hand_path = f"user hands.{user}.third hand" - elif hand_number == 4: - hand_path = f"user hands.{user}.fourth hand" - else: - hand_path = f"user hands.{user}" + hand_path = f"user hands.{user}.{max(hand_number-1,0)}" - game_updater = {"$set": {hand_path: hand}} - blackjack_games.update_one({"_id": channel}, game_updater) - game = blackjack_games.find_one({"_id": channel}) - round_done = self._is_round_done(game) - - send_message = f"{ctx.author.display_name} is standing" - log_message = "They succeeded" - - else: - log_message = "They tried to stand without being in the game" - send_message = "You have to enter the game before you can stand" + 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 round_done: + 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: discord_slash.context.SlashContext, - hand_number: int = 0): + + async def _split(self, ctx: IntCont, hand_number: int = 0): """ Split a hand. *Parameters* ------------ - ctx: discord_slash.context.SlashContext + ctx: IntCont The context of the command. hand_number: int = 0 The number of the hand to split. @@ -882,141 +610,72 @@ class Blackjack(): await self.bot.defer(ctx) channel = str(ctx.channel_id) user = f"#{ctx.author.id}" - round_done = False - hand_number_error = False - blackjack_games = self.bot.database["blackjack games"] - game = blackjack_games.find_one({"_id": channel}) + game = self.access_document(channel) - if game["user hands"][user]["split"] == 0: - hand = game["user hands"][user] - new_hand = game["user hands"][user]["other hand"] - hand_number = 0 - other_hand = 2 + 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: - if hand_number == 1: - hand = game["user hands"][user] - elif hand_number == 2: - hand = game["user hands"][user]["other hand"] - elif hand_number == 3: - hand = game["user hands"][user]["third hand"] - else: - hand_number_error = True + self.bot.money.addMoney(user, -1 * old_hand["bet"]) - if game["user hands"][user]["split"] == 1: - new_hand = game["user hands"][user]["third hand"] - other_hand = 3 - else: - new_hand = game["user hands"][user]["fourth hand"] - other_hand = 4 + old_hand["hit"] = True - if hand_number_error: - log_message = "They didn't specify a hand" - send_message = "You have to specify the hand you're hitting with" - elif game["round"] == 0: - log_message = "They tried to split on round 0" - send_message = "You can't split before you see your cards" - elif game["user hands"][user]["split"] > 3: - log_message = "They tried to split more than three times" - send_message = "You can only split 3 times" - elif hand["hit"]: - log_message = "They've already hit" - send_message = "You've already hit or split this hand." - elif hand["standing"]: - log_message = "They're already standing" - send_message = "You're already standing" - elif len(hand["hand"]) != 2: - log_message = "They tried to split after the first round" - send_message = "You can only split on the first round" - else: - first_card = self._calc_hand_value([hand["hand"][0]]) - second_card = self._calc_hand_value([hand["hand"][1]]) - if first_card != second_card: - log_message = "They tried to split two different cards" - send_message = self.long_strings["Blackjack different cards"] - else: - bet = hand["bet"] - if self.bot.money.checkBalance(user) < bet: - log_message = "They didn't have enough GwendoBucks" - send_message = "You don't have enough GwendoBucks" - else: - self.bot.money.addMoney(user, -1 * bet) + 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)) - hand["hit"] = True - new_hand["hit"] = True + 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 - new_hand = { - "hand": [], "bet": 0, "standing": False, - "busted": False, "blackjack": False, "hit": True, - "doubled": False - } + game["user hands"][user][max(hand_number-1,0)] = hands[0] + game["user hands"][user].append(hands[1]) - new_hand["bet"] = hand["bet"] + updater = {"$set": {f"user hands.{user}": game["user hands"][user]}} + self._update_document(channel, updater) - new_hand["hand"].append(hand["hand"].pop(1)) - new_hand["hand"].append(self._draw_card(channel)) - hand["hand"].append(self._draw_card(channel)) - hand["hit"] = True + send_message = self.long_strings["Blackjack split"] + user_name = self.bot.database_funcs.get_name(user) - hand_value = self._calc_hand_value(hand["hand"]) - other_hand_value = self._calc_hand_value(new_hand["hand"]) - if hand_value > 21: - hand["busted"] = True - elif hand_value == 21: - hand["blackjack"] = True + await ctx.send(send_message.format(user_name)) + self.bot.log("They succeeded") - if other_hand_value > 21: - new_hand["busted"] = True - elif other_hand_value == 21: - new_hand["blackjack"] = True + 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 + ) - if hand_number == 2: - hand_path = f"user hands.{user}.other hand" - elif hand_number == 3: - hand_path = f"user hands.{user}.third hand" - else: - hand_path = f"user hands.{user}" - - game_updater = {"$set": {hand_path: hand}} - blackjack_games.update_one({"_id": channel}, game_updater) - - if other_hand == 3: - other_hand_path = f"user hands.{user}.third hand" - elif other_hand == 4: - other_hand_path = f"user hands.{user}.fourth hand" - else: - other_hand_path = f"user hands.{user}.other hand" - - game_updater = {"$set": {other_hand_path: new_hand}} - blackjack_games.update_one({"_id": channel}, game_updater) - - split_updater = {"$inc": {"user hands."+user+".split": 1}} - blackjack_games.update_one({"_id": channel}, split_updater) - - game = blackjack_games.find_one({"_id": channel}) - round_done = self._is_round_done(game) - - send_message = self.long_strings["Blackjack split"] - user_name = self.bot.database_funcs.get_name(user) - send_message = send_message.format(user_name) - log_message = "They succeeded" - - await ctx.send(send_message) - self.bot.log(log_message) - - if round_done: - game_id = game["game_id"] - self.bot.log("Stand calling self._blackjack_loop()", channel) - await self._blackjack_loop(ctx.channel, game["round"]+1, game_id) - - async def enter_game(self, ctx: discord_slash.context.SlashContext, - bet: int): + async def enter_game(self, ctx: IntCont, bet: int): """ Enter the blackjack game. *Parameters* ------------ - ctx: discord_slash.context.SlashContext + ctx: IntCont The context of the command. bet: int The bet to enter with. @@ -1024,63 +683,52 @@ class Blackjack(): await self.bot.defer(ctx) channel = str(ctx.channel_id) user = f"#{ctx.author.id}" - collection = self.bot.database["blackjack games"] - game = collection.find_one({"_id": channel}) + + game = 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 game is None: - send_message = "There is no game going on in this channel" - log_message = send_message - elif user in game["user hands"]: - send_message = "You're already in the game!" - log_message = "They're already in the 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: - send_message = "There can't be more than 5 players in a game" - log_message = "There were already 5 players in the game" + 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: - send_message = "The table is no longer taking bets" - log_message = "They tried to join after the game begun" + await ctx.send("The table is no longer taking bets") + self.bot.log("They tried to join after the game begun") elif bet < 0: - send_message = "You can't bet a negative amount" - log_message = "They tried to bet a negative amount" + 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: - send_message = "You don't have enough GwendoBucks" - log_message = "They didn't have enough GwendoBucks" + 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)] - hand_value = self._calc_hand_value(player_hand) - - blackjack_hand = (hand_value == 21) - - new_hand = { + new_hand = [{ "hand": player_hand, "bet": bet, "standing": False, - "busted": False, "blackjack": blackjack_hand, - "hit": True, "doubled": False, "split": 0, - "other hand": {}, "third hand": {}, "fourth hand": {} - } + "busted": False, + "blackjack": self._calc_hand_value(player_hand) == 21, + "hit": True, "doubled": False + }] - function = {"$set": {f"user hands.{user}": new_hand}} - collection.update_one({"_id": channel}, function) + updater = {"$set": {f"user hands.{user}": new_hand}} + self._update_document(channel, updater) - enter_game_text = "entered the game with a bet of" - bet_text = f"{bet} GwendoBucks" - send_message = f"{user_name} {enter_game_text} {bet_text}" - log_message = send_message + 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)) - self.bot.log(log_message) - await ctx.send(send_message) - - async def start(self, ctx: discord_slash.context.SlashContext): + async def start(self, ctx: IntCont): """ Start a blackjack game. *Parameters* ------------ - ctx: discord_slash.context.SlashContext + ctx: IntCont The context of the command. """ await self.bot.defer(ctx) @@ -1090,165 +738,178 @@ class Blackjack(): self.bot.log("Starting blackjack game") await ctx.send("Starting a new game of blackjack") cards_left = 0 - cards = self.bot.database["blackjack cards"].find_one({"_id": channel}) - if cards is not None: + 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._blackjack_shuffle(channel) - self.bot.log("Shuffling the blackjack deck...", channel) + self._shuffle_cards(channel) await ctx.channel.send("Shuffling the deck...") - game = self.bot.database["blackjack games"].find_one({"_id": channel}) + self.bot.log(f"Trying to start a blackjack game in {channel}") - self.bot.log("Trying to start a blackjack game in "+channel) - game_started = False - - if game is None: - - dealer_hand = [self._draw_card(channel), self._draw_card(channel)] - game_id = datetime.datetime.now().strftime('%Y%m%d%H%M%S') - - new_game = { - "_id": channel, "dealer hand": dealer_hand, - "dealer busted": False, "dealer blackjack": False, - "user hands": {}, "all standing": False, "round": 0, - "game_id": game_id - } - - if self._calc_hand_value(dealer_hand) == 21: - new_game["dealer blackjack"] = True - - self.bot.database["blackjack games"].insert_one(new_game) - - table_images_path = "gwendolyn/resources/games/blackjack_tables/" - empty_table_image = "gwendolyn/resources/games/blackjack_table.png" - new_table_image = f"{table_images_path}blackjack_table{channel}.png" - copyfile(empty_table_image, new_table_image) - - game_started = True - - if game_started: - send_message = self.long_strings["Blackjack started"] - await ctx.channel.send(send_message) - - table_images_path = "gwendolyn/resources/games/blackjack_tables/" - file_path = f"{table_images_path}blackjack_table{channel}.png" - - old_image = await ctx.channel.send(file=discord.File(file_path)) - old_images_path = "gwendolyn/resources/games/old_images/blackjack" - - with open(old_images_path+channel, "w") as file_pointer: - file_pointer.write(str(old_image.id)) - - await asyncio.sleep(30) - - game_done = False - - blackjack_games = self.bot.database["blackjack games"] - game = blackjack_games.find_one({"_id": channel}) - - if len(game["user hands"]) == 0: - game_done = True - send_message = "No one entered the game. Ending the game." - await ctx.channel.send(send_message) - - game_id = game["game_id"] - - # Loop of game rounds - if not game_done: - self.bot.log("start() calling _blackjack_loop()", channel) - await self._blackjack_loop(ctx.channel, 1, game_id) - else: - new_message = self._blackjack_finish(channel) - await ctx.channel.send(new_message) - else: - send_message = self.long_strings["Blackjack going on"] - await ctx.channel.send(send_message) + 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 hilo(self, ctx: discord_slash.context.SlashContext): + dealer_hand = [self._draw_card(channel), self._draw_card(channel)] + game_id = datetime.datetime.now().strftime('%Y%m%d%H%M%S') + + new_game = { + "_id": channel, "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: discord_slash.context.SlashContext + ctx: IntCont The context of the command. """ - channel = ctx.channel_id - data = self.bot.database["hilo"].find_one({"_id": str(channel)}) - if data is not None: - hilo = str(data["hilo"]) - else: - hilo = "0" + 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: discord_slash.context.SlashContext): + async def shuffle(self, ctx: IntCont): """ Shuffle the cards used for blackjack. *Parameters* ------------ - ctx: discord_slash.context.SlashContext + ctx: IntCont The context of the command. """ - channel = ctx.channel_id - self._blackjack_shuffle(str(channel)) - self.bot.log("Shuffling the blackjack deck...", str(channel)) + self._shuffle_cards(str(ctx.channel_id)) await ctx.send("Shuffling the deck...") - async def cards(self, ctx: discord_slash.context.SlashContext): + async def cards(self, ctx: IntCont): """ Get how many cards are left for blackjack. *Parameters* ------------ - ctx: discord_slash.context.SlashContext + ctx: IntCont The context of the command. """ - channel = ctx.channel_id - cards_left = 0 - blackjack_games = self.bot.database["blackjack cards"] - cards = blackjack_games.find_one({"_id": str(channel)}) - if cards is not None: - cards_left = len(cards["cards"]) + 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 - decks_left = round(cards_left/52, 1) send_message = f"Cards left:\n{cards_left} cards, {decks_left} decks" await ctx.send(send_message, hidden=True) + 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(): + +class DrawBlackjack(CardDrawer): """ Draws the blackjack image. *Methods* --------- - draw_image(channel: str) *Attributes* ------------ bot: Gwendolyn The instance of the bot. - BORDER: int - The size of the border in pixels. PLACEMENT: list The order to place the user hands in. """ # pylint: disable=too-few-public-methods - def __init__(self, bot): + def __init__(self, bot, game: Blackjack): """Initialize the class.""" - self.bot = bot + super().__init__(bot, game) + # pylint: disable=invalid-name - self.BORDER = 100 - self.SMALLBORDER = int(self.BORDER/3.5) self.PLACEMENT = [2, 1, 3, 0, 4] # pylint: enable=invalid-name + self._set_card_size(197) - def draw_image(self, channel: str): + 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): """ Draw the table image. @@ -1257,140 +918,8 @@ class DrawBlackjack(): channel: str The id of the channel the game is in. """ - self.bot.log("Drawing blackjack table", channel) - game = self.bot.database["blackjack games"].find_one({"_id": channel}) - - fonts_path = "gwendolyn/resources/fonts/" - font = ImageFont.truetype(fonts_path+'futura-bold.ttf', 50) - small_font = ImageFont.truetype(fonts_path+'futura-bold.ttf', 40) - - table = Image.open("gwendolyn/resources/games/blackjack_table.png") - text_image = ImageDraw.Draw(table) - hands = game["user hands"] - - dealer_busted = game["dealer busted"] - dealer_blackjack = game["dealer blackjack"] - - if not game["all standing"]: - hand_parameters = [ - game["dealer hand"], - True, - False, - False - ] - else: - hand_parameters = [ - game["dealer hand"], - False, - dealer_busted, - dealer_blackjack - ] - - dealer_hand = self._draw_hand(*hand_parameters) - - paste_position = (800-self.SMALLBORDER, 20-self.SMALLBORDER) - table.paste(dealer_hand, paste_position, dealer_hand) - - for i in range(len(hands)): - key, value = list(hands.items())[i] - key = self.bot.database_funcs.get_name(key) - hand_parameters = [ - value["hand"], - False, - value["busted"], - value["blackjack"] - ] - user_hand = self._draw_hand(*hand_parameters) - position_x = 32-self.SMALLBORDER+(384*self.PLACEMENT[i]) - - if value["split"] >= 1: - hand_parameters_two = [ - value["other hand"]["hand"], - False, - value["other hand"]["busted"], - value["other hand"]["blackjack"] - ] - user_other_hand = self._draw_hand(*hand_parameters_two) - - if value["split"] >= 2: - hand_parameters_three = [ - value["third hand"]["hand"], - False, - value["third hand"]["busted"], - value["third hand"]["blackjack"] - ] - user_third_hand = self._draw_hand(*hand_parameters_three) - - if value["split"] >= 3: - hand_parameters_four = [ - value["fourth hand"]["hand"], - False, - value["fourth hand"]["busted"], - value["fourth hand"]["blackjack"] - ] - user_fourth_hand = self._draw_hand(*hand_parameters_four) - - if value["split"] == 3: - position_one = (position_x, 280-self.SMALLBORDER) - position_two = (position_x, 420-self.SMALLBORDER) - position_three = (position_x, 560-self.SMALLBORDER) - position_four = (position_x, 700-self.SMALLBORDER) - - table.paste(user_hand, position_one, user_hand) - table.paste(user_other_hand, position_two, user_other_hand) - table.paste(user_third_hand, position_three, user_third_hand) - table.paste(user_fourth_hand, position_four, user_fourth_hand) - elif value["split"] == 2: - position_one = (position_x, 420-self.SMALLBORDER) - position_two = (position_x, 560-self.SMALLBORDER) - position_three = (position_x, 700-self.SMALLBORDER) - - table.paste(user_hand, position_one, user_hand) - table.paste(user_other_hand, position_two, user_other_hand) - table.paste(user_third_hand, position_three, user_third_hand) - elif value["split"] == 1: - position_one = (position_x, 560-self.SMALLBORDER) - position_two = (position_x, 700-self.SMALLBORDER) - - table.paste(user_hand, position_one, user_hand) - table.paste(user_other_hand, position_two, user_other_hand) - else: - position_one = (position_x, 680-self.SMALLBORDER) - table.paste(user_hand, position_one, user_hand) - - text_width = font.getsize(key)[0] - text_x = 32+(384*self.PLACEMENT[i])+117-int(text_width/2) - black = (0, 0, 0) - white = (255, 255, 255) - - if text_width < 360: - # Black shadow behind and slightly below white text - text_image.text((text_x-3, 1010-3), key, fill=black, font=font) - text_image.text((text_x+3, 1010-3), key, fill=black, font=font) - text_image.text((text_x-3, 1010+3), key, fill=black, font=font) - text_image.text((text_x+3, 1010+3), key, fill=black, font=font) - text_image.text((text_x, 1005), key, fill=white, font=font) - else: - text_width = small_font.getsize(key)[0] - positions = [ - (text_x-2, 1020-2), - (text_x+2, 1020-2), - (text_x-2, 1020+2), - (text_x+2, 1020+2), - (text_x, 1015) - ] - text_image.text(positions[0], key, fill=black, font=small_font) - text_image.text(positions[1], key, fill=black, font=small_font) - text_image.text(positions[2], key, fill=black, font=small_font) - text_image.text(positions[3], key, fill=black, font=small_font) - text_image.text(positions[4], key, fill=white, font=small_font) - - self.bot.log("Saving table image") - table_images_path = "gwendolyn/resources/games/blackjack_tables/" - table_image_path = f"{table_images_path}blackjack_table{channel}.png" - table.save(table_image_path) - - return + 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): @@ -1415,85 +944,23 @@ class DrawBlackjack(): The image of the hand. """ self.bot.log("Drawing hand {hand}, {busted}, {blackjack}") - fonts_path = "gwendolyn/resources/fonts/" - font = ImageFont.truetype(fonts_path+'futura-bold.ttf', 200) - font_two = ImageFont.truetype(fonts_path+'futura-bold.ttf', 120) - length = len(hand) - background_width = (self.BORDER*2)+691+(125*(length-1)) - background_size = (background_width, (self.BORDER*2)+1065) - background = Image.new("RGBA", background_size, (0, 0, 0, 0)) - text_image = ImageDraw.Draw(background) - card_y = self.BORDER+self.PLACEMENT[1] - cards_path = "gwendolyn/resources/games/cards/" + background = self._draw_cards(hand, int(dealer)) - if dealer: - img = Image.open(cards_path+hand[0].upper()+".png") - card_position = (self.BORDER+self.PLACEMENT[0], card_y) - background.paste(img, card_position, img) - img = Image.open("gwendolyn/resources/games/cards/red_back.png") - card_position = (125+self.BORDER+self.PLACEMENT[0], card_y) - background.paste(img, card_position, img) - else: - for i in range(length): - card_path = cards_path + f"{hand[i].upper()}.png" - img = Image.open(card_path) - card_position = (self.BORDER+(i*125)+self.PLACEMENT[0], card_y) - background.paste(img, card_position, img) + if busted or blackjack: + if busted: + text = "BUSTED" + last_color = RED + font_size = 60 + elif blackjack: + text = "BLACKJACK" + last_color = GOLD + font_size = 35 - width, height = background.size - text_height = 290+self.BORDER - white = (255, 255, 255) - black = (0, 0, 0) - red = (255, 50, 50) - gold = (155, 123, 0) + 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) - if busted: - text_width = font.getsize("BUSTED")[0] - text_x = int(width/2)-int(text_width/2) - positions = [ - (text_x-10, text_height+20-10), - (text_x+10, text_height+20-10), - (text_x-10, text_height+20+10), - (text_x+10, text_height+20+10), - (text_x-5, text_height-5), - (text_x+5, text_height-5), - (text_x-5, text_height+5), - (text_x+5, text_height+5), - (text_x, text_height) - ] - text_image.text(positions[0], "BUSTED", fill=black, font=font) - text_image.text(positions[1], "BUSTED", fill=black, font=font) - text_image.text(positions[2], "BUSTED", fill=black, font=font) - text_image.text(positions[3], "BUSTED", fill=black, font=font) - text_image.text(positions[4], "BUSTED", fill=white, font=font) - text_image.text(positions[5], "BUSTED", fill=white, font=font) - text_image.text(positions[6], "BUSTED", fill=white, font=font) - text_image.text(positions[7], "BUSTED", fill=white, font=font) - text_image.text(positions[8], "BUSTED", fill=red, font=font) - elif blackjack: - text_width = font_two.getsize("BLACKJACK")[0] - text_x = int(width/2)-int(text_width/2) - positions = [ - (text_x-6, text_height+20-6), - (text_x+6, text_height+20-6), - (text_x-6, text_height+20+6), - (text_x+6, text_height+20+6), - (text_x-3, text_height-3), - (text_x+3, text_height-3), - (text_x-3, text_height+3), - (text_x+3, text_height+3), - (text_x, text_height) - ] - text = "BLACKJACK" - text_image.text(positions[0], text, fill=black, font=font_two) - text_image.text(positions[1], text, fill=black, font=font_two) - text_image.text(positions[2], text, fill=black, font=font_two) - text_image.text(positions[3], text, fill=black, font=font_two) - text_image.text(positions[4], text, fill=white, font=font_two) - text_image.text(positions[5], text, fill=white, font=font_two) - text_image.text(positions[6], text, fill=white, font=font_two) - text_image.text(positions[7], text, fill=white, font=font_two) - text_image.text(positions[8], text, fill=gold, font=font_two) - - resized_size = (int(width/3.5), int(height/3.5)) - return background.resize(resized_size, resample=Image.BILINEAR) + return background diff --git a/gwendolyn/funcs/games/connect_four.py b/gwendolyn/funcs/games/connect_four.py index 6dcb726..6aac84d 100644 --- a/gwendolyn/funcs/games/connect_four.py +++ b/gwendolyn/funcs/games/connect_four.py @@ -22,12 +22,46 @@ create_actionrow) from discord_slash.model import ButtonStyle from gwendolyn.utils import encode_id +from .game_base import BoardGame ROWCOUNT = 6 COLUMNCOUNT = 7 +def _encode_board_string(board: list): + string = [str(i) for row in board for i in row] -class ConnectFour(): + while len(string) > 0 and string[0] == "0": + string = string[1:] + + if string == "": + string = "0" + + dec = 0 + for i, digit in enumerate(string[::-1]): + dec += (3**i)*int(digit) + + 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 + + while len(string) < ROWCOUNT * COLUMNCOUNT: + string.append("0") + + string = string[::-1] + + board = [ + [int(x) for x in string[i*COLUMNCOUNT:i*COLUMNCOUNT+COLUMNCOUNT]] + for i in range(ROWCOUNT)] + + return board + + +class ConnectFour(BoardGame): """ Deals with connect four commands and logic. @@ -41,8 +75,7 @@ class ConnectFour(): def __init__(self, bot): """Initialize the class.""" - self.bot = bot - self.draw = DrawConnectFour(bot) + super().__init__(bot, "connectfour", DrawConnectFour) self.get_name = self.bot.database_funcs.get_name # pylint: disable=invalid-name self.AISCORES = { @@ -57,39 +90,6 @@ class ConnectFour(): } # pylint: enable=invalid-name - def _encode_board_string(self, board: list): - string = [str(i) for row in board for i in row] - - while len(string) > 0 and string[0] == "0": - string = string[1:] - - if string == "": - string = "0" - - dec = 0 - for i, digit in enumerate(string[::-1]): - dec += (3**i)*int(digit) - - return str(dec) - - def _decode_board_string(self, board_string: str): - dec = int(board_string) - string = [] - while dec: - string.append(str(dec % 3)) - dec = dec // 3 - - while len(string) < ROWCOUNT * COLUMNCOUNT: - string.append("0") - - string = string[::-1] - - board = [ - [int(x) for x in string[i*COLUMNCOUNT:i*COLUMNCOUNT+COLUMNCOUNT]] - for i in range(ROWCOUNT)] - - return board - async def start(self, ctx: SlashContext, opponent: Union[int, discord.User]): """ @@ -106,128 +106,90 @@ class ConnectFour(): searches when minimaxing. """ await self.bot.defer(ctx) - user = ctx.author.id channel = str(ctx.channel_id) - started_game = False - can_start = True + opponent_info = await self._test_opponent(ctx, opponent) + if not opponent_info: + return - 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: - send_message = "Difficulty doesn't exist" - log_message = "They challenged a difficulty that doesn't exist" - can_start = False - elif isinstance(opponent, discord.User): - if opponent.bot: - # User has challenged a bot - if opponent == self.bot.user: - # It was Gwendolyn - difficulty = 3 - difficulty_text = f" with difficulty {difficulty}" - opponent = 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 = 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 + difficulty = opponent_info[0] + difficulty_text = opponent_info[1] - if can_start: - board = [[0 for _ in range(COLUMNCOUNT)] for _ in range(ROWCOUNT)] - players = [user, opponent] - random.shuffle(players) + board = [[0 for _ in range(COLUMNCOUNT)] for _ in range(ROWCOUNT)] + players = [ctx.author.id, opponent] + random.shuffle(players) - self.draw.draw_image(channel, board, 0, [0,0], "", players) + self.draw.draw_image(channel, board, players, [0, [0,0], ""], players) - gwendolyn_turn = (players[0] == self.bot.user.id) - started_game = True + opponent_name = self.get_name(f"#{opponent}") + turn_name = self.get_name(f"#{players[0]}") - 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 - ) - turn_text = f"It's {turn_name}'s turn" - send_message = f"{started_text} {turn_text}" - log_message = "They started a game" - - self.bot.log(log_message) - await ctx.send(send_message) + 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") # Sets the whole game in motion - if started_game: - 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)) - - board_string = self._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[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) - )) + 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)) + 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", - "end", + "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.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(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)) + + await image_message.edit( + components=action_rows + ) async def place_piece(self, ctx: ComponentContext, board_string: str, column: int, players: list[int], difficulty: int, @@ -246,7 +208,7 @@ class ConnectFour(): user_name = self.get_name(f"#{placer}") placed_piece = False - board = self._decode_board_string(board_string) + board = _decode_board_string(board_string) board = self._place_on_board(board, player_number, column) if board is None: @@ -300,7 +262,7 @@ class ConnectFour(): if game_won: self._end_game(winner, players, difficulty) else: - board_string = self._encode_board_string(board) + board_string = _encode_board_string(board) if gwendolyn_turn: await self._connect_four_ai( ctx, board_string, players, difficulty, image_message.id @@ -513,7 +475,7 @@ class ConnectFour(): self.bot.log("Figuring out best move") - board = self._decode_board_string(board_string) + board = _decode_board_string(board_string) player = players.index(self.bot.user.id)+1 scores = [-math.inf for _ in range(COLUMNCOUNT)] @@ -748,7 +710,7 @@ class DrawConnectFour(): White, but with the alpha set to win_bar_alpha. """ - def __init__(self, bot): + def __init__(self, bot, game): """Initialize the class.""" self.bot = bot self.get_name = self.bot.database_funcs.get_name @@ -797,8 +759,8 @@ class DrawConnectFour(): # pylint: enable=invalid-name # Draws the whole thing - def draw_image(self, channel: str, board: list, winner: int, - win_coordinates: list, win_direction: str, players: list): + def draw_image(self, channel: str, board: list, players: list, + win_info: list): """ Draw an image of the connect four board. @@ -815,8 +777,8 @@ class DrawConnectFour(): self._draw_pieces(drawer, board) - if winner != 0: - self._draw_win(background, win_coordinates, win_direction) + if win_info[0] != 0: + self._draw_win(background, win_info[1], win_info[2]) self._draw_footer(drawer, players) diff --git a/gwendolyn/funcs/games/game_base.py b/gwendolyn/funcs/games/game_base.py new file mode 100644 index 0000000..6dd6168 --- /dev/null +++ b/gwendolyn/funcs/games/game_base.py @@ -0,0 +1,309 @@ +"""Base class for the games.""" +import random +from typing import Union + +from discord import File, User +from discord.abc import Messageable +from discord_slash.utils.manage_components import (create_button, + create_actionrow) +from discord_slash.context import InteractionContext as IntCont + +from PIL import ImageFont, Image, ImageDraw + +from gwendolyn.exceptions import GameNotInDatabase +from gwendolyn.utils import encode_id + +class GameBase(): + """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 _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(create_button( + style=style, + label=label, + custom_id=custom_id + )) + + action_rows = [] + for i in range(((len(button_objects)-1)//5)+1): + action_rows.append(create_actionrow(*button_objects[i*5:i*5+5])) + + 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) + + if buttons is not None and len(buttons) < 25: + await old_image.edit(components = self._get_action_rows(buttons)) + + return old_image + +class DatabaseGame(GameBase): + """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 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 + + 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 _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) + + 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() + + 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): + self._insert_document(new_game) + await self._send_image(channel, buttons) + + 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 + + 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) + + self._update_document( + channel, + {"$set": {"_id": channel, "cards": all_decks}}, + "cards" + ) + + 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") + + drawn_card = self.access_document(channel, "cards")["cards"][0] + self._update_document(channel, {"$pop": {"cards": -1}}, "cards") + + 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 + + return 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" + + self.resources = game.resources + game_name = game.game_name + self.default_image = f"{self.resources}default_images/{game_name}.png" + self.images_path = f"{self.resources}images/{game_name}" + + def _draw_image(self, game: dict, table: Image.Image): + pass + + def draw(self, channel: str): + game = self.game.access_document(channel) + image = Image.open(self.default_image) + 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 _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] + + 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 _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 + + 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 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) + + return image + +class CardDrawer(BaseDrawer): + 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 + + 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 _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 + ) + + 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) + + return background diff --git a/gwendolyn/funcs/games/hangman.py b/gwendolyn/funcs/games/hangman.py index 83c3dfe..0c22157 100644 --- a/gwendolyn/funcs/games/hangman.py +++ b/gwendolyn/funcs/games/hangman.py @@ -8,13 +8,13 @@ Deals with commands and logic for hangman games. DrawHangman() Draws the image shown to the player. """ +import os import datetime # Used for generating the game id import string # string.ascii_uppercase used import math # Used by DrawHangman(), mainly for drawing circles import random # Used to draw poorly import requests # Used for getting the word in Hangman.start() import discord # Used for discord.file and type hints -import os from discord_slash.utils.manage_components import (create_button, create_actionrow) @@ -50,10 +50,10 @@ class Hangman(): 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 + 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 + self._APIPARAMS = { # pylint: disable=invalid-name "hasDictionaryDef": True, "minCorpusCount": 5000, "maxCorpusCount": -1, @@ -78,7 +78,7 @@ class Hangman(): word = "-" while "-" in word or "." in word: - response = requests.get(self.__API_url, params=self.__APIPARAMS) + response = requests.get(self._API_url, params=self._APIPARAMS) word = list(response.json()[0]["word"].upper()) self.bot.log("Found the word \""+"".join(word)+"\"") @@ -87,7 +87,7 @@ class Hangman(): remaining_letters = list(string.ascii_uppercase) - self.__draw.draw_image(game_id, 0, word, guessed, []) + self._draw.draw_image(game_id, 0, word, guessed, []) send_message = f"{ctx.author.display_name} started a game of hangman." @@ -219,7 +219,7 @@ class Hangman(): 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) + self._draw.draw_image(game_id, misses, word, guessed, guessed_letters) if misses == 6: send_message += self.bot.long_strings["Hangman lost game"] @@ -325,36 +325,36 @@ class DrawHangman(): FONT SMALLFONT """ - self.__bot = bot + self._bot = bot # pylint: disable=invalid-name - self.__CIRCLESIZE = 120 - self.__LINEWIDTH = 12 + self._CIRCLESIZE = 120 + self._LINEWIDTH = 12 - self.__BODYSIZE = 210 - self.__LIMBSIZE = 60 - self.__ARMPOSITION = 60 + self._BODYSIZE = 210 + self._LIMBSIZE = 60 + self._ARMPOSITION = 60 - self.__MANX = (self.__LIMBSIZE*2) - self.__MANY = (self.__CIRCLESIZE+self.__BODYSIZE+self.__LIMBSIZE) - MANPADDING = self.__LINEWIDTH*4 - self.__MANX += MANPADDING - self.__MANY += MANPADDING + self._MANX = (self._LIMBSIZE*2) + self._MANY = (self._CIRCLESIZE+self._BODYSIZE+self._LIMBSIZE) + MANPADDING = self._LINEWIDTH*4 + self._MANX += MANPADDING + self._MANY += MANPADDING - self.__LETTERLINELENGTH = 90 - self.__LETTERLINEDISTANCE = 30 + self._LETTERLINELENGTH = 90 + self._LETTERLINEDISTANCE = 30 - self.__GALLOWX, self.__GALLOWY = 360, 600 - self.__PHI = 1-(1 / ((1 + 5 ** 0.5) / 2)) + self._GALLOWX, self._GALLOWY = 360, 600 + self._PHI = 1-(1 / ((1 + 5 ** 0.5) / 2)) LETTERSIZE = 75 # Wrong guesses letter size WORDSIZE = 70 # Correct guesses letter size FONTPATH = "gwendolyn/resources/fonts/comic-sans-bold.ttf" - self.__FONT = ImageFont.truetype(FONTPATH, LETTERSIZE) - self.__SMALLFONT = ImageFont.truetype(FONTPATH, WORDSIZE) + self._FONT = ImageFont.truetype(FONTPATH, LETTERSIZE) + self._SMALLFONT = ImageFont.truetype(FONTPATH, WORDSIZE) # pylint: enable=invalid-name - def __deviate(self, pre_deviance: int, pre_deviance_accuracy: int, + 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) @@ -371,14 +371,14 @@ class DrawHangman(): deviance = -maxmin return deviance, deviance_accuracy - def __bad_circle(self): - circle_padding = (self.__LINEWIDTH*3) - image_width = self.__CIRCLESIZE+circle_padding + 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)) drawer = ImageDraw.Draw(background, "RGBA") - middle = (self.__CIRCLESIZE+(self.__LINEWIDTH*3))/2 + middle = (self._CIRCLESIZE+(self._LINEWIDTH*3))/2 deviance_x = 0 deviance_y = 0 deviance_accuracy_x = 0 @@ -387,96 +387,96 @@ class DrawHangman(): degrees_amount = 360 + random.randint(-10, 30) for degree in range(degrees_amount): - deviance_x, deviance_accuracy_x = self.__deviate( + deviance_x, deviance_accuracy_x = self._deviate( deviance_x, deviance_accuracy_x, - self.__LINEWIDTH/100, - self.__LINEWIDTH, + self._LINEWIDTH/100, + self._LINEWIDTH, 0.03 ) - deviance_y, deviance_accuracy_y = self.__deviate( + deviance_y, deviance_accuracy_y = self._deviate( deviance_y, deviance_accuracy_y, - self.__LINEWIDTH/100, - self.__LINEWIDTH, + self._LINEWIDTH/100, + self._LINEWIDTH, 0.03 ) radians = math.radians(degree+start) - circle_x = (math.cos(radians) * (self.__CIRCLESIZE/2)) - circle_y = (math.sin(radians) * (self.__CIRCLESIZE/2)) + circle_x = (math.cos(radians) * (self._CIRCLESIZE/2)) + circle_y = (math.sin(radians) * (self._CIRCLESIZE/2)) - position_x = middle + circle_x - (self.__LINEWIDTH/2) + deviance_x - position_y = middle + circle_y - (self.__LINEWIDTH/2) + deviance_y + position_x = middle + circle_x - (self._LINEWIDTH/2) + deviance_x + position_y = middle + circle_y - (self._LINEWIDTH/2) + deviance_y circle_position = [ (position_x, position_y), - (position_x+self.__LINEWIDTH, position_y+self.__LINEWIDTH) + (position_x+self._LINEWIDTH, position_y+self._LINEWIDTH) ] drawer.ellipse(circle_position, fill=(0, 0, 0, 255)) return background - def __bad_line(self, length: int, rotated: bool = False): + def _bad_line(self, length: int, rotated: bool = False): if rotated: - width, height = length+self.__LINEWIDTH*3, self.__LINEWIDTH*3 + width, height = length+self._LINEWIDTH*3, self._LINEWIDTH*3 else: - width, height = self.__LINEWIDTH*3, length+self.__LINEWIDTH*3 + width, height = self._LINEWIDTH*3, length+self._LINEWIDTH*3 background = Image.new("RGBA", (width, height), color=(0, 0, 0, 0)) drawer = ImageDraw.Draw(background, "RGBA") - possible_deviance = int(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._deviate( deviance_x, deviance_accuracy_x, - self.__LINEWIDTH/1000, - self.__LINEWIDTH, + self._LINEWIDTH/1000, + self._LINEWIDTH, 0.004 ) - deviance_y, deviance_accuracy_y = self.__deviate( + deviance_y, deviance_accuracy_y = self._deviate( deviance_y, deviance_accuracy_y, - self.__LINEWIDTH/1000, - self.__LINEWIDTH, + self._LINEWIDTH/1000, + self._LINEWIDTH, 0.004 ) if rotated: - position_x = self.__LINEWIDTH + pixel + deviance_x - position_y = self.__LINEWIDTH + deviance_y + 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 + 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) + (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): + def _draw_man(self, misses: int, seed: str): random.seed(seed) - man_size = (self.__MANX, self.__MANY) + 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 + 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) + 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: @@ -487,30 +487,30 @@ class DrawHangman(): random.seed(seed) for limb in limbs: - limb_drawing = self.__bad_line(self.__LIMBSIZE, True) - x_position = (self.__MANX-(self.__LINEWIDTH*3))//2 + limb_drawing = self._bad_line(self._LIMBSIZE, True) + x_position = (self._MANX-(self._LINEWIDTH*3))//2 if limb[1] == "a": rotation = random.randint(-45, 45) shift = math.sin(math.radians(rotation)) - line_length = self.__LIMBSIZE+(self.__LINEWIDTH*3) + 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 + y_position = self._CIRCLESIZE + self._ARMPOSITION if limb == "ra": compensation = min(-compensation, 0) else: - x_position -= self.__LIMBSIZE + x_position -= self._LIMBSIZE compensation = min(compensation, 0) y_position += compensation else: rotation = random.randint(-15, 15) - y_position = self.__CIRCLESIZE+self.__BODYSIZE-self.__LINEWIDTH + 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 + x_position += -limb_drawing.size[0]+self._LINEWIDTH*3 limb_drawing = limb_drawing.rotate(rotation+45, expand=1) paste_position = (x_position, y_position) @@ -518,11 +518,11 @@ class DrawHangman(): return background - def __bad_text(self, text: str, big: bool, color: tuple = (0, 0, 0, 255)): + def _bad_text(self, text: str, big: bool, color: tuple = (0, 0, 0, 255)): if big: - font = self.__FONT + font = self._FONT else: - font = self.__SMALLFONT + font = self._SMALLFONT width, height = font.getsize(text) img = Image.new("RGBA", (width, height), color=(0, 0, 0, 0)) drawer = ImageDraw.Draw(img, "RGBA") @@ -530,59 +530,59 @@ class DrawHangman(): drawer.text((0, 0), text, font=font, fill=color) return img - def __draw_gallows(self): - gallow_size = (self.__GALLOWX, self.__GALLOWY) + def _draw_gallows(self): + gallow_size = (self._GALLOWX, self._GALLOWY) background = Image.new("RGBA", gallow_size, color=(0, 0, 0, 0)) - 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) + 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) - 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 + 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) - 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) + 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) - 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) + 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_letter_lines(self, word: str, guessed: list, misses: int): - letter_width = self.__LETTERLINELENGTH+self.__LETTERLINEDISTANCE + 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) + 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) + 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_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_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_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_x += (self._LETTERLINELENGTH//2)+(self._LINEWIDTH*2) letter_lines.paste( letter_drawing, (letter_x, 0), @@ -591,7 +591,7 @@ class DrawHangman(): return letter_lines - def __shortest_dist(self, positions: list, new_position: tuple): + 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]) @@ -601,18 +601,18 @@ class DrawHangman(): shortest_dist = dist return shortest_dist - def __draw_misses(self, guesses: list, word: str): + def _draw_misses(self, guesses: list, word: str): background = Image.new("RGBA", (600, 400), color=(0, 0, 0, 0)) pos = [] for guess in guesses: if guess not in word: placed = False while not placed: - letter = self.__bad_text(guess, True) - w, h = self.__FONT.getsize(guess) + 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: + if self._shortest_dist(pos, (x, y)) > 70: pos.append((x, y)) background.paste(letter, (x, y), letter) placed = True @@ -628,27 +628,27 @@ class DrawHangman(): channel: str The id of the channel the game is in. """ - self.__bot.log("Drawing hangman image") + self._bot.log("Drawing hangman image") random.seed(game_id) background = Image.open("gwendolyn/resources/paper.jpg") - gallow = self.__draw_gallows() - man = self.__draw_man(misses, game_id) + gallow = self._draw_gallows() + man = self._draw_man(misses, game_id) random.seed(game_id) letter_line_parameters = [word, guessed, misses] - letter_lines = self.__draw_letter_lines(*letter_line_parameters) + letter_lines = self._draw_letter_lines(*letter_line_parameters) random.seed(game_id) - misses = self.__draw_misses(list(guessed_letters), word) + 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 = self._bad_text("MISSES", False) misses_text_width = misses_text.size[0] background.paste(misses_text, (850-misses_text_width//2, 50), misses_text) diff --git a/gwendolyn/funcs/lookup/__init__.py b/gwendolyn/funcs/lookup/__init__.py index 2329a0a..cdbeea9 100644 --- a/gwendolyn/funcs/lookup/__init__.py +++ b/gwendolyn/funcs/lookup/__init__.py @@ -2,4 +2,4 @@ __all__ = ["LookupFuncs"] -from .lookup_funcs import LookupFuncs \ No newline at end of file +from .lookup_funcs import LookupFuncs diff --git a/gwendolyn/funcs/lookup/lookup_funcs.py b/gwendolyn/funcs/lookup/lookup_funcs.py index 19f6a44..d2a54f7 100644 --- a/gwendolyn/funcs/lookup/lookup_funcs.py +++ b/gwendolyn/funcs/lookup/lookup_funcs.py @@ -4,22 +4,162 @@ import discord from gwendolyn.utils import cap +STATS = [ + "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) + + 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"] + 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" + ] - # Calculates D&D stat modifier - def modifier(self, statistic): - mods = math.floor((statistic-10)/2) - if mods >= 0: - mods = "+"+str(mods) - return(str(mods)) + 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 = "\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)}" + else: + saving_throws = "" + + 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 skills: + skills = f"\n**Skills:** {', '.join(skills)}" + else: + skills = "" + + vulnerabilities = monster["damage_vulnerabilities"] + if vulnerabilities != "": + vulnerabilities = "\n**Damage Vulnerabilities** "+vulnerabilities + + resistances = monster["damage_resistances"] + if resistances != "": + resistances = "\n**Damage Resistances** "+resistances + + immunities = monster["damage_immunities"] + if immunities != "": + immunities = "\n**Damage Immunities** "+immunities + + c_immunities = monster["condition_immunities"] + if c_immunities != "": + c_immunities = "\n**Condition Immunities** "+c_immunities + + special_abilities = "" + if "special_abilities" in monster: + for ability in monster["special_abilities"]: + special_abilities += "\n\n***"+ability["name"]+".*** "+ability["desc"] + + act = "" + if "actions" in monster: + for action in monster["actions"]: + act += "\n\n***"+action["name"]+".*** "+action["desc"] + + react = "" + if "reactions" in monster: + for reaction in monster["reactions"]: + react += "\n\n***"+reaction["name"]+".*** "+reaction["desc"] + + legendaryActions = "" + if "legendary_actions" in monster: + for action in monster["legendary_actions"]: + legendaryActions += "\n\n***"+action["name"]+".*** "+action["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}") + + new_part = "\n--------------------" + + monster_type = f"*{monster['size']} {types}, {monster['alignment']}*" + + basic_info = "\n**Armor Class** "+str(monster["armor_class"])+"\n**Hit Points** "+str(monster["hit_points"])+" ("+hit_dice+")\n**Speed **"+monster["speed"]+new_part+"\n" + + 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"]) + + monster_info = [(info, monster['name']), + (special_abilities, "Special Abilities"), + (act, "Actions"), + (react, "Reactions"), + (legendaryActions, "Legendary Actions")] + + self.bot.log("Returning monster information") + return monster_info # Looks up a monster - async def monsterFunc(self, ctx, query): + async def monster_func(self, ctx, query): query = cap(query) self.bot.log("Looking up "+query) @@ -27,124 +167,41 @@ class LookupFuncs(): if len(query) < 2: self.bot.log("Monster name too short") await ctx.send("I don't know that monster...") + return + + # 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) + + for monster in data: + if "name" in monster and str(query) == monster["name"]: + self.bot.log("Found it!") + + monster_info = self._format_monster(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) + + break else: - # Opens "monsters.json" - data = json.load(open('gwendolyn/resources/lookup/monsters.json', encoding = "utf8")) - for monster in data: - if "name" in monster and str(query) == monster["name"]: - self.bot.log("Found it!") - - # Looks at the information about the monster and returns that information - # in separate variables, allowing Gwendolyn to know where to separate - # the messages - if monster["subtype"] != "": - types = (monster["type"]+" ("+monster["subtype"]+")") - else: - types = monster["type"] - con_mod = math.floor((monster["constitution"]-10)/2) - hit_dice = monster["hit_dice"] - - stats = ("**Str:** "+str(monster["strength"])+" ("+self.modifier(monster["strength"])+")\t**Dex:** "+str(monster["dexterity"])+" ("+self.modifier(monster["dexterity"])+")\t**Con:** "+str(monster["constitution"])+" ("+self.modifier(monster["constitution"])+")\n**Int: **"+str(monster["intelligence"])+" ("+self.modifier(monster["intelligence"])+")\t**Wis: **"+str(monster["wisdom"])+" ("+self.modifier(monster["wisdom"])+")\t**Cha: **"+str(monster["charisma"])+" ("+self.modifier(monster["charisma"])+")") - - saving_throws = "" - for save in self.saves: - if save in monster: - if monster[save] >= 0: - saving_throws += " "+cap(save[:3])+" +"+str(monster[save])+"," - else: - saving_throws += " "+cap(save[:3])+" "+str(monster[save])+"," - if saving_throws != "": - saving_throws = "\n**Saving Throws**"+saving_throws[:-1] - - skills = "" - for skill in self.abilities: - if skill in monster: - if monster[skill] >= 0: - skills += " "+cap(skill.replace("_"," "))+" +"+str(monster[skill])+"," - else: - skills += " "+cap(skill.replace("_"," "))+" "+str(monster[skill])+"," - if skills != "": - skills = "\n**Skills**"+skills[:-1] - - vulnerabilities = monster["damage_vulnerabilities"] - if vulnerabilities != "": - vulnerabilities = "\n**Damage Vulnerabilities** "+vulnerabilities - - resistances = monster["damage_resistances"] - if resistances != "": - resistances = "\n**Damage Resistances** "+resistances - - immunities = monster["damage_immunities"] - if immunities != "": - immunities = "\n**Damage Immunities** "+immunities - - c_immunities = monster["condition_immunities"] - if c_immunities != "": - c_immunities = "\n**Condition Immunities** "+c_immunities - - specialAbilities = "" - if "special_abilities" in monster: - for ability in monster["special_abilities"]: - specialAbilities += "\n\n***"+ability["name"]+".*** "+ability["desc"] - - act = "" - if "actions" in monster: - for action in monster["actions"]: - act += "\n\n***"+action["name"]+".*** "+action["desc"] - - react = "" - if "reactions" in monster: - for reaction in monster["reactions"]: - react += "\n\n***"+reaction["name"]+".*** "+reaction["desc"] - - legendaryActions = "" - if "legendary_actions" in monster: - for action in monster["legendary_actions"]: - legendaryActions += "\n\n***"+action["name"]+".*** "+action["desc"] - - if con_mod < 0: - hit_dice += (" - "+str(con_mod * int(monster["hit_dice"].replace("d"," ").split()[0])*(-1))) - if con_mod > 0: - hit_dice += (" + "+str(con_mod * int(monster["hit_dice"].replace("d"," ").split()[0]))) - - new_part = "\n--------------------" - - monster_type = monster["size"]+" "+types+", "+monster["alignment"]+"*" - - basic_info = "\n**Armor Class** "+str(monster["armor_class"])+"\n**Hit Points** "+str(monster["hit_points"])+" ("+hit_dice+")\n**Speed **"+monster["speed"]+new_part+"\n" - - 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"]) - - monsterInfo = [(info, query), - (specialAbilities, "Special Abilities"), - (act, "Actions"), - (react, "Reactions"), - (legendaryActions, "Legendary Actions")] - - self.bot.log("Returning monster information") - - # Sends the received information. Separates into separate messages if - # there is too much text - await ctx.send(f"Result for \"{query}\"") - for text, title in monsterInfo: - if text != "": - if len(text) < 2000: - em = discord.Embed(title = title, description = text, colour=0xDEADBF) - await ctx.channel.send(embed = em) - else: - index = text[:2000].rfind(".")+1 - em1 = discord.Embed(title = title, description = text[:index], colour=0xDEADBF) - await ctx.channel.send(embed = em1) - em2 = discord.Embed(title = "", description = text[index+1:], colour=0xDEADBF) - await ctx.channel.send(embed = em2) - - break - else: - self.bot.log("Monster not in database") - await ctx.send("I don't know that monster...") + self.bot.log("Monster not in database") + await ctx.send("I don't know that monster...") # Looks up a spell - async def spellFunc(self, ctx, query): + async def spell_func(self, ctx, query): query = cap(query) self.bot.log("Looking up "+query) diff --git a/gwendolyn/funcs/other/generators.py b/gwendolyn/funcs/other/generators.py index c3a99a6..f593516 100644 --- a/gwendolyn/funcs/other/generators.py +++ b/gwendolyn/funcs/other/generators.py @@ -15,9 +15,10 @@ class Generators(): yield (corpus[i], corpus[i+1], corpus[i+2]) # Generates a random name - async def nameGen(self, ctx): + async def name_gen(self, ctx): # Makes a list of all names from "names.txt" - names = open('gwendolyn/resources/names.txt', encoding='utf8').read() + with open("gwendolyn/resources/names.txt", "r", encoding='utf8') as file_pointer: + names = file_pointer.read() corpus = list(names) # Makes a list of pairs @@ -28,13 +29,13 @@ class Generators(): # 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.keys(): + 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.keys(): + 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] @@ -60,7 +61,7 @@ class Generators(): done = False # Creates the name one letter at a time - while done == False: + while not done: if random.randint(1,10) > 1: try: new_letter = random.choice(letter_dict[chain[-2]+chain[-1]]) @@ -79,15 +80,15 @@ class Generators(): await ctx.send(gen_name) # Generates a random tavern name - async def tavernGen(self, ctx): + async def tavern_gen(self, ctx): # _lists first parts, second parts and third parts of tavern names - fp = ["The Silver","The Golden","The Staggering","The Laughing","The Prancing","The Gilded","The Running","The Howling","The Slaughtered","The Leering","The Drunken","The Leaping","The Roaring","The Frowning","The Lonely","The Wandering","The Mysterious","The Barking","The Black","The Gleaming","The Tap-Dancing","The Sad","The Sexy","The Artificial","The Groovy","The Merciful","The Confused","The Pouting","The Horny","The Okay","The Friendly","The Hungry","The Handicapped","The Fire-breathing","The One-Eyed","The Psychotic","The Mad","The Evil","The Idiotic","The Trusty","The Busty"] - sp = ["Eel","Dolphin","Dwarf","Pegasus","Pony","Rose","Stag","Wolf","Lamb","Demon","Goat","Spirit","Horde","Jester","Mountain","Eagle","Satyr","Dog","Spider","Star","Dad","Rat","Jeremy","Mouse","Unicorn","Pearl","Ant","Crab","Penguin","Octopus","Lawyer","Ghost","Toad","Handjob","Immigrant","SJW","Dragon","Bard","Sphinx","Soldier","Salmon","Owlbear","Kite","Frost Giant","Arsonist"] - tp = [" Tavern"," Inn","","","","","","","","",""] + 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 - genTav = random.choice(fp)+" "+random.choice(sp)+random.choice(tp) - self.bot.log("Generated "+genTav) + 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(genTav) + await ctx.send(gen_tav) diff --git a/gwendolyn/funcs/other/nerd_shit.py b/gwendolyn/funcs/other/nerd_shit.py index 22b7159..4f4c856 100644 --- a/gwendolyn/funcs/other/nerd_shit.py +++ b/gwendolyn/funcs/other/nerd_shit.py @@ -1,4 +1,8 @@ -import discord, discord_slash, wolframalpha, requests, os +import os + +import requests +import discord +import wolframalpha from PIL import Image, ImageDraw, ImageFont class NerdShit(): @@ -6,9 +10,9 @@ class NerdShit(): """Runs misc commands.""" self.bot = bot - async def wolfSearch(self,ctx,content): + async def wolf_search(self,ctx,content): await self.bot.defer(ctx) - fnt = ImageFont.truetype('gwendolyn/resources/fonts/times-new-roman.ttf', 20) + 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) @@ -19,33 +23,33 @@ class NerdShit(): if int(res.numpods) > 0: for pod in res.pods: titles += [pod.title] - for x, sub in enumerate(pod.subpods): + for i, sub in enumerate(pod.subpods): pods += [sub] - if x > 0: + if i > 0: titles += [""] - podChunks = [pods[x:x+2] for x in range(0, len(pods), 2)] - titleChucks = [titles[x:x+2] for x in range(0, len(titles), 2)] + 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 x, chunk in enumerate(podChunks): + for i, chunk in enumerate(pod_chunks): width = 0 - for title in titleChucks[x]: - width = max(width,fnt.getsize(title)[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 titleChucks[x][count] == "": - placeFor_text = 0 + if title_chunks[i][count] == "": + place_for_text = 0 else: - placeFor_text = 30 - height += int(pod.img["@height"]) + 10 + placeFor_text + place_for_text = 30 + height += int(pod.img["@height"]) + 10 + place_for_text width += 10 height += 5 - wolfImage = Image.new("RGB",(width,height),color=(255,255,255)) + wolf_image = Image.new("RGB",(width,height),color=(255,255,255)) for count, pod in enumerate(chunk): response = requests.get(pod.img["@src"]) @@ -53,29 +57,29 @@ class NerdShit(): file.write(response.content) file.close() old_image = Image.open("gwendolyn/resources/wolfTemp.png") - oldSize = old_image.size - if titleChucks[x][count] == "": - placeFor_text = 0 + old_size = old_image.size + if title_chunks[i][count] == "": + place_for_text = 0 else: - placeFor_text = 30 - newSize = (width,int(oldSize[1]+10+placeFor_text)) - new_image = Image.new("RGB",newSize,color=(255,255,255)) - new_image.paste(old_image, (int((int(oldSize[0]+10)-oldSize[0])/2),int(((newSize[1]-placeFor_text)-oldSize[1])/2)+placeFor_text)) - if titleChucks[x][count] != "": - d = ImageDraw.Draw(new_image,"RGB") - d.text((5,7),titleChucks[x][count],font=fnt,fill=(150,150,150)) + 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)) - wolfImage.paste(new_image,(0,heights[count])) + wolf_image.paste(new_image,(0,heights[count])) new_image.close() old_image.close() count += 1 - wolfImage.save("gwendolyn/resources/wolf.png") - wolfImage.close() + 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") \ No newline at end of file + await ctx.send("Could not find anything relating to your search") diff --git a/gwendolyn/funcs/other/other.py b/gwendolyn/funcs/other/other.py index 7036ca1..13ff2e0 100644 --- a/gwendolyn/funcs/other/other.py +++ b/gwendolyn/funcs/other/other.py @@ -1,24 +1,24 @@ -import imdb # Used in movieFunc import random # Used in movieFunc -import discord # Used in movieFunc import datetime # Used in helloFunc import urllib # Used in imageFunc +import ast + +import imdb # Used in movieFunc +import discord # Used in movieFunc import lxml # Used in imageFunc import fandom # Used in findWikiPage import d20 # Used in rollDice -import ast + from .plex import Plex from .nerd_shit import NerdShit from .generators import Generators -from gwendolyn.utils import cap - fandom.set_lang("da") fandom.set_wiki("senkulpa") class MyStringifier(d20.MarkdownStringifier): def _str_expression(self, node): - if node.comment == None: + if node.comment is None: result_text = "Result" else: result_text = node.comment.capitalize() @@ -33,23 +33,23 @@ class Other(): self.generators = Generators(self.bot) # Picks a random movie and returns information about it - async def movieFunc(self, ctx): + async def movie_func(self, ctx): await self.bot.defer(ctx) self.bot.log("Creating IMDb object") - imdbClient = imdb.IMDb() + imdb_client = imdb.IMDb() self.bot.log("Picking a movie") - with open("gwendolyn/resources/movies.txt", "r") as f: - movie_list = f.read().split("\n") + 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}") - searchResult = imdbClient.search_movie(movie_name) + search_result = imdb_client.search_movie(movie_name) self.bot.log("Getting the data") - movie = searchResult[0] - imdbClient.update(movie) + movie = search_result[0] + imdb_client.update(movie) self.bot.log("Successfully ran /movie") @@ -63,13 +63,13 @@ class Other(): await ctx.send(embed = embed) # Responds with a greeting of a time-appropriate maner - async def helloFunc(self, ctx): - def time_in_range(start, end, x): - # Return true if x is in the range [start, end] + 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 <= x <= end + return start <= i <= end else: - return start <= x or x <= end + return start <= i or i <= end author = ctx.author.display_name now = datetime.datetime.now() @@ -87,31 +87,22 @@ class Other(): await ctx.send(send_message) # Finds a random picture online - async def imageFunc(self, ctx): + 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": - a = str(random.randint(0 ,9)) - b = str(random.randint(0,9)) - c = str(random.randint(0,9)) - d = str(random.randint(0,9)) - search = ("img_"+a+b+c+d) + search = f"img_{''.join([random.randint(0,9) for _ in range(4)])}" elif cam == "two": - a = str(random.randint(2012,2016)) - b = str(random.randint(1,12)).zfill(2) - c = str(random.randint(1,29)).zfill(2) - search = ("IMG_"+a+b+c) + 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": - a = str(random.randint(1,500)).zfill(4) - search = ("IMAG_"+a) + search = f"IMAG_{str(random.randint(1,500)).zfill(4)}" elif cam == "four": - a = str(random.randint(0,9)) - b = str(random.randint(0,9)) - c = str(random.randint(0,9)) - d = str(random.randint(0,9)) - search = ("DSC_"+a+b+c+d) + search = f"DSC_{''.join([random.randint(0,9) for _ in range(4)])}" self.bot.log("Searching for "+search) @@ -127,37 +118,37 @@ class Other(): # Picks an image number = random.randint(1,len(images))-1 image = ast.literal_eval(str(images[number])) - imageUrl = image["murl"] + image_url = image["murl"] self.bot.log("Picked image number "+str(number)) # Returns the image self.bot.log("Successfully returned an image") - await ctx.send(imageUrl) + await ctx.send(image_url) # Finds a page from the Senkulpa Wikia - async def findWikiPage(self, ctx, search : str): + async def find_wiki_page(self, ctx, search : str): await self.bot.defer(ctx) - foundPage = False + found_page = False if search != "": self.bot.log("Trying to find wiki page for "+search) - searchResults = fandom.search(search) - if len(searchResults) > 0: - foundPage = True - searchResult = searchResults[0] + 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: - foundPage = True + found_page = True self.bot.log("Searching for a random page") - searchResult = fandom.random() + search_result = fandom.random() - if foundPage: - self.bot.log(f"Found page \"{searchResult[0]}\"") - page = fandom.page(pageid = searchResult[1]) + 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 @@ -173,24 +164,24 @@ class Other(): await ctx.send(embed = embed) - async def rollDice(self, ctx, rollString): + async def roll_dice(self, ctx, roll_string): user = ctx.author.display_name - while len(rollString) > 1 and rollString[0] == " ": - rollString = rollString[1:] + while len(roll_string) > 1 and roll_string[0] == " ": + roll_string = roll_string[1:] - roll = d20.roll(rollString, allow_comments=True, stringifier=MyStringifier()) + roll = d20.roll(roll_string, allow_comments=True, stringifier=MyStringifier()) await ctx.send(f"{user} :game_die:\n{roll}") - async def helpFunc(self, ctx, command): + async def help_func(self, ctx, command): if command == "": - with open("gwendolyn/resources/help/help.txt",encoding="utf-8") as f: - text = f.read() - em = discord.Embed(title = "Help", description = text,colour = 0x59f442) - await ctx.send(embed = em) + 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 f: - text = f.read() - em = discord.Embed(title = command.capitalize(), description = text,colour = 0x59f442) - await ctx.send(embed = em) + 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/funcs/star_wars_funcs/star_wars_char.py b/gwendolyn/funcs/star_wars_funcs/star_wars_char.py index 7a81323..d3486bd 100644 --- a/gwendolyn/funcs/star_wars_funcs/star_wars_char.py +++ b/gwendolyn/funcs/star_wars_funcs/star_wars_char.py @@ -6,18 +6,18 @@ class StarWarsChar(): def __init__(self, bot): self.bot = bot - def getChar_name(self, user : str): + def get_char_name(self, user : str): self.bot.log("Getting name for "+self.bot.database_funcs.get_name(user)+"'s character") - userCharacter = self.bot.database["starwars characters"].find_one({"_id":user}) + user_character = self.bot.database["starwars characters"].find_one({"_id":user}) - if userCharacter != None: - self.bot.log("Name is "+userCharacter["Name"]) - return userCharacter["Name"] + 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 setUpDict(self, cmd : dict): + def set_up_dict(self, cmd : dict): self.bot.log("Setting up a dictionary in a nice way") if bool(cmd): keys = list(cmd) @@ -26,24 +26,24 @@ class StarWarsChar(): if isinstance(values[0],dict): return ", ".join(values) else: - for x, key in enumerate(keys): + for i, key in enumerate(keys): if type(key) is list: - if x%3 != 2: - result += "**" + key + "**" + ": " + ", ".join(values[x]) + " " + if i%3 != 2: + result += "**" + key + "**" + ": " + ", ".join(values[i]) + " " else: - result += "**" + key + "**" + ": " + ", ".join(values[x]) + "\n" + result += "**" + key + "**" + ": " + ", ".join(values[i]) + "\n" else: - if x%3 != 2: - result += "**" + key + "**" + ": " + str(values[x]) + " " + if i%3 != 2: + result += "**" + key + "**" + ": " + str(values[i]) + " " else: - result += "**" + key + "**" + ": " + str(values[x]) + "\n" + 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 lookUp(self, data : dict, key : str, cmd : str = ""): + def look_up(self, data : dict, key : str, cmd : str = ""): if cmd == " ": cmd = "" elif cmd != "": @@ -57,7 +57,7 @@ class StarWarsChar(): self.bot.log(key+" exists") if cmd == "": if type(data[key]) is dict and key != "Weapons": - return self.setUpDict(data[key]) + 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]): @@ -85,21 +85,21 @@ class StarWarsChar(): if type(data[key]) is int: try: - newValue = data[key] + int(cmd) - data[key] = newValue + 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" + 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" @@ -116,21 +116,21 @@ class StarWarsChar(): if type(data[key]) is int: try: - newValue = data[key] - int(cmd) - data[key] = newValue + 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" + 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" @@ -139,20 +139,20 @@ class StarWarsChar(): cmd = cmd[1:] if type(data[key]) is dict: - newKey = cmd.split(" ")[0] - cmd = cmd[len(newKey):] + 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 "+newKey+" in "+key) - lookUpResult = self.lookUp(data[key],newKey,cmd) - if type(lookUpResult) is dict: - data[key] = lookUpResult + 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 lookUpResult + return look_up_result elif type(data[key]) != list: self.bot.log("Trying to change "+key+" to "+cmd) try: @@ -193,23 +193,23 @@ class StarWarsChar(): if cmd == "": self.bot.log("Returning "+search) - return self.setUpDict(data[search]) + return self.set_up_dict(data[search]) else: - newKey = cmd.split(" ")[0] - cmd = cmd[len(newKey):] + new_key = cmd.split(" ")[0] + cmd = cmd[len(new_key):] if cmd != "": while cmd[0] == " ": cmd = cmd[1:] if cmd == "": break - lookUpResult = self.lookUp(data[search],newKey,cmd) - if type(lookUpResult) is dict: - data[search] = lookUpResult + 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 lookUpResult + return look_up_result - def characterSheet(self,character : dict): + def character_sheet(self,character : dict): self.bot.log("Setting up a character sheet for "+character["Name"]) divider = "--------------------\n" name = character["Name"] @@ -219,8 +219,8 @@ class StarWarsChar(): 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.setUpDict(character["Characteristics"]) - text4 = self.setUpDict(character["Skills"]) + text3 = self.set_up_dict(character["Characteristics"]) + text4 = self.set_up_dict(character["Skills"]) text5 = "" text6 = "" text7 = "" @@ -240,7 +240,7 @@ class StarWarsChar(): return name, text1+text2+"\n\n"+text3+divider+text4+"\n"+divider+text5+text6+text7+text8 def char_data(self,user : str,cmd : str): - userCharacter = self.bot.database["starwars characters"].find_one({"_id":user}) + user_character = self.bot.database["starwars characters"].find_one({"_id":user}) key = string.capwords(cmd.split(" ")[0]) cmd = cmd[len(key):] @@ -253,23 +253,23 @@ class StarWarsChar(): break self.bot.log("Looking for "+self.bot.database_funcs.get_name(user)+"'s character") - if userCharacter != None: + if user_character != None: self.bot.log("Found it! Looking for "+key+" in the data") - if key in userCharacter: + if key in user_character: self.bot.log("Found it!") - if type(userCharacter[key]) is dict: + 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(userCharacter[key]): + if bool(user_character[key]): self.bot.log("Returning a list of weapons") - return ", ".join(list(userCharacter[key])) + 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.setUpDict(userCharacter[key]) + return self.set_up_dict(user_character[key]) elif cmd[0] == "+": self.bot.log("Gonna add something!!!") try: @@ -287,7 +287,7 @@ class StarWarsChar(): 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 " + userCharacter["Name"] + return cmd[0]+" added to "+key+" for " + user_character["Name"] elif key == "Obligations" and "," in cmd: cmd = cmd.split(",") @@ -300,17 +300,17 @@ class StarWarsChar(): except: self.bot.log("Fucked that up") return "Wrong data type" - return cmd[0]+" added to "+key+" for " + userCharacter["Name"] + return cmd[0]+" added to "+key+" for " + user_character["Name"] elif key == "Weapons": - with open("gwendolyn/resources/star_wars/starwarstemplates.json", "r") as f: - templates = json.load(f) - newWeapon = templates["Weapon"] + 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 : newWeapon}}) + {"$set": {key+"."+cmd : new_weapon}}) - return cmd+" added to weapons for " + userCharacter["Name"] + return cmd+" added to weapons for " + user_character["Name"] else: self.bot.log("That's not happening") @@ -328,11 +328,11 @@ class StarWarsChar(): 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 userCharacter[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 "+userCharacter["Name"] + return cmd+" removed from "+key+" from "+user_character["Name"] else: self.bot.log("Welp. I fucked that up") return "Can't remove that" @@ -343,27 +343,27 @@ class StarWarsChar(): else: self.bot.log("Looking up "+cmd+" in "+key) if key == "Talents" or key == "Force-powers": - newKey = cmd - newcmd = "" + new_key = cmd + new_cmd = "" else: - newKey = string.capwords(cmd.split(" ")[0]) - newcmd = cmd[len(newKey):] + new_key = string.capwords(cmd.split(" ")[0]) + new_cmd = cmd[len(new_key):] - lookUpResult = self.lookUp(userCharacter[key],newKey,newcmd) + look_up_result = self.look_up(user_character[key],new_key,new_cmd) - if type(lookUpResult) is dict: + if type(look_up_result) is dict: self.bot.database["starwars characters"].update_one({"_id":user}, - {"$set": {key : lookUpResult}}) - return "Changed " + userCharacter["Name"] + "'s " + key + {"$set": {key : look_up_result}}) + return "Changed " + user_character["Name"] + "'s " + key else: - return lookUpResult + return look_up_result else: if cmd == "": self.bot.log("Retrieving data") - if type(userCharacter[key]) is list: - return key+":\n"+", ".join(userCharacter[key]) + if type(user_character[key]) is list: + return key+":\n"+", ".join(user_character[key]) else: - return userCharacter[key] + return user_character[key] elif cmd[0] == '+': self.bot.log("Adding") try: @@ -374,21 +374,21 @@ class StarWarsChar(): self.bot.log("Error message") return "Can't do that" - if type(userCharacter[key]) is int: + 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 " + userCharacter["Name"] + "'s " + key + return "Added " + cmd + " to " + user_character["Name"] + "'s " + key except: self.bot.log("BITCH SANDWICH") return "Can't add that" - elif type(userCharacter[key]) is list: + 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 " + userCharacter["Name"] + "'s " + key + return "Added " + cmd + " to " + user_character["Name"] + "'s " + key except: self.bot.log("tstststststs") return "Can't add that" @@ -405,16 +405,16 @@ class StarWarsChar(): self.bot.log("lalalala ") return "Can't do that" - if type(userCharacter[key]) is int: + 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 " + userCharacter["Name"] + "'s " + key + 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(userCharacter[key]) is list: + elif type(user_character[key]) is list: try: self.bot.log("removing "+cmd+" from "+key) try: @@ -423,7 +423,7 @@ class StarWarsChar(): except: self.bot.log("They can only remove stuff that's actually in the list") return "Not in list" - return "Removed " + cmd + " from " + userCharacter["Name"] + "'s " + key + return "Removed " + cmd + " from " + user_character["Name"] + "'s " + key except: self.bot.log("nah") return "Can't remove that" @@ -432,20 +432,20 @@ class StarWarsChar(): return "Can't remove that" else: self.bot.log("Changing "+key+" to "+cmd) - if type(userCharacter[key]) is int: + 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(userCharacter[key]) is str: + 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 " + userCharacter["Name"] + "'s " + key +" to " + cmd + 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?" @@ -453,32 +453,32 @@ class StarWarsChar(): self.bot.log(user+" doesn't have a character") return "You don't have a character. You can make one with /starwarscharacter" - def replaceSpaces(self,cmd : str): - withSpaces = ["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"] - withoutSpaces = ["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"] + 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 x, value in enumerate(withoutSpaces): - cmd = cmd.replace(withSpaces[x],value) + for i, value in enumerate(without_spaces): + cmd = cmd.replace(with_spaces[i],value) return cmd - def replaceWithSpaces(self,cmd : str): - withSpaces = ["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"] - withoutSpaces = ["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"] + 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 x, value in enumerate(withoutSpaces): - cmd = cmd.replace(value,withSpaces[x]) + for i, value in enumerate(without_spaces): + cmd = cmd.replace(value,with_spaces[i]) return cmd - async def parseChar(self, ctx, parameters : str): + async def parse_char(self, ctx, parameters : str): user = f"#{ctx.author.id}" cmd = string.capwords(parameters.replace("+","+ ").replace("-","- ").replace(",",", ")) - returnEmbed = False + return_embed = False - cmd = self.replaceSpaces(cmd) + cmd = self.replace_spaces(cmd) - userCharacter = self.bot.database["starwars characters"].find_one({"_id":user}) + user_character = self.bot.database["starwars characters"].find_one({"_id":user}) if cmd == " ": cmd = "" @@ -490,17 +490,17 @@ class StarWarsChar(): if cmd == "": - if userCharacter != None: - title, text = self.characterSheet(userCharacter) - text = self.replaceWithSpaces(text) - returnEmbed = True + 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 f: - templates = json.load(f) - newChar = templates["Character"] - newChar["_id"] = user - self.bot.database["starwars characters"].insert_one(newChar) + 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": @@ -508,22 +508,21 @@ class StarWarsChar(): self.bot.database["starwars characters"].delete_one({"_id":user}) await ctx.send("Character for " + self.bot.database_funcs.get_name(user) + " deleted") else: - await ctx.send(self.replaceWithSpaces(str(self.char_data(user,cmd)))) + await ctx.send(self.replace_with_spaces(str(self.char_data(user,cmd)))) - if returnEmbed: - em = discord.Embed(title = title, description = text, colour=0xDEADBF) - await ctx.send(embed = em) + if return_embed: + embed = discord.Embed(title = title, description = text, colour=0xDEADBF) + await ctx.send(embed = embed) - def lightsaberChar(self,user : str): - userCharacter = 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 userCharacter != None: - return userCharacter["Lightsaber-characteristic"] + if user_character != None: + return user_character["Lightsaber-characteristic"] - def userHasChar(self,user : str): - userCharacter = self.bot.database["starwars characters"].find_one({"_id":user}) - - return userCharacter != None + def user_has_char(self,user : str): + user_character = self.bot.database["starwars characters"].find_one({"_id":user}) + return user_character != None diff --git a/gwendolyn/funcs/star_wars_funcs/star_wars_destiny.py b/gwendolyn/funcs/star_wars_funcs/star_wars_destiny.py index 5f03e76..09979d5 100644 --- a/gwendolyn/funcs/star_wars_funcs/star_wars_destiny.py +++ b/gwendolyn/funcs/star_wars_funcs/star_wars_destiny.py @@ -2,27 +2,27 @@ class StarWarsDestiny(): def __init__(self, bot): self.bot = bot - def destinyNew(self, num : int): + def destiny_new(self, num : int): self.bot.log("Creating a new destiny pool with "+str(num)+" players") - roll, diceResults = self.bot.star_wars.roll.roll(0,0,0,0,0,0,num) + 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 f: - f.write(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(diceResults)+"\n"+self.bot.star_wars.roll.resultToEmoji(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 destinyUse(self, user : str): - with open("gwendolyn/resources/star_wars/destinyPoints.txt","rt") as f: - points = f.read() + 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 f: - f.write(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: @@ -33,15 +33,15 @@ class StarWarsDestiny(): if 'L' in points: points = points.replace("L","B",1) points = "".join(sorted(points)) - with open("gwendolyn/resources/star_wars/destinyPoints.txt","wt") as f: - f.write(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 parseDestiny(self, ctx, cmd : str): + async def parse_destiny(self, ctx, cmd : str): user = f"#{ctx.author.id}" if cmd != "": while cmd[0] == ' ': @@ -51,22 +51,22 @@ class StarWarsDestiny(): if cmd == "": self.bot.log("Retrieving destiny pool info") - with open("gwendolyn/resources/star_wars/destinyPoints.txt","rt") as f: - send_message = self.bot.star_wars.roll.resultToEmoji(f.read()) + 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.destinyNew(int(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.destinyUse(user) + 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 messageItem in message_list[1:]: - await ctx.channel.send(messageItem) + for message_item in message_list[1:]: + await ctx.channel.send(message_item) diff --git a/gwendolyn/funcs/star_wars_funcs/star_wars_roll.py b/gwendolyn/funcs/star_wars_funcs/star_wars_roll.py index 83f602d..c110e5c 100644 --- a/gwendolyn/funcs/star_wars_funcs/star_wars_roll.py +++ b/gwendolyn/funcs/star_wars_funcs/star_wars_roll.py @@ -13,43 +13,43 @@ class StarWarsRoll(): # 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 = "" - diceResult = [] + dice_result = [] for _ in range(abi): choice = random.choice(["","S","S","SS","A","A","SA","AA"]) result += choice - diceResult.append("abi"+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 - diceResult.append("prof"+choice) + dice_result.append("prof"+choice) for _ in range(dif): choice = random.choice(["","F","FF","H","H","H","HH","FH"]) result += choice - diceResult.append("dif"+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 - diceResult.append("cha"+choice) + dice_result.append("cha"+choice) for _ in range(boo): choice = random.choice(["","","S","SA","AA","A"]) result += choice - diceResult.append("boo"+choice) + dice_result.append("boo"+choice) for _ in range(setb): choice = random.choice(["","","F","F","H","H"]) result += choice - diceResult.append("setb"+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 - diceResult.append("force"+choice) + dice_result.append("force"+choice) - return result, diceResult + return result, dice_result # Lets dice cancel each other out def simplify(self, result : str): @@ -78,9 +78,9 @@ class StarWarsRoll(): return simp # Returns emoji that symbolize the dice results - def diceResultToEmoji(self, diceResults : list): + def dice_result_to_emoji(self, dice_results : list): emoji = "" - for result in diceResults: + for result in dice_results: if result == "abiA": emoji += "<:abil1a:695267684476125264> " elif result == "abiSA": @@ -167,7 +167,7 @@ class StarWarsRoll(): return emoji # Returns emoji that symbolize the results of the dice rolls - def resultToEmoji(self, result : str): + def result_to_emoji(self, result : str): emoji = "" for char in result: if char == 'S': @@ -190,7 +190,7 @@ class StarWarsRoll(): return emoji # Converts emoji into letters - def emojiToResult(self, emoji : str): + def emoji_to_result(self, emoji : str): result = "" for char in emoji: if char == "<:light:691010089905029171>": @@ -201,7 +201,7 @@ class StarWarsRoll(): return result # Returns emoji that symbolize the dice - def diceToEmoji(self, dice : list): + def dice_to_emoji(self, dice : list): emoji = "" for _ in range(dice[0]): @@ -222,7 +222,7 @@ class StarWarsRoll(): return emoji # Rolls for obligation - def obligationRoll(self): + def obligation_roll(self): self.bot.log("Rolling for obligation") data = self.bot.database["starwarscharacters"] @@ -239,40 +239,40 @@ class StarWarsRoll(): return random.choice(table) # Rolls for critical injury - async def critRoll(self, ctx, addington : int): - dd = "<:difficulty:690973992470708296>" - sd = "<:setback:690972157890658415>" - bd = "<:boost:690972178216386561>" + 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, "+dd] * 5 + [ - "**Slowed down**: The target can only act during the last allied initiative slot this turn, "+dd] * 5 + [ - "**Sudden Jolt**: The target drops whatever is in hand, "+dd] * 5 + [ - "**Distracted**: The target cannot perform a Free maneuver during his next turn, "+dd] * 5 + [ - "**Off-Balance**: The target adds "+sd+" to his next skill check, "+dd] * 5 + [ - "**Discouraging Wound**: Flip one light side Destiny point to a dark side Destiny point (reverse if NPC), "+dd] * 5 + [ - "**Stunned**: The target is staggered until the end of his next turn, "+dd] * 5 + [ - "**Stinger**: Increase the difficulty of next check by one, "+dd] * 5 + [ - "**Bowled Over**: The target is knocked prone and suffers 1 strain, "+dd+dd] * 5 + [ - "**Head Ringer**: The target increases the difficulty of all Intellect and Cunning checks by one until the end of the encounter, "+dd+dd] * 5 + [ - "**Fearsome Wound**: The target increases the difficulty of all Presence and Willpower checks by one until the end of the encounter, "+dd+dd] * 5 + [ - "**Agonizing Wound**: The target increases the difficulty of all Brawn and Agility checks by one until the end of the encounter, "+dd+dd] * 5 + [ - "**Slightly Dazed**: The target is disoriented until the end of the encounter, "+dd+dd] * 5 + [ - "**Scattered Senses**: The target removes all "+bd+" from skill checks until the end of the encounter, "+dd+dd] * 5 + [ - "**Hamstrung**: The target loses his free maneuver until the end of the encounter, "+dd+dd] * 5 + [ - "**Overpowered**: The target leaves himself open, and the attacker may immediately attempt another free attack agains him, using the exact same pool as the original, "+dd+dd] * 5 + [ - "**Winded**: Until the end of the encounter, the target cannot voluntarily suffer strain to activate any abilities or gain additional maneuvers, "+dd+dd] * 5 + [ - "**Compromised**: Incerase difficulty of all skill checks by one until the end of the encounter, "+dd+dd] * 5 + [ - "**At the brink**: The target suffers 1 strain each time he performs an action, "+dd+dd+dd] * 5 + [ - "**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, "+dd+dd+dd] * 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 "+sd+", "+dd+dd+dd] * 5 + [ + "**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, "+dd+dd+dd] * 5 + [ - "**Blinded**: The target can no longer see. Upgrade the difficulty of all checks twice. Upgrade the difficulty of perception checks three times, "+dd+dd+dd] * 5 + [ - "**Knocked Senseless**: The target is staggered for the remainder of the encounter, "+dd+dd+dd] * 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), "+dd+dd+dd+dd] * 10 + [ - "**The End is Nigh**: The target will die after the last initiative slot during the next round, "+dd+dd+dd+dd] * 10 + [ + "**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): @@ -282,56 +282,56 @@ class StarWarsRoll(): 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, "+dd+dd+dd + 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, "+dd+dd+dd+dd + 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 messageItem in message_list[1:]: - await ctx.channel.send(messageItem) + for message_item in message_list[1:]: + await ctx.channel.send(message_item) # Parses the command into something the other functions understand - async def parseRoll(self, ctx, cmd : str = ""): + 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(" ") - validCommand = False + valid_command = False if commands[0] == "": - rollParameters = [1,0,3,0,0,0,0] + roll_parameters = [1,0,3,0,0,0,0] else: - rollParameters = [0,0,0,0,0,0,0] + roll_parameters = [0,0,0,0,0,0,0] if string.capwords(commands[0]) == "Obligations": - send_message = self.obligationRoll() + 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") - skillLevel = self.bot.star_wars.character.char_data(user,"Skills " + string.capwords(commands[0])) + 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") - charLevel = self.bot.star_wars.character.char_data(user,"Characteristics " + self.bot.star_wars.character.lightsaberChar(user)) + char_level = self.bot.star_wars.character.char_data(user,"Characteristics " + self.bot.star_wars.character.lightsaberChar(user)) else: - charLevel = self.bot.star_wars.character.char_data(user,"Characteristics " + skill_data[string.capwords(commands[0])]) + char_level = self.bot.star_wars.character.char_data(user,"Characteristics " + skill_data[string.capwords(commands[0])]) - abilityDice = abs(charLevel-skillLevel) - proficiencyDice = min(skillLevel,charLevel) + ability_dice = abs(char_level-skill_level) + proficiency_dice = min(skill_level,char_level) - commands = [str(abilityDice)] + [str(proficiencyDice)] + commands[1:] + commands = [str(ability_dice)] + [str(proficiency_dice)] + commands[1:] self.bot.log("Converted skill to dice") - validCommand = True + 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" @@ -343,49 +343,49 @@ class StarWarsRoll(): else: send_message = "Did you mean \"Piloting - Planetary\" or \"Piloting - Space\"" else: - validCommand = True + valid_command = True - if validCommand: + if valid_command: self.bot.log("Converting commands to dice") - for x, command in enumerate(commands): + for i, command in enumerate(commands): if command != "": command = command.upper() if command[0] == "A": - rollParameters[0] = int(command.replace("A","")) + roll_parameters[0] = int(command.replace("A","")) elif command[0] == "P": - rollParameters[1] = int(command.replace("P","")) + roll_parameters[1] = int(command.replace("P","")) elif command[0] == "D": - rollParameters[2] = int(command.replace("D","")) + roll_parameters[2] = int(command.replace("D","")) elif command[0] == "C": - rollParameters[3] = int(command.replace("C","")) + roll_parameters[3] = int(command.replace("C","")) elif command[0] == "B": - rollParameters[4] = int(command.replace("B","")) + roll_parameters[4] = int(command.replace("B","")) elif command[0] == "S": - rollParameters[5] = int(command.replace("S","")) + roll_parameters[5] = int(command.replace("S","")) elif command[0] == "F": - rollParameters[6] = int(command.replace("F","")) + roll_parameters[6] = int(command.replace("F","")) else: - rollParameters[x] = int(command) + roll_parameters[i] = int(command) - self.bot.log("Rolling "+str(rollParameters)) - rollResults, diceResults = self.roll(rollParameters[0],rollParameters[1],rollParameters[2],rollParameters[3],rollParameters[4],rollParameters[5],rollParameters[6]) + self.bot.log("Rolling "+str(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(rollResults) + 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.diceResultToEmoji(diceResults) + "\nEverything cancels out!" + send_message = name + " rolls: " + "\n" + self.dice_result_to_emoji(dice_results) + "\nEverything cancels out!" else: - send_message = name + " rolls: " + "\n" + self.diceResultToEmoji(diceResults) + "\n" + self.resultToEmoji(simplified) + 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 messageItem in message_list[1:]: - if messageItem == "": + for message_item in message_list[1:]: + if message_item == "": self.bot.log("Tried to send empty message") else: - await ctx.channel.send(messageItem) + await ctx.channel.send(message_item) diff --git a/gwendolyn/gwendolyn_client.py b/gwendolyn/gwendolyn_client.py index 850827a..d77fd82 100644 --- a/gwendolyn/gwendolyn_client.py +++ b/gwendolyn/gwendolyn_client.py @@ -94,7 +94,7 @@ class Gwendolyn(commands.Bot): if filename.endswith(".py"): self.load_extension(f"gwendolyn.cogs.{filename[:-3]}") - def log(self, messages, channel: str = "", level: int = 20): + 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) diff --git a/gwendolyn/resources/games/blackjack_table.png b/gwendolyn/resources/games/default_images/blackjack.png similarity index 100% rename from gwendolyn/resources/games/blackjack_table.png rename to gwendolyn/resources/games/default_images/blackjack.png diff --git a/gwendolyn/resources/games/default_images/hangman.png b/gwendolyn/resources/games/default_images/hangman.png new file mode 100644 index 0000000..3370360 Binary files /dev/null and b/gwendolyn/resources/games/default_images/hangman.png differ diff --git a/gwendolyn/resources/long_strings.json b/gwendolyn/resources/long_strings.json index c5964a3..c9c5e42 100644 --- a/gwendolyn/resources/long_strings.json +++ b/gwendolyn/resources/long_strings.json @@ -7,7 +7,7 @@ "Blackjack double": "Adding another {} GwendoBucks to {}'s bet and drawing another card.", "Blackjack different cards": "You can only split if your cards have the same value", "Blackjack split": "Splitting {}'s hand into 2. Adding their original bet to the second hand. You can use \"/blackjack hit/stand/double 1\" and \"/blackjack hit/stand/double 2\" to play the different hands.", - "Blackjack started": "Blackjack game started. Use \"/blackjack bet [amount]\" to enter the game within the next 30 seconds.", + "Blackjack 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.", diff --git a/gwendolyn/resources/paper.jpg b/gwendolyn/resources/paper.jpg deleted file mode 100644 index cafeefb..0000000 Binary files a/gwendolyn/resources/paper.jpg and /dev/null differ diff --git a/gwendolyn/resources/slash_parameters.json b/gwendolyn/resources/slash_parameters.json index e5ecf1c..532d552 100644 --- a/gwendolyn/resources/slash_parameters.json +++ b/gwendolyn/resources/slash_parameters.json @@ -45,68 +45,16 @@ "name" : "cards", "description" : "Get a count of the cards used in blackjack games" }, - "blackjack_double" : { - "base" : "blackjack", - "name" : "double", - "description" : "Double your bet in blackjack", - "options" : [ - { - "name" : "hand", - "description" : "The number of the hand to double your bet on", - "type" : 4, - "required" : "false" - } - ] - }, "blackjack_hilo" : { "base" : "blackjack", "name" : "hilo", "description" : "Get the current hi-lo value for the cards used in blackjack games" }, - "blackjack_hit" : { - "base" : "blackjack", - "name" : "hit", - "description" : "Hit on your hand in blackjack", - "options" : [ - { - "name" : "hand", - "description" : "The number of the hand to hit if you've split", - "type" : 4, - "required" : "false" - } - ] - }, "blackjack_shuffle" : { "base" : "blackjack", "name" : "shuffle", "description" : "Shuffle the cards used in blackjack games" }, - "blackjack_split" : { - "base" : "blackjack", - "name" : "split", - "description" : "Split your hand in blackjack", - "options" : [ - { - "name" : "hand", - "description" : "The number of the hand to split, in case you've already split once", - "type" : 4, - "required" : "false" - } - ] - }, - "blackjack_stand" : { - "base" : "blackjack", - "name" : "stand", - "description" : "Stand on your hand in blackjack", - "options" : [ - { - "name" : "hand", - "description" : "The number of the hand to stand if you've split", - "type" : 4, - "required" : "false" - } - ] - }, "blackjack_start" : { "base" : "blackjack", "name" : "start", diff --git a/gwendolyn/utils/event_handlers.py b/gwendolyn/utils/event_handlers.py index 8fa864c..695005a 100644 --- a/gwendolyn/utils/event_handlers.py +++ b/gwendolyn/utils/event_handlers.py @@ -15,6 +15,7 @@ from discord.ext import commands # Used to compare errors with command from discord_slash.context import SlashContext, ComponentContext from gwendolyn.utils.util_functions import decode_id +from gwendolyn.exceptions import InvalidInteraction class EventHandler(): @@ -62,9 +63,11 @@ class EventHandler(): 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.origin_message.channel + channel = ctx.channel + author = str(ctx.author_id) if info[0].lower() == "plex": if info[1].lower() == "movie": @@ -73,6 +76,7 @@ class EventHandler(): info[2], not isinstance(channel, discord.DMChannel) ) + return elif info[1].lower() == "show": await self.bot.other.plex.add_show( @@ -80,18 +84,20 @@ class EventHandler(): info[2], not isinstance(channel, discord.DMChannel) ) + else: + raise InvalidInteraction(ctx.custom_id, info) - elif info[0].lower() == "hangman": - if str(ctx.author_id) == 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:]) - + 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 str(ctx.author_id) == info[2]: - params = [ + if info[1].lower() == "place" and author == info[2]: + await connect_four.place_piece( ctx, info[3], int(info[4]), @@ -99,14 +105,17 @@ class EventHandler(): int(info[7]), ctx.author_id, int(info[8]) - ] - await connect_four.place_piece(*params) - if info[1].lower() == "end": - if str(ctx.author_id) in [info[2], info[3]]: - params = [ - ctx, [int(info[2]), int(info[3])], info[4], info[5] - ] - await connect_four.surrender(*params) + ) + elif info[1].lower() == "end" and author in [info[2], info[3]]: + await connect_four.surrender( + ctx, [int(info[2]), int(info[3])], info[4], info[5] + ) + else: + raise InvalidInteraction(ctx.custom_id, info) + elif info[0].lower() == "blackjack": + await self.bot.games.blackjack.decode_interaction(ctx, info[1:]) + else: + raise InvalidInteraction(ctx.custom_id, info) diff --git a/gwendolyn/utils/helper_classes.py b/gwendolyn/utils/helper_classes.py index 4e9333e..ca20c5d 100644 --- a/gwendolyn/utils/helper_classes.py +++ b/gwendolyn/utils/helper_classes.py @@ -139,7 +139,7 @@ class DatabaseFuncs(): old_images_path = "gwendolyn/resources/games/old_images/" file_path = old_images_path + f"connect_four{channel.id}" if os.path.isfile(file_path): - with open(file_path, "r") as file_pointer: + with open(file_path, "r", encoding="utf-8") as file_pointer: old_image = int(file_pointer.read()) else: old_image = 0 diff --git a/gwendolyn/utils/util_functions.py b/gwendolyn/utils/util_functions.py index c2a6cf8..9689533 100644 --- a/gwendolyn/utils/util_functions.py +++ b/gwendolyn/utils/util_functions.py @@ -16,12 +16,12 @@ Contains utility functions used by parts of the bot. new_string: str) -> str emoji_to_command(emoji: str) -> str """ +import string import json # Used by long_strings(), get_params() and make_files() import logging # Used for logging import os # Used by make_files() to check if files exist import sys # Used to specify printing for logging import imdb # Used to disable logging for the module -import string BASE_37 = ":" + string.digits + string.ascii_uppercase @@ -109,7 +109,7 @@ def get_options(): options: dict The options of the bot. """ - with open("options.txt", "r") as file_pointer: + with open("options.txt", "r", encoding="utf-8") as file_pointer: data = sanitize(file_pointer.read(), True) options = {} @@ -128,7 +128,7 @@ def get_credentials(): credentials: dict The credentials used by the bot. """ - with open("credentials.txt", "r") as file_pointer: + with open("credentials.txt", "r", encoding="utf-8") as file_pointer: data = sanitize(file_pointer.read()) credentials = {} @@ -155,7 +155,8 @@ def long_strings(): data: dict The long strings and their keys. """ - with open("gwendolyn/resources/long_strings.json", "r") as file_pointer: + long_strings_path = "gwendolyn/resources/long_strings.json" + with open(long_strings_path, "r", encoding="utf-8") as file_pointer: data = json.load(file_pointer) return data @@ -170,7 +171,8 @@ def get_params(): params: dict The parameters for every slash command. """ - with open("gwendolyn/resources/slash_parameters.json", "r") as file_pointer: + path = "gwendolyn/resources/slash_parameters.json" + with open(path, "r", encoding="utf-8") as file_pointer: slash_parameters = json.load(file_pointer) options = get_options() @@ -256,14 +258,14 @@ def make_files(): """Create json file if it doesn't exist.""" if not os.path.isfile(path): log_this(path.split("/")[-1]+" didn't exist. Making it now.") - with open(path, "w") as file_pointer: + with open(path, "w", encoding="utf-8") as file_pointer: json.dump(content, file_pointer, indent=4) def make_txt_file(path, content): """Create txt file if it doesn't exist.""" if not os.path.isfile(path): log_this(path.split("/")[-1]+" didn't exist. Making it now.") - with open(path, "w") as file_pointer: + with open(path, "w", encoding="utf-8") as file_pointer: file_pointer.write(content) def directory(path): @@ -272,7 +274,8 @@ def make_files(): os.makedirs(path) log_this("The "+path.split("/")[-1]+" directory didn't exist") - with open("gwendolyn/resources/starting_files.json") as file_pointer: + file_path = "gwendolyn/resources/starting_files.json" + with open(file_path, "r", encoding="utf-8") as file_pointer: data = json.load(file_pointer) for path, content in data["json"].items(): diff --git a/requirements.txt b/requirements.txt index 5b760a0..4ded3c1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ -aiohttp==3.7.4.post0 -async-timeout==3.0.1 +aiohttp==3.8.1 +async-timeout==4.0.2 attrs==21.2.0 beautifulsoup4==4.9.3 cachetools==4.2.2 @@ -19,10 +19,10 @@ idna==3.2 IMDbPY==2021.4.18 jaraco.context==4.0.0 lark-parser==0.9.0 -lxml==4.6.3 +lxml==4.7.1 more-itertools==8.8.0 multidict==5.1.0 -Pillow==8.3.1 +Pillow==9.0.0 pymongo==3.12.0 requests==2.26.0 setuptools==57.4.0