diff --git a/cogs/GameCogs.py b/cogs/GameCogs.py index c813e24..ebd1f54 100644 --- a/cogs/GameCogs.py +++ b/cogs/GameCogs.py @@ -50,7 +50,7 @@ class BlackjackCog(commands.Cog): @cog_ext.cog_subcommand(**params["blackjackBet"]) async def blackjackBet(self, ctx, bet): """Enter the game of blackjack with a bet.""" - await self.bot.games.blackjack.playerDrawHand(ctx, bet) + await self.bot.games.blackjack.enterGame(ctx, bet) @cog_ext.cog_subcommand(**params["blackjackStand"]) async def blackjackStand(self, ctx, hand=""): diff --git a/funcs/__init__.py b/funcs/__init__.py index 729f445..597efd8 100644 --- a/funcs/__init__.py +++ b/funcs/__init__.py @@ -1,6 +1,6 @@ """A collection of all Gwendolyn functions.""" -__all__ = ["Games" , "Money", "LookupFuncs", "StarWars"] +__all__ = ["Games", "Money", "LookupFuncs", "StarWars"] from .games import Money, Games diff --git a/funcs/games/blackjack.py b/funcs/games/blackjack.py index 1d68b0d..075e33d 100644 --- a/funcs/games/blackjack.py +++ b/funcs/games/blackjack.py @@ -1,47 +1,109 @@ -import random -import math -import datetime -import asyncio -import discord +""" +Runs commands, game logic and imaging for blackjack games. + +*Classes* +--------- + Blackjack + Contains the blackjack game logic. + DrawBlackjack + Draws images of the blackjack table. +""" +import random # Used to shuffle the blackjack cards +import math # Used for flooring decimal numbers +import datetime # Used to generate the game id +import asyncio # Used for sleeping +import discord # Used for discord.file +import discord_slash # Used for typehints from PIL import Image, ImageDraw, ImageFont from shutil import copyfile from utils import replaceMultiple + class Blackjack(): - def __init__(self,bot): + """ + Deals with blackjack commands and gameplay logic. + + *Methods* + --------- + hit(ctx: discord_slash.context.SlashContext, + handNumber: int = 0) + double(ctx: discord_slash.context.SlashContext, + handNumber: int = 0) + stand(ctx: discord_slash.context.SlashContext, + handNumber: int = 0) + split(ctx: discord_slash.context.SlashContext, + handNumber: int = 0) + enterGame(ctx: discord_slash.context.SlashContext, bet: int) + start(ctx: discord_slash.context.SlashContext) + hilo(ctx: discord_slash.context.SlashContext) + shuffle(ctx: discord_slash.context.SlashContext) + cards(ctx: discord_slash.context.SlashContext) + """ + + def __init__(self, bot): + """Initialize the class.""" self.bot = bot self.draw = DrawBlackjack(bot) + self.decks = 4 - # Shuffles the blackjack cards - def blackjackShuffle(self, decks, channel): + def _blackjackShuffle(self, channel: str): + """ + Shuffle an amount of decks equal to self.decks. + + The shuffled cards are placed in the database as the cards that + are used by other blackjack functions. + + *Parameters* + ------------ + channel: str + The id of the channel where the cards will be used. + """ self.bot.log("Shuffling the blackjack deck") - with open("resources/games/deckOfCards.txt","r") as f: + with open("resources/games/deckOfCards.txt", "r") as f: deck = f.read() - allDecks = deck.split("\n") * decks + allDecks = deck.split("\n") * self.decks random.shuffle(allDecks) - self.bot.database["blackjack cards"].update_one({"_id":channel},{"$set":{"_id":channel,"cards":allDecks}},upsert=True) + blackjackCards = self.bot.database["blackjack cards"] + cards = {"_id": channel} + cardUpdater = {"$set": {"_id": channel, "cards": allDecks}} + blackjackCards.update_one(cards, cardUpdater, upsert=True) # Creates hilo file - self.bot.log("creating hilo doc for "+channel) + self.bot.log(f"creating hilo doc for {channel}") data = 0 - self.bot.database["hilo"].update_one({"_id":channel},{"$set":{"_id":channel,"hilo":data}},upsert=True) + blackjackHilo = self.bot.database["hilo"] + hiloUpdater = {"$set": {"_id": channel, "hilo": data}} + blackjackHilo.update_one({"_id": channel}, hiloUpdater, upsert=True) - return + def _calcHandValue(self, hand: list): + """ + Calculate the value of a blackjack hand. - # Calculates the value of a blackjack hand - def calcHandValue(self, hand : list): - self.bot.log("Calculating hand value") + *Parameters* + ------------ + hand: list + The hand to calculate the value of. Each element in the + list is a card represented as a string in the format of + "xy" where x is the number of the card (0, k, q, and j + for 10s, kings, queens and jacks) and y is the suit (d, + c, h or s). + + *Returns* + --------- + handValue: int + The blackjack value of the hand. + """ values = [] values.append(0) for card in hand: cardValue = card[0] - cardValue = replaceMultiple(cardValue,["0","k","q","j"],"10") + cardValue = replaceMultiple(cardValue, ["0", "k", "q", "j"], "10") if cardValue == "a": length = len(values) for x in range(length): @@ -58,102 +120,180 @@ class Blackjack(): if value <= 21: handValue = value - self.bot.log("Calculated "+str(hand)+" to be "+str(handValue)) + self.bot.log(f"Calculated the value of {hand} to be {handValue}") return handValue - # Draws a card from the deck - def drawCard(self, channel): + def _drawCard(self, channel: str): + """ + Draw a card from the stack. + + *Parameters* + ------------ + channel: str + The id of the channel the card is drawn in. + """ self.bot.log("drawing a card") - drawnCard = self.bot.database["blackjack cards"].find_one({"_id":channel})["cards"][0] - self.bot.database["blackjack cards"].update_one({"_id":channel},{"$pop":{"cards":-1}}) - value = self.calcHandValue([drawnCard]) + blackjackCards = self.bot.database["blackjack cards"] + drawnCard = blackjackCards.find_one({"_id": channel})["cards"][0] + blackjackCards.update_one({"_id": channel}, {"$pop": {"cards": -1}}) + value = self._calcHandValue([drawnCard]) + + blackjackHilo = self.bot.database["hilo"] if value <= 6: - self.bot.database["hilo"].update_one({"_id":channel},{"$inc":{"hilo":1}}) + blackjackHilo.update_one({"_id": channel}, {"$inc": {"hilo": 1}}) elif value >= 10: - self.bot.database["hilo"].update_one({"_id":channel},{"$inc":{"hilo":-1}}) + blackjackHilo.update_one({"_id": channel}, {"$inc": {"hilo": -1}}) return drawnCard - # Dealer draws a card and checks if they should draw another one - def dealerDraw(self,channel): - game = self.bot.database["blackjack games"].find_one({"_id":channel}) + def _dealerDraw(self, channel: str): + """ + Draw a card for the dealer. + + *Parameters* + ------------ + channel: str + The id of the channel to draw a card for the dealer in. + + *Returns* + --------- + done: bool + Whether the dealer is done drawing cards. + """ + game = self.bot.database["blackjack games"].find_one({"_id": channel}) done = False dealerHand = game["dealer hand"] - if self.calcHandValue(dealerHand) < 17: - dealerHand.append(self.drawCard(channel)) - self.bot.database["blackjack games"].update_one({"_id":channel},{"$set":{"dealer hand":dealerHand}}) + blackjackGames = self.bot.database["blackjack games"] + + if self._calcHandValue(dealerHand) < 17: + dealerHand.append(self._drawCard(channel)) + dealerUpdater = {"$set": {"dealer hand": dealerHand}} + blackjackGames.update_one({"_id": channel}, dealerUpdater) else: done = True - if self.calcHandValue(dealerHand) > 21: - self.bot.database["blackjack games"].update_one({"_id":channel},{"$set":{"dealer busted":True}}) + if self._calcHandValue(dealerHand) > 21: + dealerUpdater = {"$set": {"dealer busted": True}} + blackjackGames.update_one({"_id": channel}, dealerUpdater) return done - # Goes to the next round and calculates some stuff - def blackjackContinue(self, channel): + def _blackjackContinue(self, channel: str): + """ + Continues the blackjack game to the next round. + + *Parameters* + ------------ + channel: str + The id of the channel the blackjack game is in + + *Returns* + --------- + sendMessage: str + The message to send to the channel. + allStanding: bool + If all players are standing. + gameDone: bool + If the game has finished. + """ self.bot.log("Continuing blackjack game") - game = self.bot.database["blackjack games"].find_one({"_id":channel}) + game = self.bot.database["blackjack games"].find_one({"_id": channel}) done = False - self.bot.database["blackjack games"].update_one({"_id":channel},{"$inc":{"round":1}}) + blackjackGames = self.bot.database["blackjack games"] + blackjackGames.update_one({"_id": channel}, {"$inc": {"round": 1}}) allStanding = True preAllStanding = True - message = "All players are standing. The dealer now shows his cards and draws." + message = self.bot.longStrings["Blackjack all players standing"] if game["all standing"]: self.bot.log("All are standing") - done = self.dealerDraw(channel) + done = self._dealerDraw(channel) message = "The dealer draws a card." - game = self.bot.database["blackjack games"].find_one({"_id":channel}) + blackjackGames.find_one({"_id": channel}) self.bot.log("Testing if all are standing") for user in game["user hands"]: - try: - newUser, allStanding, preAllStanding = self.testIfStanding(game["user hands"][user],allStanding,preAllStanding,True) - self.bot.database["blackjack games"].update_one({"_id":channel},{"$set":{"user hands."+user:newUser}}) - except: - self.bot.log("Error in testing if all are standing (error code 1331)") + userHand = game["user hands"][user] + testParams = [userHand, allStanding, preAllStanding, True] + standingTest = (self._testIfStanding(*testParams)) + newUser, allStanding, preAllStanding = standingTest + handUpdater = {"$set": {"user hands."+user: newUser}} + blackjackGames.update_one({"_id": channel}, handUpdater) if allStanding: - self.bot.database["blackjack games"].update_one({"_id":channel},{"$set":{"all standing":True}}) + gameUpdater = {"$set": {"all standing": True}} + blackjackGames.update_one({"_id": channel}, gameUpdater) - try: - self.draw.drawImage(channel) - except: - self.bot.log("Error drawing blackjack table (error code 1340)") + self.draw.drawImage(channel) if allStanding: - if done == False: + if not done: return message, True, done else: return "The dealer is done drawing cards", True, done elif preAllStanding: return "", True, done else: - if game["round"] == 1: - firstRoundMessage = ". You can also double down with \"/blackjack double\" or split with \"/blackjack split\"" + if game["round"] == 0: + firstRoundMsg = self.bot.longStrings["Blackjack first round"] else: - firstRoundMessage = "" - return "You have 2 minutes to either hit or stand with \"/blackjack hit\" or \"/blackjack stand\""+firstRoundMessage+". It's assumed you're standing if you don't make a choice.", False, done + firstRoundMsg = "" - def testIfStanding(self, hand,allStanding,preAllStanding,topLevel): - if hand["hit"] == False: + sendMessage = self.bot.longStrings["Blackjack commands"] + print(firstRoundMsg) + sendMessage = sendMessage.format(firstRoundMsg) + return sendMessage, False, done + + def _testIfStanding(self, hand: dict, allStanding: bool, + preAllStanding: bool, topLevel: bool): + """ + Test if a player is standing on all their hands. + + Also resets the hand if it's not standing + + *Parameters* + ------------ + hand: dict + The hand to test and reset. + allStanding: bool + Is set to True at the top level. If it's false, the + player is not standing on one of the previously tested + hands. + preAllStanding: bool + Is set to True at the top level. + topLevel: bool + If the input hand is _all_ if the player's hands. If + False, it's one of the hands resulting from a split. + + *Returns* + --------- + hand: dict + The reset hand. + allStanding: bool + If the player is standing on all their hands. + preAllStanding: bool + Is true if allStanding is True, or if a player has done + something equivalent to standing but still needs to see + the newly drawn card. + """ + # If the user has not hit, they are by definition standing. + if not hand["hit"]: hand["standing"] = True - if hand["standing"] == False: + if not hand["standing"]: allStanding = False - if self.calcHandValue(hand["hand"]) >= 21 or hand["doubled"]: + if self._calcHandValue(hand["hand"]) >= 21 or hand["doubled"]: hand["standing"] = True else: preAllStanding = False @@ -162,29 +302,344 @@ class Blackjack(): if topLevel: if hand["split"] >= 1: - hand["other hand"], allStanding, preAllStanding = self.testIfStanding(hand["other hand"],allStanding,preAllStanding,False) + testHand = hand["other hand"] + testParams = [testHand, allStanding, preAllStanding, False] + standingTest = (self._testIfStanding(*testParams)) + hand["other hand"], allStanding, preAllStanding = standingTest if hand["split"] >= 2: - hand["third hand"], allStanding, preAllStanding = self.testIfStanding(hand["third hand"],allStanding,preAllStanding,False) + testHand = hand["third hand"] + testParams = [testHand, allStanding, preAllStanding, False] + standingTest = (self._testIfStanding(*testParams)) + hand["third hand"], allStanding, preAllStanding = standingTest if hand["split"] >= 3: - hand["fourth hand"], allStanding, preAllStanding = self.testIfStanding(hand["fourth hand"],allStanding,preAllStanding,False) + testHand = hand["fourth hand"] + testParams = [testHand, allStanding, preAllStanding, False] + standingTest = (self._testIfStanding(*testParams)) + hand["fourth hand"], allStanding, preAllStanding = standingTest return hand, allStanding, preAllStanding - # When players try to hit - async def hit(self, ctx, handNumber = 0): + def _blackjackFinish(self, channel: str): + """ + Generate the winnings message after the blackjack game ends. + + *Parameters* + ------------ + channel: str + The id of the channel the game is in. + + *Returns* + --------- + finalWinnings: str + The winnings message. + """ + finalWinnings = "*Final Winnings:*\n" + + game = self.bot.database["blackjack games"].find_one({"_id": channel}) + + dealerValue = self._calcHandValue(game["dealer hand"]) + dealerBlackjack = game["dealer blackjack"] + dealerBusted = game["dealer busted"] + + for user in game["user hands"]: + _calcWinningsParams = [ + game["user hands"][user], + dealerValue, + True, + dealerBlackjack, + dealerBusted + ] + winningCalc = (self._calcWinnings(*_calcWinningsParams)) + winnings, netWinnings, reason = winningCalc + + userName = self.bot.databaseFuncs.getName(user) + + if winnings < 0: + if winnings == -1: + finalWinnings += f"{userName} lost 1 GwendoBuck {reason}\n" + else: + moneyLost = -1 * winnings + winningText = f"{userName} lost {moneyLost} GwendoBucks" + winningText += f" {reason}\n" + finalWinnings += winningText + else: + if winnings == 1: + finalWinnings += f"{userName} won 1 GwendoBuck {reason}\n" + else: + winningText = f"{userName} won {winnings} GwendoBucks" + winningText += f" {reason}\n" + finalWinnings += winningText + + self.bot.money.addMoney(user, netWinnings) + + self.bot.database["blackjack games"].delete_one({"_id": channel}) + + return finalWinnings + + def _calcWinnings(self, hand: dict, dealerValue: int, topLevel: bool, + dealerBlackjack: bool, dealerBusted: bool): + """ + Calculate how much a user has won/lost in the blackjack game. + + *Parameters* + ------------ + hand: dict + The hand to calculate the winnings of. + dealerValue: int + The dealer's hand value. + topLevel: bool + If the input hand is _all_ if the player's hands. If + False, it's one of the hands resulting from a split. + dealerBlackjack: bool + If the dealer has a blackjack. + dealerBusted: bool + If the dealer busted. + + *Returns* + --------- + winnings: int + How much the player has won/lost. + netWinnings: int + winnings minus the original bet. This is added to the + user's account, since the bet was removed from their + account when they placed the bet. + reason: str + The reason for why they won/lost. + """ + self.bot.log("Calculating winnings") + reason = "" + bet = hand["bet"] + winnings = -1 * bet + netWinnings = 0 + handValue = self._calcHandValue(hand["hand"]) + + if hand["blackjack"] and not dealerBlackjack: + reason += "(blackjack)" + winnings += math.floor(2.5 * bet) + netWinnings += math.floor(2.5 * bet) + elif dealerBlackjack: + reason += "(dealer blackjack)" + elif hand["busted"]: + reason += "(busted)" + else: + if dealerBusted: + reason = "(dealer busted)" + winnings += 2 * bet + netWinnings += 2 * bet + elif handValue > dealerValue: + winnings += 2 * bet + netWinnings += 2 * bet + reason = "(highest value)" + elif handValue == dealerValue: + reason = "(pushed)" + winnings += bet + netWinnings += bet + else: + reason = "(highest value)" + + if topLevel: + if hand["split"] >= 1: + _calcWinningsParams = [ + hand["other hand"], + dealerValue, + False, + dealerBlackjack, + dealerBusted + ] + winningsCalc = self._calcWinnings(*_calcWinningsParams) + winningsTemp, netWinningsTemp, reasonTemp = winningsCalc + winnings += winningsTemp + netWinnings += netWinningsTemp + reason += reasonTemp + if hand["split"] >= 2: + _calcWinningsParams = [ + hand["third hand"], + dealerValue, + False, + dealerBlackjack, + dealerBusted + ] + winningsCalc = self._calcWinnings(*_calcWinningsParams) + winningsTemp, netWinningsTemp, reasonTemp = winningsCalc + winnings += winningsTemp + netWinnings += netWinningsTemp + reason += reasonTemp + if hand["split"] >= 3: + _calcWinningsParams = [ + hand["fourth hand"], + dealerValue, + False, + dealerBlackjack, + dealerBusted + ] + winningsCalc = self._calcWinnings(*_calcWinningsParams) + winningsTemp, netWinningsTemp, reasonTemp = winningsCalc + winnings += winningsTemp + netWinnings += netWinningsTemp + reason += reasonTemp + + return winnings, netWinnings, reason + + def _getHandNumber(self, user: dict, handNumber: int): + """ + Get the hand with the given number. + + *Parameters* + ------------ + user: dict + The full hand dict of the user. + handNumber: int + The number of the hand to get. + + *Returns* + --------- + hand: dict + The hand. + handNumber: int + The same as handNumber, except if the user hasn't + split. If the user hasn't split, returns 0. + """ + hand = None + + if user["split"] == 0: + hand = user + handNumber = 0 + else: + if handNumber != 0: + if handNumber == 1: + hand = user + elif handNumber == 2: + hand = user["other hand"] + elif handNumber == 3: + hand = user["third hand"] + elif handNumber == 4: + hand = user["fourth hand"] + + return hand, handNumber + + def _isRoundDone(self, game: dict): + """ + Find out if the round is done. + + *Parameters* + ------------ + game: dict + The game to check. + + *Returns* + --------- + roundDone: bool + Whether the round is done. + """ + roundDone = True + + for person in game["user hands"].values(): + if (not person["hit"]) and (not person["standing"]): + roundDone = False + + if person["split"] > 0: + if not person["other hand"]["hit"]: + if not person["other hand"]["standing"]: + roundDone = False + + if person["split"] > 1: + if not person["third hand"]["hit"]: + if not person["third hand"]["standing"]: + roundDone = False + + if person["split"] > 2: + if not person["fourth hand"]["hit"]: + if not person["fourth hand"]["standing"]: + roundDone = False + + return roundDone + + async def _blackjackLoop(self, channel, gameRound: int, gameID: str): + """ + Run blackjack logic and continue if enough time passes. + + *Parameters* + ------------ + channel: guildChannel or DMChannel + The channel the game is happening in. + gameRound: int + The round to start. + gameID: str + The ID of the game. + """ + self.bot.log("Loop "+str(gameRound), str(channel.id)) + + oldImagePath = f"resources/games/oldImages/blackjack{channel.id}" + with open(oldImagePath, "r") as f: + oldImage = await channel.fetch_message(int(f.read())) + + continueData = (self._blackjackContinue(str(channel.id))) + new_message, allStanding, gamedone = continueData + if new_message != "": + self.bot.log(new_message, str(channel.id)) + await channel.send(new_message) + + if not gamedone: + await oldImage.delete() + tablesPath = "resources/games/blackjackTables/" + filePath = f"{tablesPath}blackjackTable{channel.id}.png" + oldImage = await channel.send(file=discord.File(filePath)) + with open(oldImagePath, "w") as f: + f.write(str(oldImage.id)) + + if allStanding: + await asyncio.sleep(5) + else: + await asyncio.sleep(120) + + blackjackGames = self.bot.database["blackjack games"] + game = blackjackGames.find_one({"_id": str(channel.id)}) + + if game is None: + rightRound = False + else: + realRound = game["round"] or -1 + realID = game["gameID"] or -1 + rightRound = gameRound == realRound and gameID == realID + + if rightRound: + if not gamedone: + logMessage = f"Loop {gameRound} calling self._blackjackLoop()" + self.bot.log(logMessage, str(channel.id)) + await self._blackjackLoop(channel, gameRound+1, gameID) + else: + new_message = self._blackjackFinish(str(channel.id)) + await channel.send(new_message) + else: + logMessage = f"Ending loop on round {gameRound}" + self.bot.log(logMessage, str(channel.id)) + + async def hit(self, ctx: discord_slash.context.SlashContext, + handNumber: int = 0): + """ + Hit on a hand. + + *Parameters* + ------------ + ctx: discord_slash.context.SlashContext + The context of the command. + handNumber: int = 0 + The number of the hand to hit. + """ await self.bot.defer(ctx) channel = str(ctx.channel_id) user = f"#{ctx.author.id}" roundDone = False - game = self.bot.database["blackjack games"].find_one({"_id":channel}) + blackjackGames = self.bot.database["blackjack games"] + game = blackjackGames.find_one({"_id": channel}) if user in game["user hands"]: userHands = game["user hands"][user] - hand, handNumber = self.getHandNumber(userHands, handNumber) + hand, handNumber = self._getHandNumber(userHands, handNumber) - if hand == None: + if hand is None: logMessage = "They didn't specify a hand" sendMessage = "You need to specify a hand" elif game["round"] <= 0: @@ -197,28 +652,27 @@ class Blackjack(): logMessage = "They're already standing" sendMessage = "You can't hit when you're standing" else: - hand["hand"].append(self.drawCard(channel)) + hand["hand"].append(self._drawCard(channel)) hand["hit"] = True - handValue = self.calcHandValue(hand["hand"]) + handValue = self._calcHandValue(hand["hand"]) if handValue > 21: hand["busted"] = True if handNumber == 2: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".other hand":hand}}) + handPath = f"user hands.{user}.other hand" elif handNumber == 3: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".third hand":hand}}) + handPath = f"user hands.{user}.third hand" elif handNumber == 4: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".fourth hand":hand}}) + handPath = f"user hands.{user}.fourth hand" else: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user:hand}}) + handPath = f"user hands.{user}" - roundDone = self.isRoundDone(self.bot.database["blackjack games"].find_one({"_id":channel})) + gameUpdater = {"$set": {handPath: hand}} + blackjackGames.update_one({"_id": channel}, gameUpdater) + game = blackjackGames.find_one({"_id": channel}) + roundDone = self._isRoundDone(game) sendMessage = f"{ctx.author.display_name} hit" logMessage = "They succeeded" @@ -231,22 +685,34 @@ class Blackjack(): if roundDone: gameID = game["gameID"] - self.bot.log("Hit calling self.blackjackLoop()", channel) - await self.blackjackLoop(ctx.channel, game["round"]+1, gameID) + self.bot.log("Hit calling self._blackjackLoop()", channel) + await self._blackjackLoop(ctx.channel, game["round"]+1, gameID) - # When players try to double down - async def double(self, ctx, handNumber = 0): + async def double(self, ctx: discord_slash.context.SlashContext, + handNumber: int = 0): + """ + Double a hand. + + *Parameters* + ------------ + ctx: discord_slash.context.SlashContext + The context of the command. + handNumber: int = 0 + The number of the hand to double. + """ await self.bot.defer(ctx) channel = str(ctx.channel_id) user = f"#{ctx.author.id}" roundDone = False + blackjackGames = self.bot.database["blackjack games"] - game = self.bot.database["blackjack games"].find_one({"_id":channel}) + game = blackjackGames.find_one({"_id": channel}) if user in game["user hands"]: - hand, handNumber = self.getHandNumber(game["user hands"][user],handNumber) + handParams = [game["user hands"][user], handNumber] + hand, handNumber = self._getHandNumber(*handParams) - if hand == None: + if hand is None: logMessage = "They didn't specify a hand" sendMessage = "You need to specify a hand" elif game["round"] <= 0: @@ -261,42 +727,42 @@ class Blackjack(): elif len(hand["hand"]) != 2: logMessage = "They tried to double after round 1" sendMessage = "You can only double on the first round" + elif self.bot.money.checkBalance(user) < hand["bet"]: + logMessage = "They tried to double without being in the game" + sendMessage = "You can't double when you're not in the game" else: bet = hand["bet"] - if self.bot.money.checkBalance(user) < bet: - logMessage = "They tried to double without being in the game" - sendMessage = "You can't double when you're not in the game" + self.bot.money.addMoney(user, -1 * bet) + + hand["hand"].append(self._drawCard(channel)) + hand["hit"] = True + hand["doubled"] = True + hand["bet"] += bet + + handValue = self._calcHandValue(hand["hand"]) + + if handValue > 21: + hand["busted"] = True + + if handNumber == 2: + handPath = f"user hands.{user}.other hand" + elif handNumber == 3: + handPath = f"user hands.{user}.third hand" + elif handNumber == 4: + handPath = f"user hands.{user}.fourth hand" else: - self.bot.money.addMoney(user,-1 * bet) + handPath = f"user hands.{user}" - hand["hand"].append(self.drawCard(channel)) - hand["hit"] = True - hand["doubled"] = True - hand["bet"] += bet + gameUpdater = {"$set": {handPath: hand}} + blackjackGames.update_one({"_id": channel}, gameUpdater) - handValue = self.calcHandValue(hand["hand"]) + game = blackjackGames.find_one({"_id": channel}) + roundDone = self._isRoundDone(game) - - if handValue > 21: - hand["busted"] = True - - if handNumber == 2: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".other hand":hand}}) - elif handNumber == 3: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".third hand":hand}}) - elif handNumber == 4: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".fourth hand":hand}}) - else: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user:hand}}) - - roundDone = self.isRoundDone(self.bot.database["blackjack games"].find_one({"_id":channel})) - - sendMessage = f"Adding another {bet} GwendoBucks to {self.bot.databaseFuncs.getName(user)}'s bet and drawing another card." - logMessage = "They succeeded" + sendMessage = self.bot.longStrings["Blackjack double"] + userName = self.bot.databaseFuncs.getName(user) + sendMessage = sendMessage.format(bet, userName) + logMessage = "They succeeded" else: logMessage = "They tried to double without being in the game" sendMessage = "You can't double when you're not in the game" @@ -306,23 +772,34 @@ class Blackjack(): if roundDone: gameID = game["gameID"] - self.bot.log("Double calling self.blackjackLoop()", channel) - await self.blackjackLoop(ctx.channel, game["round"]+1, gameID) + self.bot.log("Double calling self._blackjackLoop()", channel) + await self._blackjackLoop(ctx.channel, game["round"]+1, gameID) - # When players try to stand - async def stand(self, ctx, handNumber = 0): + async def stand(self, ctx: discord_slash.context.SlashContext, + handNumber: int = 0): + """ + Stand on a hand. + + *Parameters* + ------------ + ctx: discord_slash.context.SlashContext + The context of the command. + handNumber: int = 0 + The number of the hand to stand on. + """ await self.bot.defer(ctx) channel = str(ctx.channel_id) user = f"#{ctx.author.id}" roundDone = False - game = self.bot.database["blackjack games"].find_one({"_id":channel}) + blackjackGames = self.bot.database["blackjack games"] + game = blackjackGames.find_one({"_id": channel}) - if user in game["user hands"]: + if game is not None and user in game["user hands"]: + handParams = [game["user hands"][user], handNumber] + hand, handNumber = self._getHandNumber(*handParams) - hand, handNumber = self.getHandNumber(game["user hands"][user],handNumber) - - if hand == None: + if hand is None: sendMessage = "You need to specify which hand" logMessage = "They didn't specify a hand" elif game["round"] <= 0: @@ -338,19 +815,18 @@ class Blackjack(): hand["standing"] = True if handNumber == 2: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".other hand":hand}}) + handPath = f"user hands.{user}.other hand" elif handNumber == 3: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".third hand":hand}}) + handPath = f"user hands.{user}.third hand" elif handNumber == 4: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".fourth hand":hand}}) + handPath = f"user hands.{user}.fourth hand" else: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user:hand}}) + handPath = f"user hands.{user}" - roundDone = self.isRoundDone(self.bot.database["blackjack games"].find_one({"_id":channel})) + gameUpdater = {"$set": {handPath: hand}} + blackjackGames.update_one({"_id": channel}, gameUpdater) + game = blackjackGames.find_one({"_id": channel}) + roundDone = self._isRoundDone(game) sendMessage = f"{ctx.author.display_name} is standing" logMessage = "They succeeded" @@ -364,18 +840,29 @@ class Blackjack(): if roundDone: gameID = game["gameID"] - self.bot.log("Stand calling self.blackjackLoop()", channel) - await self.blackjackLoop(ctx.channel, game["round"]+1, gameID) + self.bot.log("Stand calling self._blackjackLoop()", channel) + await self._blackjackLoop(ctx.channel, game["round"]+1, gameID) - # When players try to split - async def split(self, ctx, handNumber = 0): + async def split(self, ctx: discord_slash.context.SlashContext, + handNumber: int = 0): + """ + Split a hand. + + *Parameters* + ------------ + ctx: discord_slash.context.SlashContext + The context of the command. + handNumber: int = 0 + The number of the hand to split. + """ await self.bot.defer(ctx) channel = str(ctx.channel_id) user = f"#{ctx.author.id}" roundDone = False handNumberError = False - game = self.bot.database["blackjack games"].find_one({"_id":channel}) + blackjackGames = self.bot.database["blackjack games"] + game = blackjackGames.find_one({"_id": channel}) if game["user hands"][user]["split"] == 0: hand = game["user hands"][user] @@ -410,7 +897,7 @@ class Blackjack(): sendMessage = "You can only split 3 times" elif hand["hit"]: logMessage = "They've already hit" - sendMessage = "You've already hit" + sendMessage = "You've already hit or split this hand." elif hand["standing"]: logMessage = "They're already standing" sendMessage = "You're already standing" @@ -418,34 +905,37 @@ class Blackjack(): logMessage = "They tried to split after the first round" sendMessage = "You can only split on the first round" else: - firstCard = self.calcHandValue([hand["hand"][0]]) - secondCard = self.calcHandValue([hand["hand"][1]]) + firstCard = self._calcHandValue([hand["hand"][0]]) + secondCard = self._calcHandValue([hand["hand"][1]]) if firstCard != secondCard: logMessage = "They tried to split two different cards" - sendMessage = "You can only split if your cards have the same value" + sendMessage = self.bot.longStrings["Blackjack different cards"] else: bet = hand["bet"] if self.bot.money.checkBalance(user) < bet: logMessage = "They didn't have enough GwendoBucks" sendMessage = "You don't have enough GwendoBucks" else: - self.bot.money.addMoney(user,-1 * bet) + self.bot.money.addMoney(user, -1 * bet) hand["hit"] = True newHand["hit"] = True newHand = { - "hand":[],"bet":0,"standing":False,"busted":False, - "blackjack":False,"hit":True,"doubled":False} + "hand": [], "bet": 0, "standing": False, + "busted": False, "blackjack": False, "hit": True, + "doubled": False + } newHand["bet"] = hand["bet"] newHand["hand"].append(hand["hand"].pop(1)) - newHand["hand"].append(self.drawCard(channel)) - hand["hand"].append(self.drawCard(channel)) + newHand["hand"].append(self._drawCard(channel)) + hand["hand"].append(self._drawCard(channel)) + hand["hit"] = True - handValue = self.calcHandValue(hand["hand"]) - otherHandValue = self.calcHandValue(newHand["hand"]) + handValue = self._calcHandValue(hand["hand"]) + otherHandValue = self._calcHandValue(newHand["hand"]) if handValue > 21: hand["busted"] = True elif handValue == 21: @@ -457,31 +947,34 @@ class Blackjack(): newHand["blackjack"] = True if handNumber == 2: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".other hand":hand}}) + handPath = f"user hands.{user}.other hand" elif handNumber == 3: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".third hand":hand}}) + handPath = f"user hands.{user}.third hand" else: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user:hand}}) + handPath = f"user hands.{user}" + + gameUpdater = {"$set": {handPath: hand}} + blackjackGames.update_one({"_id": channel}, gameUpdater) if otherHand == 3: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".third hand":newHand}}) + otherHandPath = f"user hands.{user}.third hand" elif otherHand == 4: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".fourth hand":newHand}}) + otherHandPath = f"user hands.{user}.fourth hand" else: - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$set":{"user hands."+user+".other hand":newHand}}) + otherHandPath = f"user hands.{user}.other hand" - self.bot.database["blackjack games"].update_one({"_id":channel}, - {"$inc":{"user hands."+user+".split":1}}) + gameUpdater = {"$set": {otherHandPath: newHand}} + blackjackGames.update_one({"_id": channel}, gameUpdater) - roundDone = self.isRoundDone(self.bot.database["blackjack games"].find_one({"_id":channel})) + splitUpdater = {"$inc": {"user hands."+user+".split": 1}} + blackjackGames.update_one({"_id": channel}, splitUpdater) - sendMessage = f"Splitting {self.bot.databaseFuncs.getName(user)}'s hand into 2. Adding their original bet to the second hand. You can use \"/blackjack hit/stand/double 1\" and \"/blackjack hit/stand/double 2\" to play the different hands." + game = blackjackGames.find_one({"_id": channel}) + roundDone = self._isRoundDone(game) + + sendMessage = self.bot.longStrings["Blackjack split"] + userName = self.bot.databaseFuncs.getName(user) + sendMessage = sendMessage.format(userName) logMessage = "They succeeded" await ctx.send(sendMessage) @@ -489,21 +982,31 @@ class Blackjack(): if roundDone: gameID = game["gameID"] - self.bot.log("Stand calling self.blackjackLoop()", channel) - await self.blackjackLoop(ctx.channel, game["round"]+1, gameID) + self.bot.log("Stand calling self._blackjackLoop()", channel) + await self._blackjackLoop(ctx.channel, game["round"]+1, gameID) - # Player enters the game and draws a hand - async def playerDrawHand(self, ctx, bet : int): + async def enterGame(self, ctx: discord_slash.context.SlashContext, + bet: int): + """ + Enter the blackjack game. + + *Parameters* + ------------ + ctx: discord_slash.context.SlashContext + The context of the command. + bet: int + The bet to enter with. + """ await self.bot.defer(ctx) channel = str(ctx.channel_id) user = f"#{ctx.author.id}" collection = self.bot.database["blackjack games"] - game = collection.find_one({"_id":channel}) + game = collection.find_one({"_id": channel}) userName = self.bot.databaseFuncs.getName(user) self.bot.log(f"{userName} is trying to join the Blackjack game") - if game == None: + if game is None: sendMessage = "There is no game going on in this channel" logMessage = sendMessage elif user in game["user hands"]: @@ -522,78 +1025,97 @@ class Blackjack(): sendMessage = "You don't have enough GwendoBucks" logMessage = "They didn't have enough GwendoBucks" else: - self.bot.money.addMoney(user,-1 * bet) - playerHand = [self.drawCard(channel) for _ in range(2)] + self.bot.money.addMoney(user, -1 * bet) + playerHand = [self._drawCard(channel) for _ in range(2)] - handValue = self.calcHandValue(playerHand) + handValue = self._calcHandValue(playerHand) if handValue == 21: blackjackHand = True else: blackjackHand = False - newHand = {"hand":playerHand, "bet":bet, "standing":False, - "busted":False, "blackjack":blackjackHand, "hit":True, - "doubled":False, "split":0, "other hand":{}, - "third hand":{}, "fourth hand":{}} + newHand = { + "hand": playerHand, "bet": bet, "standing": False, + "busted": False, "blackjack": blackjackHand, + "hit": True, "doubled": False, "split": 0, + "other hand": {}, "third hand": {}, "fourth hand": {} + } - function = {"$set":{f"user hands.{user}":newHand}} - collection.update_one({"_id":channel}, function) + function = {"$set": {f"user hands.{user}": newHand}} + collection.update_one({"_id": channel}, function) enterGameText = "entered the game with a bet of" betText = f"{bet} GwendoBucks" sendMessage = f"{userName} {enterGameText} {betText}" logMessage = sendMessage - self.bot.log(sendMessage) - await ctx.send(logMessage) + self.bot.log(logMessage) + await ctx.send(sendMessage) - # Starts a game of blackjack - async def start(self, ctx): + async def start(self, ctx: discord_slash.context.SlashContext): + """ + Start a blackjack game. + + *Parameters* + ------------ + ctx: discord_slash.context.SlashContext + The context of the command. + """ await self.bot.defer(ctx) channel = str(ctx.channel_id) blackjackMinCards = 50 - blackjackDecks = 4 + self.bot.log("Starting blackjack game") await ctx.send("Starting a new game of blackjack") cardsLeft = 0 - cards = self.bot.database["blackjack cards"].find_one({"_id":channel}) - if cards != None: + cards = self.bot.database["blackjack cards"].find_one({"_id": channel}) + if cards is not None: cardsLeft = len(cards["cards"]) # Shuffles if not enough cards if cardsLeft < blackjackMinCards: - self.blackjackShuffle(blackjackDecks, channel) + self._blackjackShuffle(channel) self.bot.log("Shuffling the blackjack deck...", channel) await ctx.channel.send("Shuffling the deck...") - game = self.bot.database["blackjack games"].find_one({"_id":channel}) + game = self.bot.database["blackjack games"].find_one({"_id": channel}) self.bot.log("Trying to start a blackjack game in "+channel) gameStarted = False - if game == None: + if game is None: - dealerHand = [self.drawCard(channel),self.drawCard(channel)] + dealerHand = [self._drawCard(channel), self._drawCard(channel)] gameID = datetime.datetime.now().strftime('%Y%m%d%H%M%S') - newGame = {"_id":channel,"dealer hand": dealerHand,"dealer busted":False,"dealer blackjack":False,"user hands": {},"all standing":False,"round":0,"gameID":gameID} + newGame = { + "_id": channel, "dealer hand": dealerHand, + "dealer busted": False, "dealer blackjack": False, + "user hands": {}, "all standing": False, "round": 0, + "gameID": gameID + } - if self.calcHandValue(dealerHand) == 21: + if self._calcHandValue(dealerHand) == 21: newGame["dealer blackjack"] = True self.bot.database["blackjack games"].insert_one(newGame) - copyfile("resources/games/blackjackTable.png","resources/games/blackjackTables/blackjackTable"+channel+".png") + tableImagesPath = "resources/games/blackjackTables/" + emptyTableImagePath = f"resources/games/blackjackTable.png" + newTableImagePath = f"{tableImagesPath}blackjackTable{channel}.png" + copyfile(emptyTableImagePath, newTableImagePath) gameStarted = True if gameStarted: - sendMessage = "Blackjack game started. Use \"/blackjack bet [amount]\" to enter the game within the next 30 seconds." + sendMessage = self.bot.longStrings["Blackjack started"] await ctx.channel.send(sendMessage) - filePath = f"resources/games/blackjackTables/blackjackTable{channel}.png" - oldImage = await ctx.channel.send(file = discord.File(filePath)) + tableImagesPath = "resources/games/blackjackTables/" + filePath = f"{tableImagesPath}blackjackTable{channel}.png" + + oldImage = await ctx.channel.send(file=discord.File(filePath)) with open("resources/games/oldImages/blackjack"+channel, "w") as f: f.write(str(oldImage.id)) @@ -602,365 +1124,346 @@ class Blackjack(): gamedone = False - game = self.bot.database["blackjack games"].find_one({"_id":str(channel)}) + blackjackGames = self.bot.database["blackjack games"] + game = blackjackGames.find_one({"_id": channel}) if len(game["user hands"]) == 0: gamedone = True - await ctx.channel.send("No one entered the game. Ending the game.") + sendMessage = "No one entered the game. Ending the game." + await ctx.channel.send(sendMessage) + gameID = game["gameID"] # Loop of game rounds - if gamedone == False: - self.bot.log("start() calling blackjackLoop()", channel) - await self.blackjackLoop(ctx.channel,1,gameID) + if not gamedone: + self.bot.log("start() calling _blackjackLoop()", channel) + await self._blackjackLoop(ctx.channel, 1, gameID) else: - new_message = self.blackjackFinish(channel) + new_message = self._blackjackFinish(channel) await ctx.channel.send(new_message) else: - await ctx.channel.send("There's already a blackjack game going on. Try again in a few minutes.") + sendMessage = self.bot.longStrings["Blackjack going on"] + await ctx.channel.send(sendMessage) self.bot.log("There was already a game going on") - # Ends the game and calculates winnings - def blackjackFinish(self,channel): - finalWinnings = "*Final Winnings:*\n" + async def hilo(self, ctx: discord_slash.context.SlashContext): + """ + Get the hilo of the blackjack game. - game = self.bot.database["blackjack games"].find_one({"_id":channel}) - - dealerValue = self.calcHandValue(game["dealer hand"]) - dealerBlackjack = game["dealer blackjack"] - dealerBusted = game["dealer busted"] - - try: - for user in game["user hands"]: - - winnings, netWinnings, reason = self.calcWinnings(game["user hands"][user],dealerValue,True,dealerBlackjack,dealerBusted) - - if winnings < 0: - if winnings == -1: - finalWinnings += self.bot.databaseFuncs.getName(user)+" lost "+str(-1 * winnings)+" GwendoBuck "+reason+"\n" - else: - finalWinnings += self.bot.databaseFuncs.getName(user)+" lost "+str(-1 * winnings)+" GwendoBucks "+reason+"\n" - else: - if winnings == 1: - finalWinnings += self.bot.databaseFuncs.getName(user)+" won "+str(winnings)+" GwendoBuck "+reason+"\n" - else: - finalWinnings += self.bot.databaseFuncs.getName(user)+" won "+str(winnings)+" GwendoBucks "+reason+"\n" - - self.bot.money.addMoney(user,netWinnings) - - except: - self.bot.log("Error calculating winnings (error code 1311)") - - self.bot.database["blackjack games"].delete_one({"_id":channel}) - - return finalWinnings - - def calcWinnings(self,hand, dealerValue, topLevel, dealerBlackjack, dealerBusted): - self.bot.log("Calculating winnings") - reason = "" - bet = hand["bet"] - winnings = -1 * bet - netWinnings = 0 - handValue = self.calcHandValue(hand["hand"]) - - if hand["blackjack"] and dealerBlackjack == False: - reason += "(blackjack)" - winnings += math.floor(2.5 * bet) - netWinnings += math.floor(2.5 * bet) - elif dealerBlackjack: - reason += "(dealer blackjack)" - elif hand["busted"]: - reason += "(busted)" - else: - if dealerBusted: - reason = "(dealer busted)" - winnings += 2 * bet - netWinnings += 2 * bet - elif handValue > dealerValue: - winnings += 2 * bet - netWinnings += 2 * bet - reason = "(highest value)" - elif handValue == dealerValue: - reason = "(pushed)" - winnings += bet - netWinnings += bet - else: - reason = "(highest value)" - - if topLevel: - if hand["split"] >= 1: - winningsTemp, netWinningsTemp, reasonTemp = self.calcWinnings(hand["other hand"],dealerValue,False,dealerBlackjack,dealerBusted) - winnings += winningsTemp - netWinnings += netWinningsTemp - reason += reasonTemp - if hand["split"] >= 2: - winningsTemp, netWinningsTemp, reasonTemp = self.calcWinnings(hand["third hand"],dealerValue,False,dealerBlackjack,dealerBusted) - winnings += winningsTemp - netWinnings += netWinningsTemp - reason += reasonTemp - if hand["split"] >= 3: - winningsTemp, netWinningsTemp, reasonTemp = self.calcWinnings(hand["fourth hand"],dealerValue,False,dealerBlackjack,dealerBusted) - winnings += winningsTemp - netWinnings += netWinningsTemp - reason += reasonTemp - - return winnings, netWinnings, reason - - def getHandNumber(self, user,handNumber): - try: - hand = None - - if user["split"] == 0: - hand = user - handNumber = 0 - else: - if handNumber != 0: - if handNumber == 1: - hand = user - elif handNumber == 2: - hand = user["other hand"] - elif handNumber == 3: - hand = user["third hand"] - elif handNumber == 4: - hand = user["fourth hand"] - - return hand, handNumber - except: - self.bot.log("Problem with getHandNumber() (error code 1322)") - - def isRoundDone(self,game): - roundDone = True - - for person in game["user hands"].values(): - if person["hit"] == False and person["standing"] == False: - roundDone = False - - if person["split"] > 0: - if person["other hand"]["hit"] == False and person["other hand"]["standing"] == False: - roundDone = False - - if person["split"] > 1: - if person["third hand"]["hit"] == False and person["third hand"]["standing"] == False: - roundDone = False - - if person["split"] > 2: - if person["fourth hand"]["hit"] == False and person["fourth hand"]["standing"] == False: - roundDone = False - - return roundDone - - # Loop of blackjack game rounds - async def blackjackLoop(self,channel,gameRound,gameID): - self.bot.log("Loop "+str(gameRound),str(channel.id)) - - with open("resources/games/oldImages/blackjack"+str(channel.id), "r") as f: - oldImage = await channel.fetch_message(int(f.read())) - - new_message, allStanding, gamedone = self.blackjackContinue(str(channel.id)) - if new_message != "": - self.bot.log(new_message,str(channel.id)) - await channel.send(new_message) - if gamedone == False: - await oldImage.delete() - oldImage = await channel.send(file = discord.File("resources/games/blackjackTables/blackjackTable"+str(channel.id)+".png")) - with open("resources/games/oldImages/blackjack"+str(channel.id), "w") as f: - f.write(str(oldImage.id)) - - try: - if allStanding: - await asyncio.sleep(5) - else: - await asyncio.sleep(120) - except: - self.bot.log("Loop "+str(gameRound)+" interrupted (error code 1321)") - - game = self.bot.database["blackjack games"].find_one({"_id":str(channel.id)}) - - if game != None: - realRound = game["round"] - realGameID = game["gameID"] - - if gameRound == realRound and realGameID == gameID: - if gamedone == False: - self.bot.log("Loop "+str(gameRound)+" calling self.blackjackLoop()",str(channel.id)) - await self.blackjackLoop(channel,gameRound+1,gameID) - else: - try: - new_message = self.blackjackFinish(str(channel.id)) - except: - self.bot.log("Something fucked up (error code 1310)") - await channel.send(new_message) - else: - self.bot.log("Ending loop on round "+str(gameRound),str(channel.id)) - else: - self.bot.log("Ending loop on round "+str(gameRound),str(channel.id)) - - # Returning current hi-lo value - async def hilo(self, ctx): + *Parameters* + ------------ + ctx: discord_slash.context.SlashContext + The context of the command. + """ channel = ctx.channel_id - data = self.bot.database["hilo"].find_one({"_id":str(channel)}) - if data != None: + data = self.bot.database["hilo"].find_one({"_id": str(channel)}) + if data is not None: hilo = str(data["hilo"]) else: hilo = "0" await ctx.send(f"Hi-lo value: {hilo}", hidden=True) - # Shuffles the blackjack deck - async def shuffle(self, ctx): - blackjackDecks = 4 + async def shuffle(self, ctx: discord_slash.context.SlashContext): + """ + Shuffle the cards used for blackjack. + + *Parameters* + ------------ + ctx: discord_slash.context.SlashContext + The context of the command. + """ channel = ctx.channel_id - self.blackjackShuffle(blackjackDecks,str(channel)) - self.bot.log("Shuffling the blackjack deck...",str(channel)) + self._blackjackShuffle(str(channel)) + self.bot.log("Shuffling the blackjack deck...", str(channel)) await ctx.send("Shuffling the deck...") + async def cards(self, ctx: discord_slash.context.SlashContext): + """ + Get how many cards are left for blackjack. - # Tells you the amount of cards left - async def cards(self, ctx): + *Parameters* + ------------ + ctx: discord_slash.context.SlashContext + The context of the command. + """ channel = ctx.channel_id cardsLeft = 0 - cards = self.bot.database["blackjack cards"].find_one({"_id":str(channel)}) - if cards != None: + blackjackGames = self.bot.database["blackjack cards"] + cards = blackjackGames.find_one({"_id": str(channel)}) + if cards is not None: cardsLeft = len(cards["cards"]) - decksLeft = round(cardsLeft/52,1) - await ctx.send(f"Cards left:\n{cardsLeft} cards, {decksLeft} decks", hidden=True) + decksLeft = round(cardsLeft/52, 1) + sendMessage = f"Cards left:\n{cardsLeft} cards, {decksLeft} decks" + await ctx.send(sendMessage, hidden=True) + class DrawBlackjack(): - def __init__(self,bot): + """ + Draws the blackjack image. + + *Methods* + --------- + drawImage(channel: str) + + *Attributes* + ------------ + bot: Gwendolyn + The instance of the bot. + BORDER: int + The size of the border in pixels. + PLACEMENT: list + The order to place the user hands in. + """ + + def __init__(self, bot): + """Initialize the class.""" self.bot = bot self.BORDER = 100 - self.PLACEMENT = [0,0] - self.ROTATION = 0 + self.PLACEMENT = [2, 1, 3, 0, 4] - def drawImage(self,channel): - self.bot.log("Drawing blackjack table",channel) - game = self.bot.database["blackjack games"].find_one({"_id":channel}) + def drawImage(self, channel: str): + """ + Draw the table image. - fnt = ImageFont.truetype('resources/fonts/futura-bold.ttf', 50) - fntSmol = ImageFont.truetype('resources/fonts/futura-bold.ttf', 40) - self.BORDERSmol = int(self.BORDER/3.5) + *Parameters* + ------------ + channel: str + The id of the channel the game is in. + """ + self.bot.log("Drawing blackjack table", channel) + game = self.bot.database["blackjack games"].find_one({"_id": channel}) + + font = ImageFont.truetype('resources/fonts/futura-bold.ttf', 50) + smallFont = ImageFont.truetype('resources/fonts/futura-bold.ttf', 40) + self.SMALLBORDER = int(self.BORDER/3.5) table = Image.open("resources/games/blackjackTable.png") - self.PLACEMENT = [2,1,3,0,4] textImage = ImageDraw.Draw(table) hands = game["user hands"] dealerBusted = game["dealer busted"] dealerBlackjack = game["dealer blackjack"] - try: - if game["all standing"] == False: - dealerHand = self.drawHand(game["dealer hand"],True,False,False) - else: - dealerHand = self.drawHand(game["dealer hand"],False,dealerBusted,dealerBlackjack) - except: - self.bot.log("Error drawing dealer hand (error code 1341a)") + if not game["all standing"]: + handParams = [ + game["dealer hand"], + True, + False, + False + ] + else: + handParams = [ + game["dealer hand"], + False, + dealerBusted, + dealerBlackjack + ] - table.paste(dealerHand,(800-self.BORDERSmol,20-self.BORDERSmol),dealerHand) + dealerHand = self._drawHand(*handParams) + + pastePosition = (800-self.SMALLBORDER, 20-self.SMALLBORDER) + table.paste(dealerHand, pastePosition, dealerHand) for x in range(len(hands)): key, value = list(hands.items())[x] key = self.bot.databaseFuncs.getName(key) - #self.bot.log("Drawing "+key+"'s hand") - userHand = self.drawHand(value["hand"],False,value["busted"],value["blackjack"]) - try: - if value["split"] == 3: - table.paste(userHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),280-self.BORDERSmol),userHand) - userOtherHand = self.drawHand(value["other hand"]["hand"],False,value["other hand"]["busted"],value["other hand"]["blackjack"]) - table.paste(userOtherHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),420-self.BORDERSmol),userOtherHand) - userThirdHand = self.drawHand(value["third hand"]["hand"],False,value["third hand"]["busted"],value["third hand"]["blackjack"]) - table.paste(userThirdHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),560-self.BORDERSmol),userThirdHand) - userFourthHand = self.drawHand(value["fourth hand"]["hand"],False,value["fourth hand"]["busted"],value["fourth hand"]["blackjack"]) - table.paste(userFourthHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),700-self.BORDERSmol),userFourthHand) - elif value["split"] == 2: - table.paste(userHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),420-self.BORDERSmol),userHand) - userOtherHand = self.drawHand(value["other hand"]["hand"],False,value["other hand"]["busted"],value["other hand"]["blackjack"]) - table.paste(userOtherHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),560-self.BORDERSmol),userOtherHand) - userThirdHand = self.drawHand(value["third hand"]["hand"],False,value["third hand"]["busted"],value["third hand"]["blackjack"]) - table.paste(userThirdHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),700-self.BORDERSmol),userThirdHand) - elif value["split"] == 1: - table.paste(userHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),560-self.BORDERSmol),userHand) - userOtherHand = self.drawHand(value["other hand"]["hand"],False,value["other hand"]["busted"],value["other hand"]["blackjack"]) - table.paste(userOtherHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),700-self.BORDERSmol),userOtherHand) - else: - table.paste(userHand,(32-self.BORDERSmol+(384*self.PLACEMENT[x]),680-self.BORDERSmol),userHand) - except: - self.bot.log("Error drawing player hands (error code 1341b)") + handParams = [ + value["hand"], + False, + value["busted"], + value["blackjack"] + ] + userHand = self._drawHand(*handParams) + positionX = 32-self.SMALLBORDER+(384*self.PLACEMENT[x]) - textWidth = fnt.getsize(key)[0] - if textWidth < 360: - textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2)-3,1010-3),key,fill=(0,0,0), font=fnt) - textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2)+3,1010-3),key,fill=(0,0,0), font=fnt) - textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2)-3,1010+3),key,fill=(0,0,0), font=fnt) - textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2)+3,1010+3),key,fill=(0,0,0), font=fnt) - textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2),1005),key,fill=(255,255,255), font=fnt) + if value["split"] >= 1: + handParamsTwo = [ + value["other hand"]["hand"], + False, + value["other hand"]["busted"], + value["other hand"]["blackjack"] + ] + userOtherHand = self._drawHand(*handParamsTwo) + + if value["split"] >= 2: + handParamsThree = [ + value["third hand"]["hand"], + False, + value["third hand"]["busted"], + value["third hand"]["blackjack"] + ] + userThirdHand = self._drawHand(*handParamsThree) + + if value["split"] >= 3: + handParamsFour = [ + value["fourth hand"]["hand"], + False, + value["fourth hand"]["busted"], + value["fourth hand"]["blackjack"] + ] + userFourthHand = self._drawHand(*handParamsFour) + + if value["split"] == 3: + positionOne = (positionX, 280-self.SMALLBORDER) + positionTwo = (positionX, 420-self.SMALLBORDER) + positionThree = (positionX, 560-self.SMALLBORDER) + positionFour = (positionX, 700-self.SMALLBORDER) + + table.paste(userHand, positionOne, userHand) + table.paste(userOtherHand, positionTwo, userOtherHand) + table.paste(userThirdHand, positionThree, userThirdHand) + table.paste(userFourthHand, positionFour, userFourthHand) + elif value["split"] == 2: + positionOne = (positionX, 420-self.SMALLBORDER) + positionTwo = (positionX, 560-self.SMALLBORDER) + positionThree = (positionX, 700-self.SMALLBORDER) + + table.paste(userHand, positionOne, userHand) + table.paste(userOtherHand, positionTwo, userOtherHand) + table.paste(userThirdHand, positionThree, userThirdHand) + elif value["split"] == 1: + positionOne = (positionX, 560-self.SMALLBORDER) + positionTwo = (positionX, 700-self.SMALLBORDER) + + table.paste(userHand, positionOne, userHand) + table.paste(userOtherHand, positionTwo, userOtherHand) else: - textWidth = fntSmol.getsize(key)[0] - textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2)-2,1020-2),key,fill=(0,0,0), font=fntSmol) - textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2)+2,1020-2),key,fill=(0,0,0), font=fntSmol) - textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2)-2,1020+2),key,fill=(0,0,0), font=fntSmol) - textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2)+2,1020+2),key,fill=(0,0,0), font=fntSmol) - textImage.text((32+(384*self.PLACEMENT[x])+117-int(textWidth/2),1015),key,fill=(255,255,255), font=fntSmol) + positionOne = (positionX, 680-self.SMALLBORDER) + table.paste(userHand, positionOne, userHand) + + textWidth = font.getsize(key)[0] + textX = 32+(384*self.PLACEMENT[x])+117-int(textWidth/2) + black = (0, 0, 0) + white = (255, 255, 255) + + if textWidth < 360: + # Black shadow behind and slightly below white text + textImage.text((textX-3, 1010-3), key, fill=black, font=font) + textImage.text((textX+3, 1010-3), key, fill=black, font=font) + textImage.text((textX-3, 1010+3), key, fill=black, font=font) + textImage.text((textX+3, 1010+3), key, fill=black, font=font) + textImage.text((textX, 1005), key, fill=white, font=font) + else: + textWidth = smallFont.getsize(key)[0] + shadows = [ + (textX-2, 1020-2), + (textX+2, 1020-2), + (textX-2, 1020+2), + (textX+2, 1020+2) + ] + textImage.text(shadows[0], key, fill=black, font=smallFont) + textImage.text(shadows[1], key, fill=black, font=smallFont) + textImage.text(shadows[2], key, fill=black, font=smallFont) + textImage.text(shadows[3], key, fill=black, font=smallFont) + textImage.text((textX, 1015), key, fill=white, font=smallFont) self.bot.log("Saving table image") - table.save("resources/games/blackjackTables/blackjackTable"+channel+".png") + tableImagesPath = "resources/games/blackjackTables/" + tableImagePath = f"{tableImagesPath}blackjackTable{channel}.png" + table.save(tableImagePath) return - def drawHand(self, hand, dealer, busted, blackjack): - self.bot.log("Drawing hand "+str(hand)+", "+str(busted)+", "+str(blackjack)) - fnt = ImageFont.truetype('resources/fonts/futura-bold.ttf', 200) - fnt2 = ImageFont.truetype('resources/fonts/futura-bold.ttf', 120) + def _drawHand(self, hand: dict, dealer: bool, busted: bool, + blackjack: bool): + """ + Draw a hand. + + *Parameters* + ------------ + hand: dict + The hand to draw. + dealer: bool + If the hand belongs to the dealer who hasn't shown + their second card. + busted: bool + If the hand is busted. + blackjack: bool + If the hand has a blackjack. + + *Returns* + --------- + background: Image + The image of the hand. + """ + self.bot.log("Drawing hand {hand}, {busted}, {blackjack}") + font = ImageFont.truetype('resources/fonts/futura-bold.ttf', 200) + font2 = ImageFont.truetype('resources/fonts/futura-bold.ttf', 120) length = len(hand) - background = Image.new("RGBA", ((self.BORDER*2)+691+(125*(length-1)),(self.BORDER*2)+1065),(0,0,0,0)) + backgroundWidth = (self.BORDER*2)+691+(125*(length-1)) + backgroundSize = (backgroundWidth, (self.BORDER*2)+1065) + background = Image.new("RGBA", backgroundSize, (0, 0, 0, 0)) textImage = ImageDraw.Draw(background) + cardY = self.BORDER+self.PLACEMENT[1] if dealer: img = Image.open("resources/games/cards/"+hand[0].upper()+".png") - #self.ROTATION = (random.randint(-20,20)/10.0) - img = img.rotate(self.ROTATION,expand = 1) - #self.PLACEMENT = [random.randint(-20,20),random.randint(-20,20)] - background.paste(img,(self.BORDER+self.PLACEMENT[0],self.BORDER+self.PLACEMENT[1]),img) + cardPosition = (self.BORDER+self.PLACEMENT[0], cardY) + background.paste(img, cardPosition, img) img = Image.open("resources/games/cards/red_back.png") - #self.ROTATION = (random.randint(-20,20)/10.0) - img = img.rotate(self.ROTATION,expand = 1) - #self.PLACEMENT = [random.randint(-20,20),random.randint(-20,20)] - background.paste(img,(125+self.BORDER+self.PLACEMENT[0],self.BORDER+self.PLACEMENT[1]),img) + cardPosition = (125+self.BORDER+self.PLACEMENT[0], cardY) + background.paste(img, cardPosition, img) else: for x in range(length): - img = Image.open("resources/games/cards/"+hand[x].upper()+".png") - #self.ROTATION = (random.randint(-20,20)/10.0) - img = img.rotate(self.ROTATION,expand = 1) - #self.PLACEMENT = [random.randint(-20,20),random.randint(-20,20)] - background.paste(img,(self.BORDER+(x*125)+self.PLACEMENT[0],self.BORDER+self.PLACEMENT[1]),img) + cardPath = f"resources/games/cards/{hand[x].upper()}.png" + img = Image.open(cardPath) + cardPosition = (self.BORDER+(x*125)+self.PLACEMENT[0], cardY) + background.paste(img, cardPosition, img) w, h = background.size textHeight = 290+self.BORDER + white = (255, 255, 255) + black = (0, 0, 0) + red = (255, 50, 50) + gold = (155, 123, 0) - #self.bot.log("Drawing busted/blackjack") if busted: - textWidth = fnt.getsize("BUSTED")[0] - textImage.text((int(w/2)-int(textWidth/2)-10,textHeight+20-10),"BUSTED",fill=(0,0,0), font=fnt) - textImage.text((int(w/2)-int(textWidth/2)+10,textHeight+20-10),"BUSTED",fill=(0,0,0), font=fnt) - textImage.text((int(w/2)-int(textWidth/2)-10,textHeight+20+10),"BUSTED",fill=(0,0,0), font=fnt) - textImage.text((int(w/2)-int(textWidth/2)+10,textHeight+20+10),"BUSTED",fill=(0,0,0), font=fnt) - textImage.text((int(w/2)-int(textWidth/2)-5,textHeight-5),"BUSTED",fill=(255,255,255), font=fnt) - textImage.text((int(w/2)-int(textWidth/2)+5,textHeight-5),"BUSTED",fill=(255,255,255), font=fnt) - textImage.text((int(w/2)-int(textWidth/2)-5,textHeight+5),"BUSTED",fill=(255,255,225), font=fnt) - textImage.text((int(w/2)-int(textWidth/2)+5,textHeight+5),"BUSTED",fill=(255,255,255), font=fnt) - textImage.text((int(w/2)-int(textWidth/2),textHeight),"BUSTED",fill=(255,50,50), font=fnt) + textWidth = font.getsize("BUSTED")[0] + textX = int(w/2)-int(textWidth/2) + positions = [ + (textX-10, textHeight+20-10), + (textX+10, textHeight+20-10), + (textX-10, textHeight+20+10), + (textX+10, textHeight+20+10), + (textX-5, textHeight-5), + (textX+5, textHeight-5), + (textX-5, textHeight+5), + (textX+5, textHeight+5), + (textX, textHeight) + ] + textImage.text(positions[0], "BUSTED", fill=black, font=font) + textImage.text(positions[1], "BUSTED", fill=black, font=font) + textImage.text(positions[2], "BUSTED", fill=black, font=font) + textImage.text(positions[3], "BUSTED", fill=black, font=font) + textImage.text(positions[4], "BUSTED", fill=white, font=font) + textImage.text(positions[5], "BUSTED", fill=white, font=font) + textImage.text(positions[6], "BUSTED", fill=white, font=font) + textImage.text(positions[7], "BUSTED", fill=white, font=font) + textImage.text(positions[8], "BUSTED", fill=red, font=font) elif blackjack: - textWidth = fnt2.getsize("BLACKJACK")[0] - textImage.text((int(w/2)-int(textWidth/2)-6,textHeight+20-6),"BLACKJACK",fill=(0,0,0), font=fnt2) - textImage.text((int(w/2)-int(textWidth/2)+6,textHeight+20-6),"BLACKJACK",fill=(0,0,0), font=fnt2) - textImage.text((int(w/2)-int(textWidth/2)-6,textHeight+20+6),"BLACKJACK",fill=(0,0,0), font=fnt2) - textImage.text((int(w/2)-int(textWidth/2)+6,textHeight+20+6),"BLACKJACK",fill=(0,0,0), font=fnt2) - textImage.text((int(w/2)-int(textWidth/2)-3,textHeight-3),"BLACKJACK",fill=(255,255,255), font=fnt2) - textImage.text((int(w/2)-int(textWidth/2)+3,textHeight-3),"BLACKJACK",fill=(255,255,255), font=fnt2) - textImage.text((int(w/2)-int(textWidth/2)-3,textHeight+3),"BLACKJACK",fill=(255,255,255), font=fnt2) - textImage.text((int(w/2)-int(textWidth/2)+3,textHeight+3),"BLACKJACK",fill=(255,255,255), font=fnt2) - textImage.text((int(w/2)-int(textWidth/2),textHeight),"BLACKJACK",fill=(155,123,0), font=fnt2) - - #self.bot.log("Returning resized image") - return background.resize((int(w/3.5),int(h/3.5)),resample=Image.BILINEAR) + textWidth = font2.getsize("BLACKJACK")[0] + textX = int(w/2)-int(textWidth/2) + positions = [ + (textX-6, textHeight+20-6), + (textX+6, textHeight+20-6), + (textX-6, textHeight+20+6), + (textX+6, textHeight+20+6), + (textX-3, textHeight-3), + (textX+3, textHeight-3), + (textX-3, textHeight+3), + (textX+3, textHeight+3), + (textX, textHeight) + ] + textImage.text(positions[0], "BLACKJACK", fill=black, font=font2) + textImage.text(positions[1], "BLACKJACK", fill=black, font=font2) + textImage.text(positions[2], "BLACKJACK", fill=black, font=font2) + textImage.text(positions[3], "BLACKJACK", fill=black, font=font2) + textImage.text(positions[4], "BLACKJACK", fill=white, font=font2) + textImage.text(positions[5], "BLACKJACK", fill=white, font=font2) + textImage.text(positions[6], "BLACKJACK", fill=white, font=font2) + textImage.text(positions[7], "BLACKJACK", fill=white, font=font2) + textImage.text(positions[8], "BLACKJACK", fill=gold, font=font2) + resizedSize = (int(w/3.5), int(h/3.5)) + return background.resize(resizedSize, resample=Image.BILINEAR) diff --git a/requirements.txt b/requirements.txt index e66d93c..ce8b57f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,10 +8,10 @@ certifi==2020.12.5 chardet==4.0.0 colorama==0.4.4 d20==1.1.0 -discord-py-slash-command==1.1.1 +discord-py-slash-command==1.1.2 discord.py==1.7.1 dnspython==2.1.0 -docutils==0.16 +docutils==0.17 fandom-py==0.2.1 finnhub-python==2.4.0 gitdb==4.0.7 @@ -21,7 +21,7 @@ idna==2.10 IMDbPY==2020.9.25 isort==5.8.0 jaraco.context==4.0.0 -lark-parser==0.9.0 +lark-parser==0.11.2 lazy-object-proxy==1.6.0 lxml==4.6.3 mccabe==0.6.1 diff --git a/resources/longStrings.json b/resources/longStrings.json index a7b418f..1458e3d 100644 --- a/resources/longStrings.json +++ b/resources/longStrings.json @@ -1,4 +1,12 @@ { "missing parameters" : "Missing command parameters. Try using `!help [command]` to find out how to use the command.", - "Can't log in": "Could not log in. Remember to write your bot token in the credentials.txt file" + "Can't log in": "Could not log in. Remember to write your bot token in the credentials.txt file", + "Blackjack all players standing": "All players are standing. The dealer now shows his cards and draws.", + "Blackjack first round": ". You can also double down with \"/blackjack double\" or split with \"/blackjack split\"", + "Blackjack commands": "You have 2 minutes to either hit or stand with \"/blackjack hit\" or \"/blackjack stand\"{}. It's assumed you're standing if you don't make a choice.", + "Blackjack double": "Adding another {} GwendoBucks to {}'s bet and drawing another card.", + "Blackjack different cards": "You can only split if your cards have the same value", + "Blackjack split": "Splitting {}'s hand into 2. Adding their original bet to the second hand. You can use \"/blackjack hit/stand/double 1\" and \"/blackjack hit/stand/double 2\" to play the different hands.", + "Blackjack started": "Blackjack game started. Use \"/blackjack bet [amount]\" to enter the game within the next 30 seconds.", + "Blackjack going on": "There's already a blackjack game going on. Try again in a few minutes." } \ No newline at end of file diff --git a/utils/utilFunctions.py b/utils/utilFunctions.py index 6fea627..a472a21 100644 --- a/utils/utilFunctions.py +++ b/utils/utilFunctions.py @@ -13,12 +13,12 @@ Contains utility functions used by parts of the bot. newString: str) -> str emojiToCommand(emoji: str) -> str """ -import json -import logging -import os -import sys -import imdb -from .helperClasses import Options +import json # Used by longString(), getParams() and makeFiles() +import logging # Used for logging +import os # Used by makeFiles() to check if files exist +import sys # Used to specify printing for logging +import imdb # Used to disable logging for the module +from .helperClasses import Options # Used by getParams() # All of this is logging configuration