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

@ -52,26 +52,6 @@ class BlackjackCog(commands.Cog):
"""Enter the game of blackjack with a bet.""" """Enter the game of blackjack with a bet."""
await self.bot.games.blackjack.enter_game(ctx, bet) await self.bot.games.blackjack.enter_game(ctx, bet)
@cog_ext.cog_subcommand(**params["blackjack_stand"])
async def blackjack_stand(self, ctx, hand=0):
"""Stand on your hand in blackjack."""
await self.bot.games.blackjack.stand(ctx, hand)
@cog_ext.cog_subcommand(**params["blackjack_hit"])
async def blackjack_hit(self, ctx, hand=0):
"""Hit on your hand in blackjack."""
await self.bot.games.blackjack.hit(ctx, hand)
@cog_ext.cog_subcommand(**params["blackjack_double"])
async def blackjack_double(self, ctx, hand=0):
"""Double in blackjack."""
await self.bot.games.blackjack.double(ctx, hand)
@cog_ext.cog_subcommand(**params["blackjack_split"])
async def blackjack_split(self, ctx, hand=0):
"""Split your hand in blackjack."""
await self.bot.games.blackjack.split(ctx, hand)
@cog_ext.cog_subcommand(**params["blackjack_hilo"]) @cog_ext.cog_subcommand(**params["blackjack_hilo"])
async def blackjack_hilo(self, ctx): async def blackjack_hilo(self, ctx):
"""Get the hilo value for the deck in blackjack.""" """Get the hilo value for the deck in blackjack."""

View File

@ -1,6 +1,7 @@
"""Contains the MiscCog, which deals with miscellaneous commands.""" """Contains the MiscCog, which deals with miscellaneous commands."""
from discord.ext import commands # Has the cog class from discord.ext import commands # Has the cog class
from discord_slash import cog_ext # Used for slash commands from discord_slash import cog_ext # Used for slash commands
from discord_slash.context import SlashContext
from gwendolyn.utils import get_params # pylint: disable=import-error from gwendolyn.utils import get_params # pylint: disable=import-error
@ -19,77 +20,77 @@ class MiscCog(commands.Cog):
self.nerd_shit = bot.other.nerd_shit self.nerd_shit = bot.other.nerd_shit
@cog_ext.cog_slash(**params["ping"]) @cog_ext.cog_slash(**params["ping"])
async def ping(self, ctx): async def ping(self, ctx: SlashContext):
"""Send the bot's latency.""" """Send the bot's latency."""
await ctx.send(f"Pong!\nLatency is {round(self.bot.latency * 1000)}ms") await ctx.send(f"Pong!\nLatency is {round(self.bot.latency * 1000)}ms")
@cog_ext.cog_slash(**params["stop"]) @cog_ext.cog_slash(**params["stop"])
async def stop(self, ctx): async def stop(self, ctx: SlashContext):
"""Stop the bot.""" """Stop the bot."""
await self.bot.stop(ctx) await self.bot.stop(ctx)
@cog_ext.cog_slash(**params["help"]) @cog_ext.cog_slash(**params["help"])
async def help_command(self, ctx, command=""): async def help_command(self, ctx: SlashContext, command=""):
"""Get help for commands.""" """Get help for commands."""
await self.bot.other.helpFunc(ctx, command) await self.bot.other.helpFunc(ctx, command)
@cog_ext.cog_slash(**params["thank"]) @cog_ext.cog_slash(**params["thank"])
async def thank(self, ctx): async def thank(self, ctx: SlashContext):
"""Thank the bot.""" """Thank the bot."""
await ctx.send("You're welcome :blush:") await ctx.send("You're welcome :blush:")
@cog_ext.cog_slash(**params["hello"]) @cog_ext.cog_slash(**params["hello"])
async def hello(self, ctx): async def hello(self, ctx: SlashContext):
"""Greet the bot.""" """Greet the bot."""
await self.bot.other.helloFunc(ctx) await self.bot.other.helloFunc(ctx)
@cog_ext.cog_slash(**params["roll"]) @cog_ext.cog_slash(**params["roll"])
async def roll(self, ctx, dice="1d20"): async def roll(self, ctx: SlashContext, dice="1d20"):
"""Roll dice.""" """Roll dice."""
await self.bot.other.rollDice(ctx, dice) await self.bot.other.rollDice(ctx, dice)
@cog_ext.cog_slash(**params["image"]) @cog_ext.cog_slash(**params["image"])
async def image(self, ctx): async def image(self, ctx: SlashContext):
"""Get a random image from Bing.""" """Get a random image from Bing."""
await self.bot.other.imageFunc(ctx) await self.bot.other.imageFunc(ctx)
@cog_ext.cog_slash(**params["movie"]) @cog_ext.cog_slash(**params["movie"])
async def movie(self, ctx): async def movie(self, ctx: SlashContext):
"""Get a random movie from the Plex server.""" """Get a random movie from the Plex server."""
await self.bot.other.movieFunc(ctx) await self.bot.other.movieFunc(ctx)
@cog_ext.cog_slash(**params["name"]) @cog_ext.cog_slash(**params["name"])
async def name(self, ctx): async def name(self, ctx: SlashContext):
"""Generate a random name.""" """Generate a random name."""
await self.generators.nameGen(ctx) await self.generators.nameGen(ctx)
@cog_ext.cog_slash(**params["tavern"]) @cog_ext.cog_slash(**params["tavern"])
async def tavern(self, ctx): async def tavern(self, ctx: SlashContext):
"""Generate a random tavern name.""" """Generate a random tavern name."""
await self.generators.tavernGen(ctx) await self.generators.tavernGen(ctx)
@cog_ext.cog_slash(**params["wiki"]) @cog_ext.cog_slash(**params["wiki"])
async def wiki(self, ctx, wiki_page=""): async def wiki(self, ctx: SlashContext, wiki_page=""):
"""Get a page on a fandom wiki.""" """Get a page on a fandom wiki."""
await self.bot.other.findWikiPage(ctx, wiki_page) await self.bot.other.findWikiPage(ctx, wiki_page)
@cog_ext.cog_slash(**params["add_movie"]) @cog_ext.cog_slash(**params["add_movie"])
async def add_movie(self, ctx, movie): async def add_movie(self, ctx: SlashContext, movie):
"""Search for a movie and add it to the Plex server.""" """Search for a movie and add it to the Plex server."""
await self.plex.request_movie(ctx, movie) await self.plex.request_movie(ctx, movie)
@cog_ext.cog_slash(**params["add_show"]) @cog_ext.cog_slash(**params["add_show"])
async def add_show(self, ctx, show): async def add_show(self, ctx: SlashContext, show):
"""Search for a show and add it to the Plex server.""" """Search for a show and add it to the Plex server."""
await self.plex.request_show(ctx, show) await self.plex.request_show(ctx, show)
@cog_ext.cog_slash(**params["downloading"]) @cog_ext.cog_slash(**params["downloading"])
async def downloading(self, ctx, parameters="-d"): async def downloading(self, ctx: SlashContext, parameters="-d"):
"""Get the current downloading torrents.""" """Get the current downloading torrents."""
await self.plex.downloading(ctx, parameters) await self.plex.downloading(ctx, parameters)
@cog_ext.cog_slash(**params["wolf"]) @cog_ext.cog_slash(**params["wolf"])
async def wolf(self, ctx, query): async def wolf(self, ctx: SlashContext, query):
"""Perform a search on Wolfram Alpha.""" """Perform a search on Wolfram Alpha."""
await self.nerd_shit.wolfSearch(ctx, query) await self.nerd_shit.wolfSearch(ctx, query)

9
gwendolyn/exceptions.py Normal file
View File

@ -0,0 +1,9 @@
class GameNotInDatabase(Exception):
def __init__(self, game: str, channel: str):
self.message = f"There is no {game} game in channel {channel}"
super().__init__(self.message)
class InvalidInteraction(Exception):
def __init__(self, custom_id: str, decoded: str):
self.message = f"{custom_id = }, {decoded = }"
super().__init__(self.message)

View File

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

View File

@ -22,12 +22,46 @@ create_actionrow)
from discord_slash.model import ButtonStyle from discord_slash.model import ButtonStyle
from gwendolyn.utils import encode_id from gwendolyn.utils import encode_id
from .game_base import BoardGame
ROWCOUNT = 6 ROWCOUNT = 6
COLUMNCOUNT = 7 COLUMNCOUNT = 7
def _encode_board_string(board: list):
string = [str(i) for row in board for i in row]
class ConnectFour(): while len(string) > 0 and string[0] == "0":
string = string[1:]
if string == "":
string = "0"
dec = 0
for i, digit in enumerate(string[::-1]):
dec += (3**i)*int(digit)
return str(dec)
def _decode_board_string(board_string: str):
dec = int(board_string)
string = []
while dec:
string.append(str(dec % 3))
dec = dec // 3
while len(string) < ROWCOUNT * COLUMNCOUNT:
string.append("0")
string = string[::-1]
board = [
[int(x) for x in string[i*COLUMNCOUNT:i*COLUMNCOUNT+COLUMNCOUNT]]
for i in range(ROWCOUNT)]
return board
class ConnectFour(BoardGame):
""" """
Deals with connect four commands and logic. Deals with connect four commands and logic.
@ -41,8 +75,7 @@ class ConnectFour():
def __init__(self, bot): def __init__(self, bot):
"""Initialize the class.""" """Initialize the class."""
self.bot = bot super().__init__(bot, "connectfour", DrawConnectFour(bot))
self.draw = DrawConnectFour(bot)
self.get_name = self.bot.database_funcs.get_name self.get_name = self.bot.database_funcs.get_name
# pylint: disable=invalid-name # pylint: disable=invalid-name
self.AISCORES = { self.AISCORES = {
@ -57,39 +90,6 @@ class ConnectFour():
} }
# pylint: enable=invalid-name # pylint: enable=invalid-name
def _encode_board_string(self, board: list):
string = [str(i) for row in board for i in row]
while len(string) > 0 and string[0] == "0":
string = string[1:]
if string == "":
string = "0"
dec = 0
for i, digit in enumerate(string[::-1]):
dec += (3**i)*int(digit)
return str(dec)
def _decode_board_string(self, board_string: str):
dec = int(board_string)
string = []
while dec:
string.append(str(dec % 3))
dec = dec // 3
while len(string) < ROWCOUNT * COLUMNCOUNT:
string.append("0")
string = string[::-1]
board = [
[int(x) for x in string[i*COLUMNCOUNT:i*COLUMNCOUNT+COLUMNCOUNT]]
for i in range(ROWCOUNT)]
return board
async def start(self, ctx: SlashContext, async def start(self, ctx: SlashContext,
opponent: Union[int, discord.User]): opponent: Union[int, discord.User]):
""" """
@ -106,128 +106,90 @@ class ConnectFour():
searches when minimaxing. searches when minimaxing.
""" """
await self.bot.defer(ctx) await self.bot.defer(ctx)
user = ctx.author.id
channel = str(ctx.channel_id) channel = str(ctx.channel_id)
started_game = False opponent_info = self._test_opponent(ctx, opponent)
can_start = True if not opponent_info:
return
if isinstance(opponent, int): difficulty = opponent_info[0]
# Opponent is Gwendolyn difficulty_text = opponent_info[1]
if opponent in range(1, 6):
difficulty = int(opponent)
difficulty_text = f" with difficulty {difficulty}"
opponent = self.bot.user.id
else:
send_message = "Difficulty doesn't exist"
log_message = "They challenged a difficulty that doesn't exist"
can_start = False
elif isinstance(opponent, discord.User):
if opponent.bot:
# User has challenged a bot
if opponent == self.bot.user:
# It was Gwendolyn
difficulty = 3
difficulty_text = f" with difficulty {difficulty}"
opponent = self.bot.user.id
else:
send_message = "You can't challenge a bot!"
log_message = "They tried to challenge a bot"
can_start = False
else:
# Opponent is another player
if ctx.author != opponent:
opponent = opponent.id
difficulty = 5
difficulty_text = ""
else:
send_message = "You can't play against yourself"
log_message = "They tried to play against themself"
can_start = False
if can_start: board = [[0 for _ in range(COLUMNCOUNT)] for _ in range(ROWCOUNT)]
board = [[0 for _ in range(COLUMNCOUNT)] for _ in range(ROWCOUNT)] players = [ctx.author.id, opponent]
players = [user, opponent] random.shuffle(players)
random.shuffle(players)
self.draw.draw_image(channel, board, 0, [0,0], "", players) self.draw.draw_image(channel, board, players, [0, [0,0], ""], players)
gwendolyn_turn = (players[0] == self.bot.user.id) opponent_name = self.get_name(f"#{opponent}")
started_game = True turn_name = self.get_name(f"#{players[0]}")
opponent_name = self.get_name(f"#{opponent}") started_text = "Started game against {}{}.".format(
turn_name = self.get_name(f"#{players[0]}") opponent_name,
difficulty_text
started_text = "Started game against {}{}.".format( )
opponent_name, turn_text = f"It's {turn_name}'s turn"
difficulty_text await ctx.send(f"{started_text} {turn_text}")
) self.bot.log("They started a game")
turn_text = f"It's {turn_name}'s turn"
send_message = f"{started_text} {turn_text}"
log_message = "They started a game"
self.bot.log(log_message)
await ctx.send(send_message)
# Sets the whole game in motion # Sets the whole game in motion
if started_game: boards_path = "gwendolyn/resources/games/connect_four_boards/"
boards_path = "gwendolyn/resources/games/connect_four_boards/" file_path = f"{boards_path}board{ctx.channel_id}.png"
file_path = f"{boards_path}board{ctx.channel_id}.png" image_message = await ctx.send(file=discord.File(file_path))
image_message = await ctx.send(file=discord.File(file_path))
board_string = self._encode_board_string(board)
if gwendolyn_turn:
await self._connect_four_ai(
ctx, board_string, players, difficulty, image_message.id
)
else:
buttons = []
for i in range(7):
custom_id = encode_id(
[
"connectfour",
"place",
str(players[0]),
board_string,
str(i),
str(players[0]),
str(players[1]),
str(difficulty),
str(image_message.id)
]
)
buttons.append(create_button(
style=ButtonStyle.blue,
label=str(i+1),
custom_id=custom_id,
disabled=(board[0][i] != 0)
))
board_string = _encode_board_string(board)
if (players[0] == self.bot.user.id):
await self._connect_four_ai(
ctx, board_string, players, difficulty, image_message.id
)
else:
buttons = []
for i in range(7):
custom_id = encode_id( custom_id = encode_id(
[ [
"connectfour", "connectfour",
"end", "place",
str(players[0]),
board_string,
str(i),
str(players[0]), str(players[0]),
str(players[1]), str(players[1]),
str(difficulty),
str(image_message.id) str(image_message.id)
] ]
) )
buttons.append(create_button( buttons.append(create_button(
style=ButtonStyle.red, style=ButtonStyle.blue,
label="Surrender", label=str(i+1),
custom_id=custom_id custom_id=custom_id,
disabled=(board[0][i] != 0)
)) ))
action_rows = [] custom_id = encode_id(
for x in range(((len(buttons)-1)//4)+1): [
row_buttons = buttons[ "connectfour",
(x*4):(min(len(buttons),x*4+4)) "end",
] str(players[0]),
action_rows.append(create_actionrow(*row_buttons)) str(players[1]),
str(image_message.id)
]
)
buttons.append(create_button(
style=ButtonStyle.red,
label="Surrender",
custom_id=custom_id
))
await image_message.edit( action_rows = []
components=action_rows for x in range(((len(buttons)-1)//4)+1):
) row_buttons = buttons[
(x*4):(min(len(buttons),x*4+4))
]
action_rows.append(create_actionrow(*row_buttons))
await image_message.edit(
components=action_rows
)
async def place_piece(self, ctx: ComponentContext, board_string: str, async def place_piece(self, ctx: ComponentContext, board_string: str,
column: int, players: list[int], difficulty: int, column: int, players: list[int], difficulty: int,
@ -246,7 +208,7 @@ class ConnectFour():
user_name = self.get_name(f"#{placer}") user_name = self.get_name(f"#{placer}")
placed_piece = False placed_piece = False
board = self._decode_board_string(board_string) board = _decode_board_string(board_string)
board = self._place_on_board(board, player_number, column) board = self._place_on_board(board, player_number, column)
if board is None: if board is None:
@ -300,7 +262,7 @@ class ConnectFour():
if game_won: if game_won:
self._end_game(winner, players, difficulty) self._end_game(winner, players, difficulty)
else: else:
board_string = self._encode_board_string(board) board_string = _encode_board_string(board)
if gwendolyn_turn: if gwendolyn_turn:
await self._connect_four_ai( await self._connect_four_ai(
ctx, board_string, players, difficulty, image_message.id ctx, board_string, players, difficulty, image_message.id
@ -513,7 +475,7 @@ class ConnectFour():
self.bot.log("Figuring out best move") self.bot.log("Figuring out best move")
board = self._decode_board_string(board_string) board = _decode_board_string(board_string)
player = players.index(self.bot.user.id)+1 player = players.index(self.bot.user.id)+1
scores = [-math.inf for _ in range(COLUMNCOUNT)] scores = [-math.inf for _ in range(COLUMNCOUNT)]
@ -797,8 +759,8 @@ class DrawConnectFour():
# pylint: enable=invalid-name # pylint: enable=invalid-name
# Draws the whole thing # Draws the whole thing
def draw_image(self, channel: str, board: list, winner: int, def draw_image(self, channel: str, board: list, players: list,
win_coordinates: list, win_direction: str, players: list): win_info: list):
""" """
Draw an image of the connect four board. Draw an image of the connect four board.
@ -815,8 +777,8 @@ class DrawConnectFour():
self._draw_pieces(drawer, board) self._draw_pieces(drawer, board)
if winner != 0: if win_info[0] != 0:
self._draw_win(background, win_coordinates, win_direction) self._draw_win(background, win_info[1], win_info[2])
self._draw_footer(drawer, players) self._draw_footer(drawer, players)

View File

@ -1,28 +1,80 @@
"""Base class for the games.""" """Base class for the games."""
import random import random
from shutil import copyfile from typing import Union
from discord import File from discord import File, User
from discord.abc import Messageable from discord.abc import Messageable
from discord_slash.utils.manage_components import (create_button,
create_actionrow)
from discord_slash.context import InteractionContext as IntCont
from PIL import ImageFont, Image, ImageDraw from PIL import ImageFont, Image, ImageDraw
from gwendolyn.exceptions import GameNotInDatabase
from gwendolyn.utils import encode_id
class GameBase(): class GameBase():
"""The base class for the games.""" """The base class for the games."""
def __init__(self, bot): def __init__(self, bot, game_name: int, drawer):
"""Initialize the class.""" """Initialize the class."""
self.bot = bot self.bot = bot
self.database = self.bot.database
self.long_strings = self.bot.long_strings self.long_strings = self.bot.long_strings
self.resources = "gwendolyn/resources/games/" self.resources = "gwendolyn/resources/games/"
self.game_name = "" self.game_name = game_name
self.old_images_path = f"{self.resources}old_images/{self.game_name}" self.draw = drawer
self.draw = BaseDrawer(bot, self)
def access_document(self, channel: str, collection_name: str="games"): def _get_action_rows(self, buttons: list[tuple[str, list]]):
self.bot.log("Generation action rows")
button_objects = []
for label, data, style in buttons:
custom_id = encode_id([self.game_name] + data)
button_objects.append(create_button(
style=style,
label=label,
custom_id=custom_id
))
action_rows = []
for i in range(((len(button_objects)-1)//5)+1):
action_rows.append(create_actionrow(*button_objects[i*5:i*5+5]))
return action_rows
async def _send_image(self, channel: Messageable,
buttons: list[tuple[str, list]] = None, delete=True):
self.draw.draw(str(channel.id))
file_path = f"{self.resources}images/{self.game_name}{channel.id}.png"
old_image = await channel.send(
file=File(file_path), delete_after=120 if delete else None)
if buttons is not None and len(buttons) < 25:
await old_image.edit(components = self._get_action_rows(buttons))
return old_image
class DatabaseGame(GameBase):
"""The base class for the games."""
def __init__(self, bot, game_name, drawer):
"""Initialize the class."""
super().__init__(bot, game_name, drawer)
self.database = self.bot.database
self.old_images_path = f"{self.resources}old_images/{self.game_name}"
def access_document(self, channel: str, collection_name: str="games",
raise_missing_error: bool=True):
collection = self.bot.database[f"{self.game_name} {collection_name}"] collection = self.bot.database[f"{self.game_name} {collection_name}"]
return collection.find_one({"_id": channel}) game = collection.find_one({"_id": channel})
if game is None and raise_missing_error:
raise GameNotInDatabase(self.game_name, channel)
return game
def _test_document(self, channel: str, collection_name: str="games"):
collection = self.bot.database[f"{self.game_name} {collection_name}"]
game = collection.find_one({"_id": channel})
return game is not None
def _update_document(self, channel: str, updater: dict, def _update_document(self, channel: str, updater: dict,
collection_name: str="games"): collection_name: str="games"):
@ -46,28 +98,29 @@ class GameBase():
await old_image.delete() await old_image.delete()
async def _send_image(self, channel: Messageable): async def _send_image(self, channel: Messageable,
self.draw.draw(str(channel.id)) buttons: list[tuple[str, list]] = None, delete=True):
file_path = f"{self.resources}images/blackjack{channel.id}.png" old_image = super()._send_image(channel, buttons, delete)
old_image = await channel.send(file=File(file_path))
with open(self.old_images_path + str(channel.id), "w") as file_pointer: with open(self.old_images_path + str(channel.id), "w") as file_pointer:
file_pointer.write(str(old_image.id)) file_pointer.write(str(old_image.id))
async def _start_new(self, channel: Messageable, new_game: dict): async def _start_new(self, channel: Messageable, new_game: dict,
buttons: list[tuple[str, list]] = None):
self._insert_document(new_game) self._insert_document(new_game)
default_image = f"{self.resources}default_images/{self.game_name}.png" await self._send_image(channel, buttons)
new_image = f"{self.resources}images/{self.game_name}{channel.id}.png"
copyfile(default_image, new_image)
await self._send_image(channel) async def _end_game(self, channel: Messageable):
await self._delete_old_image(channel)
await self._send_image(channel, delete=False)
self._delete_document(str(channel.id))
class CardGame(GameBase): class CardGame(DatabaseGame):
"""The class for card games.""" """The class for card games."""
def __init__(self, bot): def __init__(self, bot, game_name, drawer, deck_used):
"""Initialize the class.""" """Initialize the class."""
super().__init__(bot) super().__init__(bot, game_name, drawer)
self.decks_used = 1 self.decks_used = deck_used
def _shuffle_cards(self, channel: str): def _shuffle_cards(self, channel: str):
self.bot.log(f"Shuffling cards for {self.game_name}") self.bot.log(f"Shuffling cards for {self.game_name}")
@ -99,6 +152,43 @@ class CardGame(GameBase):
return drawn_card return drawn_card
class BoardGame(GameBase):
def _test_opponent(self, ctx: IntCont, opponent: Union[int, User]):
if isinstance(opponent, int):
# Opponent is Gwendolyn
if opponent in range(1, 6):
difficulty = int(opponent)
difficulty_text = f" with difficulty {difficulty}"
opponent = self.bot.user.id
else:
await ctx.send("Difficulty doesn't exist")
self.bot.log("They challenged a difficulty that doesn't exist")
return False
elif isinstance(opponent, User):
if opponent.bot:
# User has challenged a bot
if opponent == self.bot.user:
# It was Gwendolyn
difficulty = 3
difficulty_text = f" with difficulty {difficulty}"
opponent = self.bot.user.id
else:
await ctx.send("You can't challenge a bot!")
self.bot.log("They tried to challenge a bot")
return False
else:
# Opponent is another player
if ctx.author != opponent:
opponent = opponent.id
difficulty = 5
difficulty_text = ""
else:
await ctx.send("You can't play against yourself")
self.bot.log("They tried to play against themself")
return False
return difficulty, difficulty_text
class BaseDrawer(): class BaseDrawer():
"""Class for drawing games.""" """Class for drawing games."""
def __init__(self, bot, game: GameBase): def __init__(self, bot, game: GameBase):

View File

@ -8,13 +8,13 @@ Deals with commands and logic for hangman games.
DrawHangman() DrawHangman()
Draws the image shown to the player. Draws the image shown to the player.
""" """
import os
import datetime # Used for generating the game id import datetime # Used for generating the game id
import string # string.ascii_uppercase used import string # string.ascii_uppercase used
import math # Used by DrawHangman(), mainly for drawing circles import math # Used by DrawHangman(), mainly for drawing circles
import random # Used to draw poorly import random # Used to draw poorly
import requests # Used for getting the word in Hangman.start() import requests # Used for getting the word in Hangman.start()
import discord # Used for discord.file and type hints import discord # Used for discord.file and type hints
import os
from discord_slash.utils.manage_components import (create_button, from discord_slash.utils.manage_components import (create_button,
create_actionrow) create_actionrow)
@ -50,10 +50,10 @@ class Hangman():
The parameters to pass to every api call. The parameters to pass to every api call.
""" """
self.bot = bot self.bot = bot
self.__draw = DrawHangman(bot) self._draw = DrawHangman(bot)
self.__API_url = "https://api.wordnik.com/v4/words.json/randomWords?" # pylint: disable=invalid-name self._API_url = "https://api.wordnik.com/v4/words.json/randomWords?" # pylint: disable=invalid-name
api_key = self.bot.credentials["wordnik_key"] api_key = self.bot.credentials["wordnik_key"]
self.__APIPARAMS = { # pylint: disable=invalid-name self._APIPARAMS = { # pylint: disable=invalid-name
"hasDictionaryDef": True, "hasDictionaryDef": True,
"minCorpusCount": 5000, "minCorpusCount": 5000,
"maxCorpusCount": -1, "maxCorpusCount": -1,
@ -78,7 +78,7 @@ class Hangman():
word = "-" word = "-"
while "-" in word or "." in word: while "-" in word or "." in word:
response = requests.get(self.__API_url, params=self.__APIPARAMS) response = requests.get(self._API_url, params=self._APIPARAMS)
word = list(response.json()[0]["word"].upper()) word = list(response.json()[0]["word"].upper())
self.bot.log("Found the word \""+"".join(word)+"\"") self.bot.log("Found the word \""+"".join(word)+"\"")
@ -87,7 +87,7 @@ class Hangman():
remaining_letters = list(string.ascii_uppercase) remaining_letters = list(string.ascii_uppercase)
self.__draw.draw_image(game_id, 0, word, guessed, []) self._draw.draw_image(game_id, 0, word, guessed, [])
send_message = f"{ctx.author.display_name} started a game of hangman." send_message = f"{ctx.author.display_name} started a game of hangman."
@ -219,7 +219,7 @@ class Hangman():
send_message = "Guessed {}. There were {} {}s in the word." send_message = "Guessed {}. There were {} {}s in the word."
send_message = send_message.format(guess, correct_guess, guess) send_message = send_message.format(guess, correct_guess, guess)
self.__draw.draw_image(game_id, misses, word, guessed, guessed_letters) self._draw.draw_image(game_id, misses, word, guessed, guessed_letters)
if misses == 6: if misses == 6:
send_message += self.bot.long_strings["Hangman lost game"] send_message += self.bot.long_strings["Hangman lost game"]
@ -325,36 +325,36 @@ class DrawHangman():
FONT FONT
SMALLFONT SMALLFONT
""" """
self.__bot = bot self._bot = bot
# pylint: disable=invalid-name # pylint: disable=invalid-name
self.__CIRCLESIZE = 120 self._CIRCLESIZE = 120
self.__LINEWIDTH = 12 self._LINEWIDTH = 12
self.__BODYSIZE = 210 self._BODYSIZE = 210
self.__LIMBSIZE = 60 self._LIMBSIZE = 60
self.__ARMPOSITION = 60 self._ARMPOSITION = 60
self.__MANX = (self.__LIMBSIZE*2) self._MANX = (self._LIMBSIZE*2)
self.__MANY = (self.__CIRCLESIZE+self.__BODYSIZE+self.__LIMBSIZE) self._MANY = (self._CIRCLESIZE+self._BODYSIZE+self._LIMBSIZE)
MANPADDING = self.__LINEWIDTH*4 MANPADDING = self._LINEWIDTH*4
self.__MANX += MANPADDING self._MANX += MANPADDING
self.__MANY += MANPADDING self._MANY += MANPADDING
self.__LETTERLINELENGTH = 90 self._LETTERLINELENGTH = 90
self.__LETTERLINEDISTANCE = 30 self._LETTERLINEDISTANCE = 30
self.__GALLOWX, self.__GALLOWY = 360, 600 self._GALLOWX, self._GALLOWY = 360, 600
self.__PHI = 1-(1 / ((1 + 5 ** 0.5) / 2)) self._PHI = 1-(1 / ((1 + 5 ** 0.5) / 2))
LETTERSIZE = 75 # Wrong guesses letter size LETTERSIZE = 75 # Wrong guesses letter size
WORDSIZE = 70 # Correct guesses letter size WORDSIZE = 70 # Correct guesses letter size
FONTPATH = "gwendolyn/resources/fonts/comic-sans-bold.ttf" FONTPATH = "gwendolyn/resources/fonts/comic-sans-bold.ttf"
self.__FONT = ImageFont.truetype(FONTPATH, LETTERSIZE) self._FONT = ImageFont.truetype(FONTPATH, LETTERSIZE)
self.__SMALLFONT = ImageFont.truetype(FONTPATH, WORDSIZE) self._SMALLFONT = ImageFont.truetype(FONTPATH, WORDSIZE)
# pylint: enable=invalid-name # pylint: enable=invalid-name
def __deviate(self, pre_deviance: int, pre_deviance_accuracy: int, def _deviate(self, pre_deviance: int, pre_deviance_accuracy: int,
position_change: float, maxmin: int, position_change: float, maxmin: int,
max_acceleration: float): max_acceleration: float):
random_deviance = random.uniform(-position_change, position_change) random_deviance = random.uniform(-position_change, position_change)
@ -371,14 +371,14 @@ class DrawHangman():
deviance = -maxmin deviance = -maxmin
return deviance, deviance_accuracy return deviance, deviance_accuracy
def __bad_circle(self): def _bad_circle(self):
circle_padding = (self.__LINEWIDTH*3) circle_padding = (self._LINEWIDTH*3)
image_width = self.__CIRCLESIZE+circle_padding image_width = self._CIRCLESIZE+circle_padding
image_size = (image_width, image_width) image_size = (image_width, image_width)
background = Image.new("RGBA", image_size, color=(0, 0, 0, 0)) background = Image.new("RGBA", image_size, color=(0, 0, 0, 0))
drawer = ImageDraw.Draw(background, "RGBA") drawer = ImageDraw.Draw(background, "RGBA")
middle = (self.__CIRCLESIZE+(self.__LINEWIDTH*3))/2 middle = (self._CIRCLESIZE+(self._LINEWIDTH*3))/2
deviance_x = 0 deviance_x = 0
deviance_y = 0 deviance_y = 0
deviance_accuracy_x = 0 deviance_accuracy_x = 0
@ -387,96 +387,96 @@ class DrawHangman():
degrees_amount = 360 + random.randint(-10, 30) degrees_amount = 360 + random.randint(-10, 30)
for degree in range(degrees_amount): for degree in range(degrees_amount):
deviance_x, deviance_accuracy_x = self.__deviate( deviance_x, deviance_accuracy_x = self._deviate(
deviance_x, deviance_x,
deviance_accuracy_x, deviance_accuracy_x,
self.__LINEWIDTH/100, self._LINEWIDTH/100,
self.__LINEWIDTH, self._LINEWIDTH,
0.03 0.03
) )
deviance_y, deviance_accuracy_y = self.__deviate( deviance_y, deviance_accuracy_y = self._deviate(
deviance_y, deviance_y,
deviance_accuracy_y, deviance_accuracy_y,
self.__LINEWIDTH/100, self._LINEWIDTH/100,
self.__LINEWIDTH, self._LINEWIDTH,
0.03 0.03
) )
radians = math.radians(degree+start) radians = math.radians(degree+start)
circle_x = (math.cos(radians) * (self.__CIRCLESIZE/2)) circle_x = (math.cos(radians) * (self._CIRCLESIZE/2))
circle_y = (math.sin(radians) * (self.__CIRCLESIZE/2)) circle_y = (math.sin(radians) * (self._CIRCLESIZE/2))
position_x = middle + circle_x - (self.__LINEWIDTH/2) + deviance_x position_x = middle + circle_x - (self._LINEWIDTH/2) + deviance_x
position_y = middle + circle_y - (self.__LINEWIDTH/2) + deviance_y position_y = middle + circle_y - (self._LINEWIDTH/2) + deviance_y
circle_position = [ circle_position = [
(position_x, position_y), (position_x, position_y),
(position_x+self.__LINEWIDTH, position_y+self.__LINEWIDTH) (position_x+self._LINEWIDTH, position_y+self._LINEWIDTH)
] ]
drawer.ellipse(circle_position, fill=(0, 0, 0, 255)) drawer.ellipse(circle_position, fill=(0, 0, 0, 255))
return background return background
def __bad_line(self, length: int, rotated: bool = False): def _bad_line(self, length: int, rotated: bool = False):
if rotated: if rotated:
width, height = length+self.__LINEWIDTH*3, self.__LINEWIDTH*3 width, height = length+self._LINEWIDTH*3, self._LINEWIDTH*3
else: else:
width, height = self.__LINEWIDTH*3, length+self.__LINEWIDTH*3 width, height = self._LINEWIDTH*3, length+self._LINEWIDTH*3
background = Image.new("RGBA", (width, height), color=(0, 0, 0, 0)) background = Image.new("RGBA", (width, height), color=(0, 0, 0, 0))
drawer = ImageDraw.Draw(background, "RGBA") drawer = ImageDraw.Draw(background, "RGBA")
possible_deviance = int(self.__LINEWIDTH/3) possible_deviance = int(self._LINEWIDTH/3)
deviance_x = random.randint(-possible_deviance, possible_deviance) deviance_x = random.randint(-possible_deviance, possible_deviance)
deviance_y = 0 deviance_y = 0
deviance_accuracy_x = 0 deviance_accuracy_x = 0
deviance_accuracy_y = 0 deviance_accuracy_y = 0
for pixel in range(length): for pixel in range(length):
deviance_x, deviance_accuracy_x = self.__deviate( deviance_x, deviance_accuracy_x = self._deviate(
deviance_x, deviance_x,
deviance_accuracy_x, deviance_accuracy_x,
self.__LINEWIDTH/1000, self._LINEWIDTH/1000,
self.__LINEWIDTH, self._LINEWIDTH,
0.004 0.004
) )
deviance_y, deviance_accuracy_y = self.__deviate( deviance_y, deviance_accuracy_y = self._deviate(
deviance_y, deviance_y,
deviance_accuracy_y, deviance_accuracy_y,
self.__LINEWIDTH/1000, self._LINEWIDTH/1000,
self.__LINEWIDTH, self._LINEWIDTH,
0.004 0.004
) )
if rotated: if rotated:
position_x = self.__LINEWIDTH + pixel + deviance_x position_x = self._LINEWIDTH + pixel + deviance_x
position_y = self.__LINEWIDTH + deviance_y position_y = self._LINEWIDTH + deviance_y
else: else:
position_x = self.__LINEWIDTH + deviance_x position_x = self._LINEWIDTH + deviance_x
position_y = self.__LINEWIDTH + pixel + deviance_y position_y = self._LINEWIDTH + pixel + deviance_y
circle_position = [ circle_position = [
(position_x, position_y), (position_x, position_y),
(position_x+self.__LINEWIDTH, position_y+self.__LINEWIDTH) (position_x+self._LINEWIDTH, position_y+self._LINEWIDTH)
] ]
drawer.ellipse(circle_position, fill=(0, 0, 0, 255)) drawer.ellipse(circle_position, fill=(0, 0, 0, 255))
return background return background
def __draw_man(self, misses: int, seed: str): def _draw_man(self, misses: int, seed: str):
random.seed(seed) random.seed(seed)
man_size = (self.__MANX, self.__MANY) man_size = (self._MANX, self._MANY)
background = Image.new("RGBA", man_size, color=(0, 0, 0, 0)) background = Image.new("RGBA", man_size, color=(0, 0, 0, 0))
if misses >= 1: if misses >= 1:
head = self.__bad_circle() head = self._bad_circle()
paste_x = (self.__MANX-(self.__CIRCLESIZE+(self.__LINEWIDTH*3)))//2 paste_x = (self._MANX-(self._CIRCLESIZE+(self._LINEWIDTH*3)))//2
paste_position = (paste_x, 0) paste_position = (paste_x, 0)
background.paste(head, paste_position, head) background.paste(head, paste_position, head)
if misses >= 2: if misses >= 2:
body = self.__bad_line(self.__BODYSIZE) body = self._bad_line(self._BODYSIZE)
paste_x = (self.__MANX-(self.__LINEWIDTH*3))//2 paste_x = (self._MANX-(self._LINEWIDTH*3))//2
paste_position = (paste_x, self.__CIRCLESIZE) paste_position = (paste_x, self._CIRCLESIZE)
background.paste(body, paste_position, body) background.paste(body, paste_position, body)
if misses >= 3: if misses >= 3:
@ -487,30 +487,30 @@ class DrawHangman():
random.seed(seed) random.seed(seed)
for limb in limbs: for limb in limbs:
limb_drawing = self.__bad_line(self.__LIMBSIZE, True) limb_drawing = self._bad_line(self._LIMBSIZE, True)
x_position = (self.__MANX-(self.__LINEWIDTH*3))//2 x_position = (self._MANX-(self._LINEWIDTH*3))//2
if limb[1] == "a": if limb[1] == "a":
rotation = random.randint(-45, 45) rotation = random.randint(-45, 45)
shift = math.sin(math.radians(rotation)) shift = math.sin(math.radians(rotation))
line_length = self.__LIMBSIZE+(self.__LINEWIDTH*3) line_length = self._LIMBSIZE+(self._LINEWIDTH*3)
compensation = int(shift*line_length) compensation = int(shift*line_length)
limb_drawing = limb_drawing.rotate(rotation, expand=1) limb_drawing = limb_drawing.rotate(rotation, expand=1)
y_position = self.__CIRCLESIZE + self.__ARMPOSITION y_position = self._CIRCLESIZE + self._ARMPOSITION
if limb == "ra": if limb == "ra":
compensation = min(-compensation, 0) compensation = min(-compensation, 0)
else: else:
x_position -= self.__LIMBSIZE x_position -= self._LIMBSIZE
compensation = min(compensation, 0) compensation = min(compensation, 0)
y_position += compensation y_position += compensation
else: else:
rotation = random.randint(-15, 15) rotation = random.randint(-15, 15)
y_position = self.__CIRCLESIZE+self.__BODYSIZE-self.__LINEWIDTH y_position = self._CIRCLESIZE+self._BODYSIZE-self._LINEWIDTH
if limb == "rl": if limb == "rl":
limb_drawing = limb_drawing.rotate(rotation-45, expand=1) limb_drawing = limb_drawing.rotate(rotation-45, expand=1)
else: else:
x_position += -limb_drawing.size[0]+self.__LINEWIDTH*3 x_position += -limb_drawing.size[0]+self._LINEWIDTH*3
limb_drawing = limb_drawing.rotate(rotation+45, expand=1) limb_drawing = limb_drawing.rotate(rotation+45, expand=1)
paste_position = (x_position, y_position) paste_position = (x_position, y_position)
@ -518,11 +518,11 @@ class DrawHangman():
return background return background
def __bad_text(self, text: str, big: bool, color: tuple = (0, 0, 0, 255)): def _bad_text(self, text: str, big: bool, color: tuple = (0, 0, 0, 255)):
if big: if big:
font = self.__FONT font = self._FONT
else: else:
font = self.__SMALLFONT font = self._SMALLFONT
width, height = font.getsize(text) width, height = font.getsize(text)
img = Image.new("RGBA", (width, height), color=(0, 0, 0, 0)) img = Image.new("RGBA", (width, height), color=(0, 0, 0, 0))
drawer = ImageDraw.Draw(img, "RGBA") drawer = ImageDraw.Draw(img, "RGBA")
@ -530,59 +530,59 @@ class DrawHangman():
drawer.text((0, 0), text, font=font, fill=color) drawer.text((0, 0), text, font=font, fill=color)
return img return img
def __draw_gallows(self): def _draw_gallows(self):
gallow_size = (self.__GALLOWX, self.__GALLOWY) gallow_size = (self._GALLOWX, self._GALLOWY)
background = Image.new("RGBA", gallow_size, color=(0, 0, 0, 0)) background = Image.new("RGBA", gallow_size, color=(0, 0, 0, 0))
bottom_line = self.__bad_line(int(self.__GALLOWX * 0.75), True) bottom_line = self._bad_line(int(self._GALLOWX * 0.75), True)
bottom_line_x = int(self.__GALLOWX * 0.125) bottom_line_x = int(self._GALLOWX * 0.125)
bottom_line_y = self.__GALLOWY-(self.__LINEWIDTH*4) bottom_line_y = self._GALLOWY-(self._LINEWIDTH*4)
paste_position = (bottom_line_x, bottom_line_y) paste_position = (bottom_line_x, bottom_line_y)
background.paste(bottom_line, paste_position, bottom_line) background.paste(bottom_line, paste_position, bottom_line)
line_two = self.__bad_line(self.__GALLOWY-self.__LINEWIDTH*6) line_two = self._bad_line(self._GALLOWY-self._LINEWIDTH*6)
line_two_x = int(self.__GALLOWX*(0.75*self.__PHI)) line_two_x = int(self._GALLOWX*(0.75*self._PHI))
line_two_y = self.__LINEWIDTH*2 line_two_y = self._LINEWIDTH*2
paste_position = (line_two_x, line_two_y) paste_position = (line_two_x, line_two_y)
background.paste(line_two, paste_position, line_two) background.paste(line_two, paste_position, line_two)
top_line = self.__bad_line(int(self.__GALLOWY*0.30), True) top_line = self._bad_line(int(self._GALLOWY*0.30), True)
paste_x = int(self.__GALLOWX*(0.75*self.__PHI))-self.__LINEWIDTH paste_x = int(self._GALLOWX*(0.75*self._PHI))-self._LINEWIDTH
paste_position = (paste_x, self.__LINEWIDTH*3) paste_position = (paste_x, self._LINEWIDTH*3)
background.paste(top_line, paste_position, top_line) background.paste(top_line, paste_position, top_line)
last_line = self.__bad_line(int(self.__GALLOWY*0.125)) last_line = self._bad_line(int(self._GALLOWY*0.125))
paste_x += int(self.__GALLOWY*0.30) paste_x += int(self._GALLOWY*0.30)
background.paste(last_line, (paste_x, self.__LINEWIDTH*3), last_line) background.paste(last_line, (paste_x, self._LINEWIDTH*3), last_line)
return background return background
def __draw_letter_lines(self, word: str, guessed: list, misses: int): def _draw_letter_lines(self, word: str, guessed: list, misses: int):
letter_width = self.__LETTERLINELENGTH+self.__LETTERLINEDISTANCE letter_width = self._LETTERLINELENGTH+self._LETTERLINEDISTANCE
image_width = letter_width*len(word) image_width = letter_width*len(word)
image_size = (image_width, self.__LETTERLINELENGTH+self.__LINEWIDTH*3) image_size = (image_width, self._LETTERLINELENGTH+self._LINEWIDTH*3)
letter_lines = Image.new("RGBA", image_size, color=(0, 0, 0, 0)) letter_lines = Image.new("RGBA", image_size, color=(0, 0, 0, 0))
for i, letter in enumerate(word): for i, letter in enumerate(word):
line = self.__bad_line(self.__LETTERLINELENGTH, True) line = self._bad_line(self._LETTERLINELENGTH, True)
paste_x = i*(self.__LETTERLINELENGTH+self.__LETTERLINEDISTANCE) paste_x = i*(self._LETTERLINELENGTH+self._LETTERLINEDISTANCE)
paste_position = (paste_x, self.__LETTERLINELENGTH) paste_position = (paste_x, self._LETTERLINELENGTH)
letter_lines.paste(line, paste_position, line) letter_lines.paste(line, paste_position, line)
if guessed[i]: if guessed[i]:
letter_drawing = self.__bad_text(letter, True) letter_drawing = self._bad_text(letter, True)
letter_width = self.__FONT.getsize(letter)[0] letter_width = self._FONT.getsize(letter)[0]
letter_x = i*(self.__LETTERLINELENGTH+self.__LETTERLINEDISTANCE) letter_x = i*(self._LETTERLINELENGTH+self._LETTERLINEDISTANCE)
letter_x -= (letter_width//2) letter_x -= (letter_width//2)
letter_x += (self.__LETTERLINELENGTH//2)+(self.__LINEWIDTH*2) letter_x += (self._LETTERLINELENGTH//2)+(self._LINEWIDTH*2)
letter_lines.paste( letter_lines.paste(
letter_drawing, letter_drawing,
(letter_x, 0), (letter_x, 0),
letter_drawing letter_drawing
) )
elif misses == 6: elif misses == 6:
letter_drawing = self.__bad_text(letter, True, (242, 66, 54)) letter_drawing = self._bad_text(letter, True, (242, 66, 54))
letter_width = self.__FONT.getsize(letter)[0] letter_width = self._FONT.getsize(letter)[0]
letter_x = i*(self.__LETTERLINELENGTH+self.__LETTERLINEDISTANCE) letter_x = i*(self._LETTERLINELENGTH+self._LETTERLINEDISTANCE)
letter_x -= (letter_width//2) letter_x -= (letter_width//2)
letter_x += (self.__LETTERLINELENGTH//2)+(self.__LINEWIDTH*2) letter_x += (self._LETTERLINELENGTH//2)+(self._LINEWIDTH*2)
letter_lines.paste( letter_lines.paste(
letter_drawing, letter_drawing,
(letter_x, 0), (letter_x, 0),
@ -591,7 +591,7 @@ class DrawHangman():
return letter_lines return letter_lines
def __shortest_dist(self, positions: list, new_position: tuple): def _shortest_dist(self, positions: list, new_position: tuple):
shortest_dist = math.inf shortest_dist = math.inf
for i, j in positions: for i, j in positions:
x_distance = abs(i-new_position[0]) x_distance = abs(i-new_position[0])
@ -601,18 +601,18 @@ class DrawHangman():
shortest_dist = dist shortest_dist = dist
return shortest_dist return shortest_dist
def __draw_misses(self, guesses: list, word: str): def _draw_misses(self, guesses: list, word: str):
background = Image.new("RGBA", (600, 400), color=(0, 0, 0, 0)) background = Image.new("RGBA", (600, 400), color=(0, 0, 0, 0))
pos = [] pos = []
for guess in guesses: for guess in guesses:
if guess not in word: if guess not in word:
placed = False placed = False
while not placed: while not placed:
letter = self.__bad_text(guess, True) letter = self._bad_text(guess, True)
w, h = self.__FONT.getsize(guess) w, h = self._FONT.getsize(guess)
x = random.randint(0, 600-w) x = random.randint(0, 600-w)
y = random.randint(0, 400-h) y = random.randint(0, 400-h)
if self.__shortest_dist(pos, (x, y)) > 70: if self._shortest_dist(pos, (x, y)) > 70:
pos.append((x, y)) pos.append((x, y))
background.paste(letter, (x, y), letter) background.paste(letter, (x, y), letter)
placed = True placed = True
@ -628,27 +628,27 @@ class DrawHangman():
channel: str channel: str
The id of the channel the game is in. The id of the channel the game is in.
""" """
self.__bot.log("Drawing hangman image") self._bot.log("Drawing hangman image")
random.seed(game_id) random.seed(game_id)
background = Image.open("gwendolyn/resources/paper.jpg") background = Image.open("gwendolyn/resources/paper.jpg")
gallow = self.__draw_gallows() gallow = self._draw_gallows()
man = self.__draw_man(misses, game_id) man = self._draw_man(misses, game_id)
random.seed(game_id) random.seed(game_id)
letter_line_parameters = [word, guessed, misses] letter_line_parameters = [word, guessed, misses]
letter_lines = self.__draw_letter_lines(*letter_line_parameters) letter_lines = self._draw_letter_lines(*letter_line_parameters)
random.seed(game_id) random.seed(game_id)
misses = self.__draw_misses(list(guessed_letters), word) misses = self._draw_misses(list(guessed_letters), word)
background.paste(gallow, (100, 100), gallow) background.paste(gallow, (100, 100), gallow)
background.paste(man, (300, 210), man) background.paste(man, (300, 210), man)
background.paste(letter_lines, (120, 840), letter_lines) background.paste(letter_lines, (120, 840), letter_lines)
background.paste(misses, (600, 150), misses) background.paste(misses, (600, 150), misses)
misses_text = self.__bad_text("MISSES", False) misses_text = self._bad_text("MISSES", False)
misses_text_width = misses_text.size[0] misses_text_width = misses_text.size[0]
background.paste(misses_text, (850-misses_text_width//2, 50), misses_text) background.paste(misses_text, (850-misses_text_width//2, 50), misses_text)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 610 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@ -7,7 +7,7 @@
"Blackjack double": "Adding another {} GwendoBucks to {}'s bet and drawing another card.", "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 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 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 started": "Blackjack game started. Use the buttons or \"/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.", "Blackjack going on": "There's already a blackjack game going on. Try again in a few minutes.",
"Stock value": "The current {} stock is valued at **{}** GwendoBucks", "Stock value": "The current {} stock is valued at **{}** GwendoBucks",
"Stock parameters": "You must give both a stock name and an amount of GwendoBucks you wish to spend.", "Stock parameters": "You must give both a stock name and an amount of GwendoBucks you wish to spend.",

View File

@ -45,68 +45,16 @@
"name" : "cards", "name" : "cards",
"description" : "Get a count of the cards used in blackjack games" "description" : "Get a count of the cards used in blackjack games"
}, },
"blackjack_double" : {
"base" : "blackjack",
"name" : "double",
"description" : "Double your bet in blackjack",
"options" : [
{
"name" : "hand",
"description" : "The number of the hand to double your bet on",
"type" : 4,
"required" : "false"
}
]
},
"blackjack_hilo" : { "blackjack_hilo" : {
"base" : "blackjack", "base" : "blackjack",
"name" : "hilo", "name" : "hilo",
"description" : "Get the current hi-lo value for the cards used in blackjack games" "description" : "Get the current hi-lo value for the cards used in blackjack games"
}, },
"blackjack_hit" : {
"base" : "blackjack",
"name" : "hit",
"description" : "Hit on your hand in blackjack",
"options" : [
{
"name" : "hand",
"description" : "The number of the hand to hit if you've split",
"type" : 4,
"required" : "false"
}
]
},
"blackjack_shuffle" : { "blackjack_shuffle" : {
"base" : "blackjack", "base" : "blackjack",
"name" : "shuffle", "name" : "shuffle",
"description" : "Shuffle the cards used in blackjack games" "description" : "Shuffle the cards used in blackjack games"
}, },
"blackjack_split" : {
"base" : "blackjack",
"name" : "split",
"description" : "Split your hand in blackjack",
"options" : [
{
"name" : "hand",
"description" : "The number of the hand to split, in case you've already split once",
"type" : 4,
"required" : "false"
}
]
},
"blackjack_stand" : {
"base" : "blackjack",
"name" : "stand",
"description" : "Stand on your hand in blackjack",
"options" : [
{
"name" : "hand",
"description" : "The number of the hand to stand if you've split",
"type" : 4,
"required" : "false"
}
]
},
"blackjack_start" : { "blackjack_start" : {
"base" : "blackjack", "base" : "blackjack",
"name" : "start", "name" : "start",

View File

@ -15,6 +15,7 @@ from discord.ext import commands # Used to compare errors with command
from discord_slash.context import SlashContext, ComponentContext from discord_slash.context import SlashContext, ComponentContext
from gwendolyn.utils.util_functions import decode_id from gwendolyn.utils.util_functions import decode_id
from gwendolyn.exceptions import InvalidInteraction
class EventHandler(): class EventHandler():
@ -64,7 +65,8 @@ class EventHandler():
async def on_component(self, ctx: ComponentContext): async def on_component(self, ctx: ComponentContext):
info = decode_id(ctx.custom_id) info = decode_id(ctx.custom_id)
self.bot.log(f"Component action with info {info}") self.bot.log(f"Component action with info {info}")
channel = ctx.origin_message.channel channel = ctx.channel
author = str(ctx.author_id)
if info[0].lower() == "plex": if info[0].lower() == "plex":
if info[1].lower() == "movie": if info[1].lower() == "movie":
@ -73,25 +75,29 @@ class EventHandler():
info[2], info[2],
not isinstance(channel, discord.DMChannel) not isinstance(channel, discord.DMChannel)
) )
return
elif info[1].lower() == "show": if info[1].lower() == "show":
await self.bot.other.plex.add_show( await self.bot.other.plex.add_show(
ctx.origin_message, ctx.origin_message,
info[2], info[2],
not isinstance(channel, discord.DMChannel) not isinstance(channel, discord.DMChannel)
) )
return
elif info[0].lower() == "hangman": elif info[0].lower() == "hangman" and author == info[2]:
if str(ctx.author_id) == info[2]: if info[1].lower() == "guess":
if info[1].lower() == "guess": await self.bot.games.hangman.guess(ctx, *info[3:])
await self.bot.games.hangman.guess(ctx, *info[3:]) return
elif info[1].lower() == "end":
await self.bot.games.hangman.stop(ctx, *info[3:]) if info[1].lower() == "end":
await self.bot.games.hangman.stop(ctx, *info[3:])
return
elif info[0].lower() == "connectfour": elif info[0].lower() == "connectfour":
connect_four = self.bot.games.connect_four connect_four = self.bot.games.connect_four
if info[1].lower() == "place" and str(ctx.author_id) == info[2]: if info[1].lower() == "place" and author == info[2]:
params = [ await connect_four.place_piece(
ctx, ctx,
info[3], info[3],
int(info[4]), int(info[4]),
@ -99,14 +105,20 @@ class EventHandler():
int(info[7]), int(info[7]),
ctx.author_id, ctx.author_id,
int(info[8]) int(info[8])
] )
await connect_four.place_piece(*params) return
if info[1].lower() == "end":
if str(ctx.author_id) in [info[2], info[3]]: if info[1].lower() == "end" and author in [info[2], info[3]]:
params = [ await connect_four.surrender(
ctx, [int(info[2]), int(info[3])], info[4], info[5] ctx, [int(info[2]), int(info[3])], info[4], info[5]
] )
await connect_four.surrender(*params) return
elif info[0].lower() == "blackjack":
await self.bot.games.blackjack.decode_interaction(ctx, info[1:])
return
raise InvalidInteraction(ctx.custom_id, info)