from __future__ import annotations # Standard Library import asyncio import random # Casino from .deck import Deck from .engine import game_engine # Red from redbot.core import bank from redbot.core.i18n import Translator from redbot.core.errors import BalanceTooHigh from redbot.core.utils.chat_formatting import box from redbot.core.utils.predicates import MessagePredicate # Discord import discord _ = Translator("Casino", __file__) deck = Deck() # Any game created must return a tuple of 3 arguments. # The outcome (True or False) # The final bet or amount (int) # A msg that is either a string or an embed # If the msg is a string it is added to the description of the final embed. # If the msg is an embed, it's fields are added to the final embed. class Core: """ A simple class to hold the basic original Casino mini games. Games ----------- Allin Bet all your credits. All or nothing gamble. Coin Coin flip game. Pick heads or tails. Cups Three cups are shuffled. Pick the one covering the ball. Dice Roll a pair of die. 2, 7, 11, or 12 wins. Hilo Guess if the dice result will be high, low, or 7. Craps Win with a comeout roll of 7 or 11, lose on 2, 3, or 12. If you roll any other number you must match it on your second roll to win. """ def __init__(self, old_message_cache): self.old_message_cache = old_message_cache @game_engine("Allin") async def play_allin(self, ctx, bet, multiplier): message = await ctx.send( _("You put all your chips into the machine and pull the lever...") ) await asyncio.sleep(3) outcome = random.randint(0, multiplier + 1) if outcome == 0: msg = "▂▃▅▇█▓▒░ [♠] [♥] [♦] [♣] ░▒▓█▇▅▃▂\n" msg += _(" CONGRATULATIONS YOU WON\n") msg += _("░▒▓█▇▅▃▂ ⚅ J A C K P O T ⚅ ▂▃▅▇█▓▒░") msg = box(msg, lang="py") bet *= multiplier else: msg = _("Nothing happens. You stare at the machine contemplating your decision.") return outcome == 0, bet, msg, message @game_engine("Coin", (_("heads"), _("tails"))) async def play_coin(self, ctx, bet, choice): message = await ctx.send(_("The coin flips into the air...")) await asyncio.sleep(2) outcome = random.choice((_("heads"), _("tails"))) msg = _("The coin landed on {}!").format(outcome) return choice.lower() in outcome, bet, msg, message @game_engine("Cups", ("1", "2", "3")) async def play_cups(self, ctx, bet, choice): message = await ctx.send(_("The cups start shuffling along the table...")) await asyncio.sleep(3) outcome = random.randint(1, 3) msg = _("The coin was under cup {}!").format(outcome) return int(choice) == outcome, bet, msg, message @game_engine("Dice") async def play_dice(self, ctx, bet): message = await ctx.send( _("The dice strike the back of the table and begin to tumble into place...") ) await asyncio.sleep(2) die_one, die_two = self.roll_dice() outcome = die_one + die_two msg = _("The dice landed on {} and {} ({}).").format(die_one, die_two, outcome) return outcome in (2, 7, 11, 12), bet, msg, message @game_engine("Hilo", (_("low"), _("lo"), _("high"), _("hi"), _("seven"), _("7"))) async def play_hilo(self, ctx, bet, choice): message = await ctx.send(_("The dice hit the table and slowly fall into place...")) await asyncio.sleep(2) result = sum(self.roll_dice()) if result < 7: outcome = (_("low"), _("lo")) elif result > 7: outcome = (_("high"), _("hi")) else: outcome = (_("seven"), "7") msg = _("The outcome was {} ({[0]})!").format(result, outcome) if result == 7 and outcome[1] == "7": bet *= 5 return choice.lower() in outcome, bet, msg, message @game_engine(name="Craps") async def play_craps(self, ctx, bet): return await self._craps_game(ctx, bet) async def _craps_game(self, ctx, bet, comeout=False, message=None): msg1 = _("The dice strike against the back of the table...") if (not await self.old_message_cache.get_guild(ctx.guild)) and message: await asyncio.sleep(3) await message.edit(content=msg1) else: message = await ctx.send(msg1) await asyncio.sleep(2) d1, d2 = self.roll_dice() result = d1 + d2 msg = _("You rolled a {} and {}.") if comeout: if result == comeout: return True, bet, msg.format(d1, d2), message return False, bet, msg.format(d1, d2), message if result == 7: bet *= 3 return True, bet, msg.format(d1, d2), message elif result == 11: return True, bet, msg.format(d1, d2), message elif result in (2, 3, 12): return False, bet, msg.format(d1, d2), message msg2 = _( "{}\nI'll roll the dice one more time. This time you will need exactly {} to win." ).format(msg.format(d1, d2), d1 + d2) if not await self.old_message_cache.get_guild(ctx.guild): await message.edit(content=msg2) else: await ctx.send(msg2) return await self._craps_game(ctx, bet, comeout=result, message=message) @staticmethod def roll_dice(): return random.randint(1, 6), random.randint(1, 6) class BlackjackView(discord.ui.View): def __init__(self, ctx, include_double: bool): self.ctx = ctx super().__init__() self.result = None if include_double is False: self.double_button.disabled = True @discord.ui.button(label="Hit", emoji="\N{RAISED FIST}") async def hit_button(self, interaction: discord.Interaction, button): self.result = "hit" await interaction.response.defer() self.stop() @discord.ui.button(label="Stay", emoji="\N{RAISED HAND}") async def stay_button(self, interaction: discord.Interaction, button): self.result = "stay" await interaction.response.defer() self.stop() @discord.ui.button(label="Double", emoji="\N{VICTORY HAND}\N{VARIATION SELECTOR-16}") async def double_button(self, interaction: discord.Interaction, button): self.result = "double" await interaction.response.defer() self.stop() async def interaction_check(self, interaction: discord.Interaction): if interaction.user.id != self.ctx.author.id: await interaction.response.send_message( "You are not authorized to interact with this.", ephemeral=True ) return False return True class Blackjack: """A simple class to hold the game logic for Blackjack. Blackjack requires inheritance from data to verify the user can double down. """ def __init__(self, old_message_cache): self.old_message_cache = old_message_cache super().__init__() @game_engine(name="Blackjack") async def play(self, ctx, bet): ph, dh, amt, msg = await self.blackjack_game(ctx, bet) result = await self.blackjack_results(ctx, amt, ph, dh, message=msg) return result @game_engine(name="Blackjack") async def mock(self, ctx, bet, ph, dh): result = await self.blackjack_results(ctx, bet, ph, dh) return result async def blackjack_game(self, ctx, amount): ph = deck.deal(num=2) ph_count = deck.bj_count(ph) dh = deck.deal(num=2) # End game if player has 21 if ph_count == 21: return ph, dh, amount, None embed = self.bj_embed(ctx, ph, dh, ph_count, initial=True) view = BlackjackView(ctx, include_double=True) msg = await ctx.send(ctx.author.mention, embed=embed, view=view) await view.wait() if view.result == "stay": dh = self.dealer(dh) return ph, dh, amount, msg if view.result == "double": return await self.double_down(ctx, ph, dh, amount, message=msg) else: ph, dh, message = await self.bj_loop(ctx, ph, dh, ph_count, message=msg) dh = self.dealer(dh) return ph, dh, amount, msg async def double_down(self, ctx, ph, dh, amount, message): try: await bank.withdraw_credits(ctx.author, amount) except ValueError: await ctx.send( _("{} You can not cover the bet. Please choose hit or stay.").format( ctx.author.mention ) ) view = BlackjackView(ctx, include_double=False) ph_count = deck.bj_count(ph) embed = self.bj_embed(ctx, ph, dh, ph_count, initial=False) if not await self.old_message_cache.get_guild(ctx.guild): await message.edit(content=ctx.author.mention, embed=embed, view=view) else: await ctx.send(content=ctx.author.mention, embed=embed, view=view) await view.wait() if view.result == "stay": dh = self.dealer(dh) return ph, dh, amount, message elif view.result == "hit": ph, dh, message = await self.bj_loop( ctx, ph, dh, deck.bj_count(ph), message=message ) dh = self.dealer(dh) return ph, dh, amount, message else: deck.deal(hand=ph) dh = self.dealer(dh) amount *= 2 return ph, dh, amount, message async def blackjack_results(self, ctx, amount, ph, dh, message=None): dc = deck.bj_count(dh) pc = deck.bj_count(ph) if dc > 21 >= pc or dc < pc <= 21: outcome = _("Winner!") result = True elif pc > 21: outcome = _("BUST!") result = False elif dc == pc <= 21: outcome = _("Pushed") try: await bank.deposit_credits(ctx.author, amount) except BalanceTooHigh as e: await bank.set_balance(ctx.author, e.max_balance) result = False else: outcome = _("House Wins!") result = False embed = self.bj_embed(ctx, ph, dh, pc, outcome=outcome) return result, amount, embed, message async def bj_loop(self, ctx, ph, dh, count, message: discord.Message): while count < 21: ph = deck.deal(hand=ph) count = deck.bj_count(hand=ph) if count >= 21: break embed = self.bj_embed(ctx, ph, dh, count) view = BlackjackView(ctx, include_double=False) if not await self.old_message_cache.get_guild(ctx.guild): await message.edit(content=ctx.author.mention, embed=embed, view=view) else: await ctx.send(content=ctx.author.mention, embed=embed, view=view) await view.wait() if view.result == "stay": break await asyncio.sleep(1) # Return player hand & dealer hand when count >= 21 or the player picks stay. return ph, dh, message @staticmethod def dealer(dh): count = deck.bj_count(dh) # forces hit if ace in first two cards without 21 if deck.hand_check(dh, "Ace") and count != 21: deck.deal(hand=dh) count = deck.bj_count(dh) # defines maximum hit score X while count < 17: deck.deal(hand=dh) count = deck.bj_count(dh) return dh @staticmethod def bj_embed(ctx, ph, dh, count1, initial=False, outcome=None): hand = _("{}\n**Score:** {}") footer = _("Cards in Deck: {}") start = _("**Options:** hit, stay, or double") after = _("**Options:** hit or stay") options = "**Outcome:** " + outcome if outcome else start if initial else after count2 = deck.bj_count(dh, hole=True) if not outcome else deck.bj_count(dh) hole = " ".join(deck.fmt_hand([dh[0]])) dealer_hand = hole if not outcome else ", ".join(deck.fmt_hand(dh)) embed = discord.Embed(colour=0xFF0000) embed.add_field( name=_("{}'s Hand").format(ctx.author.name), value=hand.format(", ".join(deck.fmt_hand(ph)), count1), ) embed.add_field( name=_("{}'s Hand").format(ctx.bot.user.name), value=hand.format(dealer_hand, count2) ) embed.add_field(name="\u200b", value=options, inline=False) embed.set_footer(text=footer.format(len(deck))) return embed class WarView(discord.ui.View): def __init__(self, ctx): self.ctx = ctx super().__init__() self.result = None @discord.ui.button(label=_("War")) async def war_button(self, interaction: discord.Interaction, button): self.result = "war" await interaction.response.defer() self.stop() @discord.ui.button(label=_("Surrender"), emoji="\N{WAVING WHITE FLAG}\N{VARIATION SELECTOR-16}") async def surrender_button(self, interaction: discord.Interaction, button): self.result = "surrender" await interaction.response.defer() self.stop() async def interaction_check(self, interaction: discord.Interaction): if interaction.user.id != self.ctx.author.id: await interaction.response.send_message( "You are not authorized to interact with this.", ephemeral=True ) return False return True class War: """A simple class for the war card game.""" def __init__(self, old_message_cache): self.old_message_cache = old_message_cache @game_engine("War") async def play(self, ctx, bet): outcome, player_card, dealer_card, amount, msg = await self.war_game(ctx, bet) return await self.war_results(outcome, player_card, dealer_card, amount, message=msg) async def war_game(self, ctx, bet): player_card, dealer_card, pc, dc = self.war_draw() message = await ctx.send( _( "The dealer shuffles the deck and deals 2 cards face down. One for the " "player and one for the dealer..." ) ) await asyncio.sleep(2) text = _("**FLIP!**") if not await self.old_message_cache.get_guild(ctx.guild): await message.edit(content=text) else: await ctx.send(text) await asyncio.sleep(1) if pc != dc: if pc >= dc: outcome = "Win" else: outcome = "Loss" return outcome, player_card, dealer_card, bet, message content = _( "The player and dealer are both showing a **{}**!\nTHIS MEANS " "WAR! You may choose to surrender and forfeit half your bet, or " "you can go to war.\nIf you go to war your bet will be doubled, " "but the multiplier is only applied to your original bet, the rest will " "be pushed." ).format(deck.fmt_card(player_card)) view = WarView(ctx) if not await self.old_message_cache.get_guild(ctx.guild): await message.edit(content=content, view=view) else: await ctx.send(content=content, view=view) await view.wait() choice = view.result if choice is None or choice.title() in (_("Surrender"), _("Ffs")): outcome = "Surrender" bet /= 2 return outcome, player_card, dealer_card, bet, message else: player_card, dealer_card, pc, dc = self.burn_and_draw() msg1 = _("The dealer burns three cards and deals two cards face down...") msg2 = _("**FLIP!**") if not await self.old_message_cache.get_guild(ctx.guild): action = message.edit else: action = ctx.send await action(content=msg1) await asyncio.sleep(3) await action(content=msg2) if pc >= dc: outcome = "Win" else: outcome = "Loss" return outcome, player_card, dealer_card, bet, message @staticmethod async def war_results(outcome, player_card, dealer_card, amount, message=None): msg = _("**Player Card:** {}\n**Dealer Card:** {}\n").format( deck.fmt_card(player_card), deck.fmt_card(dealer_card) ) if outcome == "Win": msg += _("**Result**: Winner") return True, amount, msg, message elif outcome == "Loss": msg += _("**Result**: Loser") return False, amount, msg, message else: msg += _("**Result**: Surrendered") return False, amount, msg, message @staticmethod def get_count(pc, dc): return deck.war_count(pc), deck.war_count(dc) def war_draw(self): player_card, dealer_card = deck.deal(num=2) pc, dc = self.get_count(player_card, dealer_card) return player_card, dealer_card, pc, dc def burn_and_draw(self): deck.burn(3) player_card, dealer_card = deck.deal(num=2) pc, dc = self.get_count(player_card, dealer_card) return player_card, dealer_card, pc, dc class DoubleView(discord.ui.View): def __init__(self, ctx): self.ctx = ctx super().__init__() self.result = None @discord.ui.button(label=_("Double")) async def war_button(self, interaction: discord.Interaction, button): self.result = "double" await interaction.response.defer() self.stop() @discord.ui.button(label=_("Cash out"), emoji="\N{BANKNOTE WITH DOLLAR SIGN}") async def surrender_button(self, interaction: discord.Interaction, button): self.result = None # set this to None so we exit even if the user doesn't interact await interaction.response.defer() self.stop() async def interaction_check(self, interaction: discord.Interaction): if interaction.user.id != self.ctx.author.id: await interaction.response.send_message( "You are not authorized to interact with this.", ephemeral=True ) return False return True class Double: """A simple class for the Double Or Nothing game.""" def __init__(self, old_message_cache): self.old_message_cache = old_message_cache @game_engine("Double") async def play(self, ctx, bet): count, amount, message = await self.double_game(ctx, bet) return await self.double_results(ctx, count, amount, message=message) async def double_game(self, ctx, bet): count = 0 message = None while bet > 0: count += 1 flip = random.randint(0, 1) if flip == 0: bet = 0 break else: bet *= 2 view = DoubleView(ctx) embed = self.double_embed(ctx, count, bet) if (not await self.old_message_cache.get_guild(ctx.guild)) and message: await message.edit(content=ctx.author.mention, embed=embed, view=view) else: message = await ctx.send(ctx.author.mention, embed=embed, view=view) await view.wait() if not view.result: break await asyncio.sleep(1) return count, bet, message async def double_results(self, ctx, count, amount, message=None): if amount > 0: outcome = _("Cashed Out!") result = True else: outcome = _("You Lost It All!") result = False embed = self.double_embed(ctx, count, amount, outcome=outcome) return result, amount, embed, message @staticmethod def double_embed(ctx, count, amount, outcome=None): double = _("{}\n**DOUBLE!:** x{}") zero = _("{}\n**NOTHING!**") choice = _("**Options:** double or cash out") options = "**Outcome:** " + outcome if outcome else choice if amount == 0: score = zero.format(amount) else: score = double.format(amount, count) embed = discord.Embed(colour=0xFF0000) embed.add_field(name=_("{}'s Score").format(ctx.author.name), value=score) embed.add_field(name="\u200b", value=options, inline=False) if not outcome: embed.add_field( name="\u200b", value="Remember, you can cash out at anytime.", inline=False ) embed.set_footer(text="Try again and test your luck!") return embed