game stuff

This commit is contained in:
NikolajDanger
2021-09-30 14:05:50 +02:00
parent 5330a51fe0
commit cbf2ca765e
12 changed files with 538 additions and 551 deletions

View File

@ -12,7 +12,10 @@ import math # Used for flooring decimal numbers
import datetime # Used to generate the game id
import asyncio # Used for sleeping
from discord_slash.context import SlashContext # Used for typehints
from discord_slash.context import InteractionContext as IntCont # Used for
# typehints
from discord_slash.context import ComponentContext
from discord.abc import Messageable
from PIL import Image
from gwendolyn.utils import replace_multiple
@ -38,15 +41,10 @@ def _is_round_done(game: dict):
round_done: bool
Whether the round is done.
"""
round_done = True
for user_hands in game["user hands"].values():
for hand in user_hands:
if (not hand["hit"]) and (not hand["standing"]):
round_done = False
break
return round_done
return all(
hand["hit"] or hand["standing"]
for user_hands in game["user hands"].values() for hand in user_hands
)
class Blackjack(CardGame):
"""
@ -54,53 +52,51 @@ class Blackjack(CardGame):
*Methods*
---------
hit(ctx: SlashContext, hand_number: int = 0)
double(ctx: SlashContext, hand_number: int = 0)
stand(ctx: SlashContext, hand_number: int = 0)
split(ctx: SlashContext, hand_number: int = 0)
enter_game(ctx: SlashContext, bet: int)
start(ctx: SlashContext)
hilo(ctx: SlashContext)
shuffle(ctx: SlashContext)
cards(ctx: SlashContext)
hit(ctx: IntCont, hand_number: int = 0)
double(ctx: IntCont, hand_number: int = 0)
stand(ctx: IntCont, hand_number: int = 0)
split(ctx: IntCont, hand_number: int = 0)
enter_game(ctx: IntCont, bet: int)
start(ctx: IntCont)
hilo(ctx: IntCont)
shuffle(ctx: IntCont)
cards(ctx: IntCont)
"""
def __init__(self, bot):
"""Initialize the class."""
super().__init__(bot)
self.decks_used = 4
self.game_name = "blackjack"
self.draw = DrawBlackjack(bot, self)
super().__init__(bot, "blackjack", DrawBlackjack(bot, self), 4)
default_buttons = ["Hit", "Stand", "Double", "Split"]
self.default_buttons = [(i, [i, "0"], 1) for i in default_buttons]
async def _test_valid_command(self, game: dict, user: str,
hand_number: int, ctx: SlashContext):
async def _test_command(self, game: dict, user: str, command: str,
hand_number: int, ctx: IntCont): #pylint:disable=too-many-arguments
valid_command = False
if game is None:
self.bot.log("There's no game going on")
await ctx.send("There's no game going on")
elif user not in game["user hands"]:
self.bot.log("They tried something without being in the game")
await ctx.send("You have to enter the game before you can hit")
elif len(game["user hands"][user]) > 1 and hand_number == 0:
self.bot.log("They didn't specify a hand")
await ctx.send("You need to specify a hand")
if user not in game["user hands"]:
self.bot.log(f"They tried to {command} without being in the game")
send_msg = f"You have to enter the game before you can {command}"
elif len(game["user hands"][user]) < hand_number:
self.bot.log("They tried something with a hand they don't have")
await ctx.send("You don't have that many hands")
self.bot.log(f"They tried to {command} with a hand they don't have")
send_msg = "You don't have that many hands"
elif game["round"] <= 0:
self.bot.log("They tried to do something on the 0th round")
await ctx.send("You haven't seen your cards yet!")
self.bot.log(f"They tried to {command} on the 0th round")
send_msg = "You haven't seen your cards yet!"
elif len(game["user hands"][user]) > 1 and hand_number == 0:
self._get_hand_number(ctx, command, game["user hands"][user])
return False
elif game["user hands"][user][max(hand_number-1,0)]["hit"]:
self.bot.log("They've already hit this round")
await ctx.send("You've already hit this round")
send_msg = "You've already hit this round"
elif game["user hands"][user][max(hand_number-1,0)]["standing"]:
self.bot.log("They're already standing")
await ctx.send("You're already standing")
send_msg = "You're already standing"
else:
valid_command = True
if not valid_command:
await ctx.send(send_msg, hidden=True)
return valid_command
def _shuffle_cards(self, channel: str):
@ -158,12 +154,8 @@ class Blackjack(CardGame):
def _draw_card(self, channel: str):
drawn_card = super()._draw_card(channel)
value = self._calc_hand_value([drawn_card])
if value <= 6:
self._update_document(channel, {"$inc": {"hilo": 1}}, "hilo")
elif value >= 10:
self._update_document(channel, {"$inc": {"hilo": -1}}, "hilo")
hilo_value = min(1,-1-((self._calc_hand_value([drawn_card])-10)//3))
self._update_document(channel, {"$inc": {"hilo": hilo_value}}, "hilo")
return drawn_card
@ -182,14 +174,13 @@ class Blackjack(CardGame):
Whether the dealer is done drawing cards.
"""
game = self.access_document(channel)
done = False
dealer_hand = game["dealer hand"]
if self._calc_hand_value(dealer_hand) < 17:
dealer_hand.append(self._draw_card(channel))
dealer_updater = {"$set": {"dealer hand": dealer_hand}}
self._update_document(channel, dealer_updater)
done = False
else:
done = True
@ -219,31 +210,31 @@ class Blackjack(CardGame):
self.bot.log("Continuing blackjack game")
game = self.access_document(channel)
done = False
self._update_document(channel, {"$inc": {"round": 1}})
message = self.long_strings["Blackjack all players standing"]
all_standing = True
pre_all_standing = True
message = self.long_strings["Blackjack all players standing"]
done = False
if game["all standing"]:
self.bot.log("All are standing")
done = self._dealer_draw(channel)
message = "The dealer draws a card."
self.bot.log("Testing if all are standing")
for user, user_hands in game["user hands"].items():
standing_test = (self._test_if_standing(user_hands))
new_hands, all_standing, pre_all_standing = standing_test
hand_updater = {"$set": {"user hands."+user: new_hands}}
hand_updater = {"$set": {"user hands."+user: standing_test[0]}}
self._update_document(channel, hand_updater)
if not standing_test[1]:
all_standing = False
if not standing_test[2]:
pre_all_standing = False
if all_standing:
self._update_document(channel, {"$set": {"all standing": True}})
if all_standing:
if done:
message = "The dealer is done drawing cards"
@ -304,7 +295,7 @@ class Blackjack(CardGame):
return user_hands, all_standing, pre_all_standing
def _blackjack_finish(self, channel: str):
def _blackjack_finish(self, channel: Messageable):
"""
Generate the winnings message after the blackjack game ends.
@ -320,7 +311,7 @@ class Blackjack(CardGame):
"""
final_winnings = "*Final Winnings:*\n"
game = self.access_document(channel)
game = self.access_document(str(channel.id))
for user in game["user hands"]:
winnings, net_winnings, reason = self._calc_winning(
@ -333,7 +324,7 @@ class Blackjack(CardGame):
user_name = self.bot.database_funcs.get_name(user)
if winnings < 0:
final_winnings += "{user_name} lost"
final_winnings += f"{user_name} lost"
else:
final_winnings += f"{user_name} won"
@ -342,11 +333,11 @@ class Blackjack(CardGame):
else:
final_winnings += f" {abs(winnings)} GwendoBucks"
winnings_text += f" {reason}\n"
final_winnings += f" {reason}\n"
self.bot.money.addMoney(user, net_winnings)
self._delete_document(channel)
await self._end_game(channel)
return final_winnings
@ -386,32 +377,27 @@ class Blackjack(CardGame):
net_winnings = 0
for hand in user_hands:
bet = hand["bet"]
winnings += -1 * bet
winnings += -1 * hand["bet"]
hand_value = self._calc_hand_value(hand["hand"])
if hand["blackjack"] and not dealer_blackjack:
reason += "\n(blackjack)"
net_winnings += math.floor(2.5 * bet)
elif dealer_blackjack:
reason += "\n(dealer blackjack)"
elif hand["busted"]:
reason += "\n(busted)"
else:
hand_value = self._calc_hand_value(hand["hand"])
if dealer_busted:
reason += "\n(dealer busted)"
winnings += 2 * bet
elif hand_value > dealer_value:
winnings += 2 * bet
reason += "\n(highest value)"
elif hand_value == dealer_value:
reason += "\n(pushed)"
winnings += bet
else:
reason += "\n(highest value)"
states = [
(dealer_blackjack, "dealer blackjack", 0),
(hand["blackjack"], "blackjack", math.floor(2.5 * hand["bet"])),
(hand["busted"], "busted", 0),
(dealer_busted, dealer_busted, 2*hand["bet"]),
(hand_value == dealer_value, "pushed", hand["bet"]),
(hand_value > dealer_value, "highest value", 2 * hand["bet"]),
(True, "highest value", 0)
]
for state in states:
if state[0]:
reason += f"({state[1]})"
net_winnings += state[2]
break
winnings += net_winnings
return winnings, net_winnings, reason[1:]
return winnings, net_winnings, reason
async def _blackjack_loop(self, channel, game_round: int, game_id: str):
"""
@ -437,22 +423,17 @@ class Blackjack(CardGame):
if not game_done:
await self._delete_old_image(channel)
await self._send_image(channel)
buttons = None if all_standing else self.default_buttons
await self._send_image(channel, buttons)
if all_standing:
await asyncio.sleep(5)
else:
await asyncio.sleep(120)
await asyncio.sleep([120,5][all_standing])
game = self.access_document(str(channel.id))
if game is None:
if not self._test_document(str(channel.id)):
self.bot.log(f"Ending loop on round {game_round}", str(channel.id))
return
real_round = game["round"] or -1
real_id = game["game_id"] or -1
if game_round != real_round or game_id != real_id:
game = self.access_document(str(channel.id))
if game_round != game["round"] or game_id != game["game_id"]:
self.bot.log(f"Ending loop on round {game_round}", str(channel.id))
return
@ -461,17 +442,26 @@ class Blackjack(CardGame):
self.bot.log(log_message, str(channel.id))
await self._blackjack_loop(channel, game_round+1, game_id)
else:
new_message = self._blackjack_finish(str(channel.id))
await channel.send(new_message)
await channel.send(self._blackjack_finish(channel))
async def _get_hand_number(self, ctx: IntCont, command: str,
hands_amount: int):
buttons = [
(str(i+1), [command, "0", str(i+1)], 1) for i in range(hands_amount)
]
await ctx.send(
f"Which hand do you want to {command}?",
hidden=True,
components=self._get_action_rows(buttons)
)
async def hit(self, ctx: SlashContext, hand_number: int = 0):
async def _hit(self, ctx: IntCont, hand_number: int = 0):
"""
Hit on a hand.
*Parameters*
------------
ctx: SlashContext
ctx: IntCont
The context of the command.
hand_number: int = 1
The number of the hand to hit.
@ -481,10 +471,7 @@ class Blackjack(CardGame):
user = f"#{ctx.author.id}"
game = self.access_document(channel)
valid_command = self._test_valid_command(game, user, hand_number, ctx)
if not valid_command:
if not await self._test_command(game, user, "hit", hand_number, ctx):
return
hand = game["user hands"][user][max(hand_number-1,0)]
@ -511,13 +498,13 @@ class Blackjack(CardGame):
ctx.channel, game["round"]+1, game_id
)
async def double(self, ctx: SlashContext, hand_number: int = 0):
async def _double(self, ctx: IntCont, hand_number: int = 0):
"""
Double a hand.
*Parameters*
------------
ctx: SlashContext
ctx: IntCont
The context of the command.
hand_number: int = 0
The number of the hand to double.
@ -530,17 +517,15 @@ class Blackjack(CardGame):
balance = self.bot.money.checkBalance(user)
valid_command = self._test_valid_command(game, user, hand_number, ctx)
if not valid_command:
if not await self._test_command(game, user, "double", hand_number, ctx):
return
if len(game["user hands"][user][max(hand_number-1,0)]["hand"]) != 2:
await ctx.send("They tried to double after round 1")
self.bot.log("You can only double on the first round")
await ctx.send("You can only double on the first round")
self.bot.log("They tried to double after round 1")
elif balance < game["user hands"][user][max(hand_number-1,0)]["bet"]:
await ctx.send("They tried to double without having enough money")
self.bot.log("You can't double when you don't have enough money")
await ctx.send("You can't double when you don't have enough money")
self.bot.log("They tried to double without having enough money")
else:
hand = game["user hands"][user][max(hand_number-1,0)]
self.bot.money.addMoney(user, -1 * hand["bet"])
@ -573,13 +558,13 @@ class Blackjack(CardGame):
)
async def stand(self, ctx: SlashContext, hand_number: int = 0):
async def _stand(self, ctx: IntCont, hand_number: int = 0):
"""
Stand on a hand.
*Parameters*
------------
ctx: SlashContext
ctx: IntCont
The context of the command.
hand_number: int = 0
The number of the hand to stand on.
@ -590,9 +575,7 @@ class Blackjack(CardGame):
game = self.access_document(channel)
valid_command = self._test_valid_command(game, user, hand_number, ctx)
if not valid_command:
if not await self._test_command(game, user, "stand", hand_number, ctx):
return
hand = game["user hands"][user][max(hand_number-1,0)]
@ -613,13 +596,13 @@ class Blackjack(CardGame):
await self._blackjack_loop(ctx.channel, game["round"]+1, game_id)
async def split(self, ctx: SlashContext, hand_number: int = 0):
async def _split(self, ctx: IntCont, hand_number: int = 0):
"""
Split a hand.
*Parameters*
------------
ctx: SlashContext
ctx: IntCont
The context of the command.
hand_number: int = 0
The number of the hand to split.
@ -630,10 +613,7 @@ class Blackjack(CardGame):
game = self.access_document(channel)
valid_command = self._test_valid_command(game, user, hand_number, ctx)
if not valid_command:
if not await self._test_command(game, user, "split", hand_number, ctx):
return
old_hand = game["user hands"][user][max(hand_number-1,0)]
@ -644,7 +624,7 @@ class Blackjack(CardGame):
elif len(old_hand["hand"]) != 2:
await ctx.send("You can only split on the first round")
self.bot.log("They tried to split after the first round")
elif len({self._calc_hand_value([i]) for i in old_hand["hand"]}) == 1:
elif len({self._calc_hand_value([i]) for i in old_hand["hand"]}) != 1:
await ctx.send(self.long_strings["Blackjack different cards"])
self.bot.log("They tried to split two different cards")
elif self.bot.money.checkBalance(user) < old_hand["bet"]:
@ -689,13 +669,13 @@ class Blackjack(CardGame):
ctx.channel, game["round"]+1, game_id
)
async def enter_game(self, ctx: SlashContext, bet: int):
async def enter_game(self, ctx: IntCont, bet: int):
"""
Enter the blackjack game.
*Parameters*
------------
ctx: SlashContext
ctx: IntCont
The context of the command.
bet: int
The bet to enter with.
@ -709,24 +689,21 @@ class Blackjack(CardGame):
self.bot.log(f"{user_name} is trying to join the Blackjack game")
if game is None:
send_message = "There is no game going on in this channel"
log_message = send_message
elif user in game["user hands"]:
send_message = "You're already in the game!"
log_message = "They're already in the game"
if user in game["user hands"]:
await ctx.send("You're already in the game!")
self.bot.log("They're already in the game")
elif len(game["user hands"]) >= 5:
send_message = "There can't be more than 5 players in a game"
log_message = "There were already 5 players in the game"
await ctx.send("There can't be more than 5 players in a game")
self.bot.log("There were already 5 players in the game")
elif game["round"] != 0:
send_message = "The table is no longer taking bets"
log_message = "They tried to join after the game begun"
await ctx.send("The table is no longer taking bets")
self.bot.log("They tried to join after the game begun")
elif bet < 0:
send_message = "You can't bet a negative amount"
log_message = "They tried to bet a negative amount"
await ctx.send("You can't bet a negative amount")
self.bot.log("They tried to bet a negative amount")
elif self.bot.money.checkBalance(user) < bet:
send_message = "You don't have enough GwendoBucks"
log_message = "They didn't have enough GwendoBucks"
await ctx.send("You don't have enough GwendoBucks")
self.bot.log("They didn't have enough GwendoBucks")
else:
self.bot.money.addMoney(user, -1 * bet)
player_hand = [self._draw_card(channel) for _ in range(2)]
@ -742,19 +719,16 @@ class Blackjack(CardGame):
self._update_document(channel, updater)
send_message = "{} entered the game with a bet of {} GwendoBucks"
send_message = send_message.format(user_name, bet)
log_message = send_message
await ctx.send(send_message.format(user_name, bet))
self.bot.log(send_message.format(user_name, bet))
self.bot.log(log_message)
await ctx.send(send_message)
async def start(self, ctx: SlashContext):
async def start(self, ctx: IntCont):
"""
Start a blackjack game.
*Parameters*
------------
ctx: SlashContext
ctx: IntCont
The context of the command.
"""
await self.bot.defer(ctx)
@ -764,21 +738,18 @@ class Blackjack(CardGame):
self.bot.log("Starting blackjack game")
await ctx.send("Starting a new game of blackjack")
cards_left = 0
cards = self.access_document(channel, "cards")
if cards is not None:
if self._test_document(channel, "cards"):
cards = self.access_document(channel, "cards")
cards_left = len(cards["cards"])
# Shuffles if not enough cards
if cards_left < blackjack_min_cards:
self._shuffle_cards(channel)
self.bot.log("Shuffling the blackjack deck...", channel)
await ctx.channel.send("Shuffling the deck...")
game = self.access_document(channel)
self.bot.log(f"Trying to start a blackjack game in {channel}")
if game is not None:
if self._test_document(channel):
await ctx.channel.send(self.long_strings["Blackjack going on"])
self.bot.log("There was already a game going on")
return
@ -794,14 +765,18 @@ class Blackjack(CardGame):
}
await ctx.channel.send(self.long_strings["Blackjack started"])
await self._start_new(ctx.channel, new_game)
labels = [0] + [10**i for i in range(4)]
betting_buttons = [
(f"Bet {i}", ["bet", "0", str(i)], 1) for i in labels
]
await self._start_new(ctx.channel, new_game, betting_buttons)
await asyncio.sleep(30)
game = self.access_document(channel)
if len(game["user hands"]) == 0:
await ctx.channel.send("No one entered the game. Ending the game.")
await ctx.channel.send(self._blackjack_finish(channel))
await ctx.channel.send(self._blackjack_finish(ctx.channel))
return
game_id = game["game_id"]
@ -810,49 +785,60 @@ class Blackjack(CardGame):
self.bot.log("start() calling _blackjack_loop()", channel)
await self._blackjack_loop(ctx.channel, 1, game_id)
async def hilo(self, ctx: SlashContext):
async def hilo(self, ctx: IntCont):
"""
Get the hilo of the blackjack game.
*Parameters*
------------
ctx: SlashContext
ctx: IntCont
The context of the command.
"""
data = self.access_document(str(ctx.channel_id), "hilo")
data = self.access_document(str(ctx.channel_id), "hilo", False)
hilo = data["hilo"] if data else 0
await ctx.send(f"Hi-lo value: {hilo}", hidden=True)
async def shuffle(self, ctx: SlashContext):
async def shuffle(self, ctx: IntCont):
"""
Shuffle the cards used for blackjack.
*Parameters*
------------
ctx: SlashContext
ctx: IntCont
The context of the command.
"""
self._shuffle_cards(str(ctx.channel_id))
self.bot.log("Shuffling the blackjack deck...", str(ctx.channel_id))
await ctx.send("Shuffling the deck...")
async def cards(self, ctx: SlashContext):
async def cards(self, ctx: IntCont):
"""
Get how many cards are left for blackjack.
*Parameters*
------------
ctx: SlashContext
ctx: IntCont
The context of the command.
"""
cards = self.access_document(str(ctx.channel_id), "cards")
cards = self.access_document(str(ctx.channel_id), "cards", False)
cards_left = len(cards["cards"]) if cards else 0
decks_left = round(cards_left/52, 1)
decks_left = round(cards_left/52, 1) if cards else 0
send_message = f"Cards left:\n{cards_left} cards, {decks_left} decks"
await ctx.send(send_message, hidden=True)
async def decode_interaction(self, ctx: ComponentContext, info: list[str]):
if info[0].lower() == "bet":
await self.enter_game(ctx, int(info[2]))
elif info[0].lower() == "hit":
await self._hit(ctx, int(info[2]) if len(info) > 2 else 0)
elif info[0].lower() == "stand":
await self._stand(ctx, int(info[2]) if len(info) > 2 else 0)
elif info[0].lower() == "double":
await self._double(ctx, int(info[2]) if len(info) > 2 else 0)
elif info[0].lower() == "split":
await self._split(ctx, int(info[2]) if len(info) > 2 else 0)
class DrawBlackjack(CardDrawer):
"""
@ -882,7 +868,7 @@ class DrawBlackjack(CardDrawer):
def _draw_dealer_hand(self, game: dict, table: Image.Image):
dealer_hand = self._draw_hand(
game["dealer hand"],
game["all standing"],
not game["all standing"],
game["dealer busted"] if game["all standing"] else False,
game["dealer blackjack"] if game["all standing"] else False
)
@ -972,10 +958,9 @@ class DrawBlackjack(CardDrawer):
colors = [BLACK, WHITE, last_color]
text_image = self._draw_shadow_text(text, colors, font_size)
background.paste(text_image, text_position, text_image)
width = background.size[0]
text_width = self._get_font(font_size).getsize(text)[0]
text_position = (int(width/2)-int(text_width/2), 85)
background.paste(text_image, text_position, text_image)
return background