From 3c18d552c2c9eb15834b33687915a7b16c98883b Mon Sep 17 00:00:00 2001 From: Parker Date: Sat, 25 Jan 2025 22:58:41 -0600 Subject: reupload --- code/cogs/blackjack.py | 257 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 257 insertions(+) create mode 100644 code/cogs/blackjack.py (limited to 'code/cogs/blackjack.py') diff --git a/code/cogs/blackjack.py b/code/cogs/blackjack.py new file mode 100644 index 0000000..3912993 --- /dev/null +++ b/code/cogs/blackjack.py @@ -0,0 +1,257 @@ +import asyncio +import os +import random +from typing import List, Tuple +import discord +from discord.ext import commands +from PIL import Image +from discord import app_commands + +from bot import InsufficientFundsException +from database import Database + + +"""NOTE: This code was found somewhere on GitHub a long time ago. I changed it a bit to work for +discord.py 2.0 and for my needs. If anyone knows who wrote this, please let me know so I can +give them credit.""" + +Entry = Tuple[int, int] + +class Card: + suits = ["clubs", "diamonds", "hearts", "spades"] + def __init__(self, suit: str, value: int, down=False): + self.suit = suit + self.value = value + self.down = down + self.symbol = self.name[0].upper() + + @property + def name(self) -> str: + """The name of the card value.""" + if self.value <= 10: return str(self.value) + else: return { + 11: 'jack', + 12: 'queen', + 13: 'king', + 14: 'ace', + }[self.value] + + @property + def image(self): + return ( + f"{self.symbol if self.name != '10' else '10'}"\ + f"{self.suit[0].upper()}.png" \ + if not self.down else "red_back.png" + ) + + def flip(self): + self.down = not self.down + return self + + def __str__(self) -> str: + return f'{self.name.title()} of {self.suit.title()}' + + def __repr__(self) -> str: + return str(self) + + +class Blackjack(commands.Cog): + def __init__(self, bot: commands.Bot): + self.bot = bot + self.economy = Database(bot) + + async def check_bet( + self, + interaction: discord.Interaction, + bet + ): + bet = int(bet) + if bet <= 0: + raise commands.errors.BadArgument() + current = (await self.economy.get_entry(interaction.user.id))[1] + if bet > current: + raise InsufficientFundsException() + + @staticmethod + def hand_to_images(hand: List[Card]) -> List[Image.Image]: + return ([ + Image.open(f'./code/utils/cards/{card.image}') + for card in hand + ]) + + @staticmethod + def center(*hands: Tuple[Image.Image]) -> Image.Image: + """Creates blackjack table with cards placed""" + bg: Image.Image = Image.open('./code/utils/table.png') + bg_center_x = bg.size[0] // 2 + bg_center_y = bg.size[1] // 2 + + img_w = hands[0][0].size[0] + img_h = hands[0][0].size[1] + + start_y = bg_center_y - (((len(hands)*img_h) + \ + ((len(hands) - 1) * 15)) // 2) + for hand in hands: + start_x = bg_center_x - (((len(hand)*img_w) + \ + ((len(hand) - 1) * 10)) // 2) + for card in hand: + bg.alpha_composite(card, (start_x, start_y)) + start_x += img_w + 10 + start_y += img_h + 15 + return bg + + def output(self, name, *hands: Tuple[List[Card]]) -> None: + self.center(*map(self.hand_to_images, hands)).save(f'./code/players/tables/{name}.png') + + @staticmethod + def calc_hand(hand: List[List[Card]]) -> int: + """Calculates the sum of the card values and accounts for aces""" + non_aces = [c for c in hand if c.symbol != 'A'] + aces = [c for c in hand if c.symbol == 'A'] + sum = 0 + for card in non_aces: + if not card.down: + if card.symbol in 'JQK': sum += 10 + else: sum += card.value + for card in aces: + if not card.down: + if sum <= 10: sum += 11 + else: sum += 1 + return sum + + + @app_commands.command() + @app_commands.describe(bet='Amount of money to bet') + async def blackjack( + self, + interaction: discord.Interaction, + bet: app_commands.Range[int, 1, None] + ): + "Bet your money on a blackjack game vs. the dealer" + + if f"{interaction.user.id}.png" in os.listdir("./code/players/tables"): + await interaction.response.send_message(f"{interaction.user.mention}, It appears you have a game already running in this server or another, please finish that game before starting a new one.", ephemeral=True) + + else: + await self.check_bet(interaction, bet) + deck = [Card(suit, num) for num in range(2,15) for suit in Card.suits] + random.shuffle(deck) # Generate deck and shuffle it + + player_hand: List[Card] = [] + dealer_hand: List[Card] = [] + + player_hand.append(deck.pop()) + dealer_hand.append(deck.pop()) + player_hand.append(deck.pop()) + dealer_hand.append(deck.pop().flip()) + + player_score = self.calc_hand(player_hand) + dealer_score = self.calc_hand(dealer_hand) + + async def out_table(**kwargs) -> discord.Interaction: + self.output(f'{interaction.user.id}', dealer_hand, player_hand) + embed = discord.Embed(**kwargs) + file = discord.File( + f"./code/players/tables/{interaction.user.id}.png", filename=f"{interaction.user.id}.png" + ) + embed.set_image(url=f"attachment://{interaction.user.id}.png") + try: + msg = await interaction.response.send_message(file=file, embed=embed) + except: + msg = await interaction.edit_original_response(attachments=[file], embed=embed) + reac = await interaction.original_response() + await reac.clear_reactions() + return msg + + standing = False + + while True: + player_score = self.calc_hand(player_hand) + dealer_score = self.calc_hand(dealer_hand) + if player_score == 21: # win condition + await self.economy.add_money(interaction.user.id, bet*2) + result = (f"Blackjack! - you win ${bet*2:,}", 'won') + break + elif player_score > 21: # losing condition + await self.economy.add_money(interaction.user.id, bet*-1) + result = (f"Player busts - you lose ${bet:,}", 'lost') + break + msg = await out_table( + title="Your Turn", + description=f"Your hand: {player_score}\n" \ + f"Dealer's hand: {dealer_score}" + ) + + reac = await interaction.original_response() + await reac.add_reaction("🇭") + await reac.add_reaction("🇸") + + buttons = {"🇭", "🇸"} + + try: + reaction, _ = await self.bot.wait_for( + 'reaction_add', check=lambda reaction, user:user == interaction.user and reaction.emoji in buttons, timeout=60 + ) + except asyncio.TimeoutError: + os.remove(f'./code/players/tables/{interaction.user.id}.png') + await interaction.followup.send(f"{interaction.user.mention} your game timed out. No money was lost or gained.") + return + + if str(reaction.emoji) == "🇭": + player_hand.append(deck.pop()) + continue + elif str(reaction.emoji) == "🇸": + standing = True + break + + if standing: + dealer_hand[1].flip() + player_score = self.calc_hand(player_hand) + dealer_score = self.calc_hand(dealer_hand) + + while dealer_score < 17: # dealer draws until 17 or greater + dealer_hand.append(deck.pop()) + dealer_score = self.calc_hand(dealer_hand) + + if dealer_score == 21: # winning/losing conditions + await self.economy.add_money(interaction.user.id, bet*-1) + result = (f"Dealer blackjack - you lose ${bet:,}", 'lost') + elif dealer_score > 21: + await self.economy.add_money(interaction.user.id, bet*2) + result = (f"Dealer busts - you win ${bet*2:,}", 'won') + elif dealer_score == player_score: + result = (f"Tie - you keep your money", 'kept') + elif dealer_score > player_score: + await self.economy.add_money(interaction.user.id, bet*-1) + result = (f"You lose ${bet:,}", 'lost') + elif dealer_score < player_score: + await self.economy.add_money(interaction.user.id, bet*2) + result = (f"You win ${bet*2:,}", 'won') + + color = ( + discord.Color.red() if result[1] == 'lost' + else discord.Color.green() if result[1] == 'won' + else discord.Color.blue() + ) + + if result[1] == 'won': + description=( + f"**You won ${bet*2:,}**\nYour hand: {player_score}\n" + + f"Dealer's hand: {dealer_score}" + ) + + elif result[1] == 'lost': + description=( + f"**You lost ${bet:,}**\nYour hand: {player_score}\n" + + f"Dealer's hand: {dealer_score}" + ) + + msg = await out_table( + title=result[0], + color=color, + ) + os.remove(f'./code/players/tables/{interaction.user.id}.png') + + +async def setup(bot: commands.Bot): + await bot.add_cog(Blackjack(bot)) \ No newline at end of file -- cgit v1.2.3-70-g09d2