""" Runs commands, game logic and imaging for blackjack games. *Classes* --------- Blackjack Contains the blackjack game logic. DrawBlackjack Draws images of the blackjack table. """ import random # Used to shuffle the blackjack cards import math # Used for flooring decimal numbers import datetime # Used to generate the game id import asyncio # Used for sleeping from shutil import copyfile import discord # Used for discord.file import discord_slash # Used for typehints from PIL import Image, ImageDraw, ImageFont from gwendolyn.utils import replace_multiple class Blackjack(): """ 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) """ def __init__(self, bot): """Initialize the class.""" self.bot = bot self.draw = DrawBlackjack(bot) self.decks = 4 self.long_strings = self.bot.long_strings def _blackjack_shuffle(self, channel: str): """ Shuffle an amount of decks equal to self.decks. The shuffled cards are placed in the database as the cards that are used by other blackjack functions. *Parameters* ------------ channel: str The id of the channel where the cards will be used. """ self.bot.log("Shuffling the blackjack deck") 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) # 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) def _calc_hand_value(self, hand: list): """ Calculate the value of a blackjack hand. *Parameters* ------------ hand: list The hand to calculate the value of. Each element in the list is a card represented as a string in the format of "xy" where x is the number of the card (0, k, q, and j for 10s, kings, queens and jacks) and y is the suit (d, c, h or s). *Returns* --------- hand_value: int The blackjack value of the hand. """ values = [0] for card in hand: card_value = replace_multiple(card[0], ["0", "k", "q", "j"], "10") if card_value == "a": length = len(values) for i in range(length): values.append(values[i] + 11) values[i] += 1 else: values = [int(i)+int(card_value) for i in values] values.sort() hand_value = values[0] for value in values: if value <= 21: hand_value = value 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}}) return drawn_card def _dealer_draw(self, channel: str): """ Draw a card for the dealer. *Parameters* ------------ channel: str The id of the channel to draw a card for the dealer in. *Returns* --------- done: bool Whether the dealer is done drawing cards. """ game = self.bot.database["blackjack games"].find_one({"_id": channel}) done = False 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) 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) return done def _blackjack_continue(self, channel: str): """ Continues the blackjack game to the next round. *Parameters* ------------ channel: str The id of the channel the blackjack game is in *Returns* --------- send_message: str The message to send to the channel. all_standing: bool If all players are standing. gameDone: bool If the game has finished. """ self.bot.log("Continuing blackjack game") game = self.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}}) all_standing = True pre_all_standing = True message = self.long_strings["Blackjack all players standing"] if game["all standing"]: self.bot.log("All are standing") done = self._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) if all_standing: game_updater = {"$set": {"all standing": True}} blackjack_games.update_one({"_id": channel}, game_updater) self.draw.draw_image(channel) 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 "", True, done else: send_message = self.long_strings["Blackjack commands"] if game["round"] == 0: send_message = send_message.format( self.long_strings["Blackjack first round"]) else: send_message = send_message.format("") return send_message, False, done def _test_if_standing(self, hand: dict, all_standing: bool, pre_all_standing: bool, top_level: bool): """ Test if a player is standing on all their hands. Also resets the hand if it's not standing *Parameters* ------------ hand: dict The hand to test and reset. 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. *Returns* --------- hand: dict The reset hand. all_standing: bool If the player is standing on all their hands. pre_all_standing: bool Is true if all_standing is True, or if a player has done something equivalent to standing but still needs to see the newly drawn card. """ # If the user has not hit, they are by definition standing. if not hand["hit"]: hand["standing"] = True if not hand["standing"]: all_standing = False if self._calc_hand_value(hand["hand"]) >= 21 or hand["doubled"]: hand["standing"] = True else: pre_all_standing = False hand["hit"] = False 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] return hand, all_standing, pre_all_standing def _blackjack_finish(self, channel: str): """ Generate the winnings message after the blackjack game ends. *Parameters* ------------ channel: str The id of the channel the game is in. *Returns* --------- final_winnings: str The winnings message. """ 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"] for user in game["user hands"]: calc_winnings_parameters = [ 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 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 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 self.bot.money.addMoney(user, net_winnings) self.bot.database["blackjack games"].delete_one({"_id": channel}) return final_winnings def _calc_winning(self, hand: dict, dealer_value: int, top_level: bool, dealer_blackjack: bool, dealer_busted: bool): """ Calculate how much a user has won/lost in the blackjack game. *Parameters* ------------ hand: dict The hand to calculate the winnings of. dealer_value: int The dealer's hand value. top_level: bool If the input hand is _all_ if the player's hands. If False, it's one of the hands resulting from a split. dealer_blackjack: bool If the dealer has a blackjack. dealer_busted: bool If the dealer busted. *Returns* --------- winnings: int How much the player has won/lost. net_winnings: int winnings minus the original bet. This is added to the user's account, since the bet was removed from their account when they placed the bet. reason: str The reason for why they won/lost. """ self.bot.log("Calculating winnings") reason = "" bet = hand["bet"] winnings = -1 * bet 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)" 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 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. *Parameters* ------------ channel: guildChannel or DMChannel The channel the game is happening in. game_round: int The round to start. game_id: str The ID of the game. """ self.bot.log("Loop "+str(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 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)) if all_standing: await asyncio.sleep(5) else: await asyncio.sleep(120) blackjack_games = self.bot.database["blackjack games"] game = blackjack_games.find_one({"_id": str(channel.id)}) 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 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}" self.bot.log(log_message, str(channel.id)) async def hit(self, ctx: discord_slash.context.SlashContext, hand_number: int = 0): """ Hit on a hand. *Parameters* ------------ ctx: discord_slash.context.SlashContext The context of the command. hand_number: int = 0 The number of the hand to hit. """ await self.bot.defer(ctx) channel = str(ctx.channel_id) user = f"#{ctx.author.id}" round_done = False blackjack_games = self.bot.database["blackjack games"] game = blackjack_games.find_one({"_id": channel}) if user in game["user hands"]: user_hands = game["user hands"][user] hand, hand_number = self._get_hand_number(user_hands, hand_number) if hand is None: log_message = "They didn't specify a hand" 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 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 = 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: game_id = game["game_id"] self.bot.log("Hit calling self._blackjack_loop()", channel) await self._blackjack_loop(ctx.channel, game["round"]+1, game_id) async def double(self, ctx: discord_slash.context.SlashContext, hand_number: int = 0): """ Double a hand. *Parameters* ------------ ctx: discord_slash.context.SlashContext The context of the command. hand_number: int = 0 The number of the hand to double. """ await self.bot.defer(ctx) channel = str(ctx.channel_id) user = f"#{ctx.author.id}" round_done = False blackjack_games = self.bot.database["blackjack games"] game = blackjack_games.find_one({"_id": channel}) if user in game["user hands"]: hand_parameters = [game["user hands"][user], hand_number] hand, hand_number = self._get_hand_number(*hand_parameters) if hand is None: log_message = "They didn't specify a hand" 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) 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" 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" await ctx.send(send_message) self.bot.log(log_message) 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) async def stand(self, ctx: discord_slash.context.SlashContext, hand_number: int = 0): """ Stand on a hand. *Parameters* ------------ ctx: discord_slash.context.SlashContext The context of the command. hand_number: int = 0 The number of the hand to stand on. """ await self.bot.defer(ctx) channel = str(ctx.channel_id) user = f"#{ctx.author.id}" round_done = False blackjack_games = self.bot.database["blackjack games"] game = blackjack_games.find_one({"_id": channel}) if game is not None and user in game["user hands"]: hand_parameters = [game["user hands"][user], hand_number] hand, hand_number = self._get_hand_number(*hand_parameters) 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 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 = 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" 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 split(self, ctx: discord_slash.context.SlashContext, hand_number: int = 0): """ Split a hand. *Parameters* ------------ ctx: discord_slash.context.SlashContext The context of the command. hand_number: int = 0 The number of the hand to split. """ await self.bot.defer(ctx) channel = str(ctx.channel_id) user = f"#{ctx.author.id}" round_done = False hand_number_error = False blackjack_games = self.bot.database["blackjack games"] game = blackjack_games.find_one({"_id": channel}) if game["user hands"][user]["split"] == 0: hand = game["user hands"][user] new_hand = game["user hands"][user]["other hand"] hand_number = 0 other_hand = 2 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 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 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) hand["hit"] = True new_hand["hit"] = True new_hand = { "hand": [], "bet": 0, "standing": False, "busted": False, "blackjack": False, "hit": True, "doubled": False } new_hand["bet"] = hand["bet"] 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 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 if other_hand_value > 21: new_hand["busted"] = True elif other_hand_value == 21: new_hand["blackjack"] = 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" 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): """ Enter the blackjack game. *Parameters* ------------ ctx: discord_slash.context.SlashContext The context of the command. bet: int The bet to enter with. """ await self.bot.defer(ctx) channel = str(ctx.channel_id) user = f"#{ctx.author.id}" collection = self.bot.database["blackjack games"] game = collection.find_one({"_id": channel}) 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" 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" elif game["round"] != 0: send_message = "The table is no longer taking bets" log_message = "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" elif self.bot.money.checkBalance(user) < bet: send_message = "You don't have enough GwendoBucks" log_message = "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 = { "hand": player_hand, "bet": bet, "standing": False, "busted": False, "blackjack": blackjack_hand, "hit": True, "doubled": False, "split": 0, "other hand": {}, "third hand": {}, "fourth hand": {} } function = {"$set": {f"user hands.{user}": new_hand}} collection.update_one({"_id": channel}, function) enter_game_text = "entered the game with a bet of" bet_text = f"{bet} GwendoBucks" send_message = f"{user_name} {enter_game_text} {bet_text}" log_message = send_message self.bot.log(log_message) await ctx.send(send_message) async def start(self, ctx: discord_slash.context.SlashContext): """ Start a blackjack game. *Parameters* ------------ ctx: discord_slash.context.SlashContext The context of the command. """ await self.bot.defer(ctx) channel = str(ctx.channel_id) blackjack_min_cards = 50 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: 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) await ctx.channel.send("Shuffling the deck...") game = self.bot.database["blackjack games"].find_one({"_id": channel}) self.bot.log("Trying to start a blackjack game in "+channel) 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) self.bot.log("There was already a game going on") async def hilo(self, ctx: discord_slash.context.SlashContext): """ Get the hilo of the blackjack game. *Parameters* ------------ ctx: discord_slash.context.SlashContext The context of the command. """ channel = ctx.channel_id data = self.bot.database["hilo"].find_one({"_id": str(channel)}) if data is not None: hilo = str(data["hilo"]) else: hilo = "0" await ctx.send(f"Hi-lo value: {hilo}", hidden=True) async def shuffle(self, ctx: discord_slash.context.SlashContext): """ Shuffle the cards used for blackjack. *Parameters* ------------ ctx: discord_slash.context.SlashContext The context of the command. """ channel = ctx.channel_id self._blackjack_shuffle(str(channel)) self.bot.log("Shuffling the blackjack deck...", str(channel)) await ctx.send("Shuffling the deck...") async def cards(self, ctx: discord_slash.context.SlashContext): """ Get how many cards are left for blackjack. *Parameters* ------------ ctx: discord_slash.context.SlashContext 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"]) decks_left = round(cards_left/52, 1) send_message = f"Cards left:\n{cards_left} cards, {decks_left} decks" await ctx.send(send_message, hidden=True) class DrawBlackjack(): """ 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): """Initialize the class.""" self.bot = bot # pylint: disable=invalid-name self.BORDER = 100 self.SMALLBORDER = int(self.BORDER/3.5) self.PLACEMENT = [2, 1, 3, 0, 4] # pylint: enable=invalid-name def draw_image(self, channel: str): """ Draw the table image. *Parameters* ------------ channel: str The id of the channel the game is in. """ self.bot.log("Drawing blackjack table", channel) game = self.bot.database["blackjack games"].find_one({"_id": channel}) 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 def _draw_hand(self, hand: dict, dealer: bool, busted: bool, blackjack: bool): """ Draw a hand. *Parameters* ------------ hand: dict The hand to draw. dealer: bool If the hand belongs to the dealer who hasn't shown their second card. busted: bool If the hand is busted. blackjack: bool If the hand has a blackjack. *Returns* --------- background: Image The image of the hand. """ self.bot.log("Drawing hand {hand}, {busted}, {blackjack}") 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/" 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) width, height = background.size text_height = 290+self.BORDER white = (255, 255, 255) black = (0, 0, 0) red = (255, 50, 50) gold = (155, 123, 0) if busted: 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)