diff --git a/bot/blackjack.py b/bot/blackjack.py index a0fab53..cf3b388 100644 --- a/bot/blackjack.py +++ b/bot/blackjack.py @@ -1,7 +1,10 @@ import logging import os import re -from typing import Tuple, Optional, List, Union, Dict +from pprint import pprint +from typing import Tuple, Optional, List, Dict + +import discord from bot import exceptions, constants @@ -10,9 +13,13 @@ logger.setLevel(constants.LOGGING_LEVEL) class Card(object): - suits = {'h': 'Hearts', 's': 'Spades', 'c': 'Clubs', 'd': 'Diamonds'} - symbols = {'10': 'Ten', '9': 'Nine', '8': 'Eight', '7': 'Seven', '6': 'Six', '5': 'Five', '4': 'Four', '3': 'Three', - '2': 'Two', 'k': 'King', 'q': 'Queen', 'j': 'Jack', 'a': 'Ace'} + _suits = {'H': 'Hearts', 'S': 'Spades', 'C': 'Clubs', 'D': 'Diamonds'} + _symbols = {'10': 'Ten', '9': 'Nine', '8': 'Eight', '7': 'Seven', '6': 'Six', '5': 'Five', '4': 'Four', + '3': 'Three', + '2': 'Two', 'k': 'King', 'q': 'Queen', 'j': 'Jack', 'a': 'Ace'} + + EMOTE_REGEX = re.compile(r'<:([A-z0-9]+):\d+>') + VALUE_PATTERN = re.compile(r'Value: (?:Soft )?(\d+)') def __init__(self, card: str) -> None: if 4 <= len(card) <= 1: @@ -22,7 +29,7 @@ class Card(object): self.symbol, self.suit = self.parts() @property - def value(self, safe: bool = True) -> int: + def value(self, safe: bool = True, unsafe_default: int = 0) -> int: """ Attempts to determine the numerical value of the card. @@ -33,25 +40,24 @@ class Card(object): raise exceptions.NoAceValue( 'The Ace has multiple values (1 and 11) in Blackjack. Special handling is required.') return 0 - - if self.isFace(): + elif self.isFace(): return 10 - - numeric_match = re.match(r'^(\d{1,2})', self.raw_card) - if numeric_match is not None: - return int(numeric_match.group(1)) - - if safe: + elif self.isNumerical(): + return int(self.symbol) + elif safe: raise exceptions.IndetermineValue('Could not determine the numeric value of this card.') - return 0 + return unsafe_default def isAce(self) -> bool: + """Returns whether or not the card is a Ace card.""" return self.symbol == 'a' def isNumerical(self) -> bool: + """Returns whether or not the card is numerical (not face or ace).""" return self.symbol.isnumeric() def isFace(self) -> bool: + """Returns whether or not the card is a face card (Queen, King, or Jack)""" return self.symbol in ['q', 'k', 'j'] def parts(self) -> Tuple[str, str]: @@ -62,15 +68,29 @@ class Card(object): match = re.match(r'^(\d{1,2}|[aqkj])([cdhs])$', self.raw_card, flags=re.IGNORECASE) return match.group(1), match.group(2) + @classmethod + def parse_cards(cls, card_str: discord.embeds.EmbedProxy) -> Tuple[int, List['Card']]: + """Given a EmbedProxy relating to a Blackjack Embed, finds a returns a list of Card objects and the value.""" + card_matches = re.finditer(cls.EMOTE_REGEX, card_str.value) + value = re.search(cls.VALUE_PATTERN, card_str.value) + + cards = [] + for card in card_matches: + identifier = card.group(1) + if identifier != 'cardBack': + cards.append(Card(identifier)) + + return int(value.group(1)), cards + def __repr__(self) -> str: - return f'Card({self.symbols[self.symbol]} of {self.suits[self.suit]})' + return f'Card({self._symbols[self.symbol]} of {self._suits[self.suit]})' def generate_table_structure(filename: str, column_keys: List[str], row_keys: List[str]) -> Dict[Tuple[str, str], str]: data = {} logger.debug(f'Generating table structure with {filename}') with open(os.path.join(constants.STATIC_DIR, filename)) as hard_file: - raw_data = [list(line) for line in hard_file.read().split('\n')] + raw_data = [list(line) for line in hard_file.read().split('\n') if len(line) > 0] for x, col_key in enumerate(column_keys): for y, row_key in enumerate(row_keys): data[(col_key, row_key)] = raw_data[y][x] @@ -107,7 +127,7 @@ class Blackjack(object): """Converts the choice to the best possible choice based on the options given by the bot.""" @classmethod - def access(cls, table: Union[HARD, SOFT, PAIR], key: Tuple[str, str]) -> str: + def access(cls, table: int, key: Tuple[str, str]) -> str: """ Access table data given a column and row key. diff --git a/bot/client.py b/bot/client.py index a910eb7..29c3627 100644 --- a/bot/client.py +++ b/bot/client.py @@ -8,7 +8,8 @@ from typing import Optional, Tuple import discord from discord.ext.tasks import loop -from bot import constants, parsers, timings +from bot import constants, parsers, timings, helpers +from bot.blackjack import Card from bot.constants import PlayOptions logger = logging.getLogger(__file__) @@ -20,10 +21,10 @@ class UnbelievaClient(discord.Client): super().__init__(*args, **kwargs) self.last_message = -1 + # References self.bot_id, self.channel_id = bot_id, channel_id - self.channel: Optional[discord.TextChannel] = None - self.bot: Optional[discord.User] = None + self.tasks = { '$work': timings.Cooldown(5 * 60 + 2), '$slut': timings.Cooldown(13 * 60 + 2), @@ -42,7 +43,6 @@ class UnbelievaClient(discord.Client): await self.wait_until_ready() self.channel: discord.TextChannel = self.get_channel(self.channel_id) - self.bot = self.bot_id self.check_task_available.start() ctypes.windll.kernel32.SetConsoleTitleW(f"#{self.channel.name}/{self.channel.guild.name}") logger.info(f'Connected to #{self.channel.name} in {self.channel.guild.name}') @@ -52,7 +52,7 @@ class UnbelievaClient(discord.Client): if message.channel != self.channel or message.author == self.user: return - if message.author.id == self.bot and len(message.embeds) > 0: + if message.author.id == self.bot_id and len(message.embeds) > 0: embed = message.embeds[0] is_self = embed.author.name == f'{self.user.name}#{self.user.discriminator}' @@ -73,8 +73,8 @@ class UnbelievaClient(discord.Client): # Handling for blackjack if embed.description.startswith('Type `hit` to draw another card'): options = self.parse_options(embed.description) - my_cards = self.parse_cards(embed.fields[0]) - dealer_cards = self.parse_cards(embed.fields[1]) + my_cards = Card.parse_cards(embed.fields[0]) + dealer_cards = Card.parse_cards(embed.fields[1]) print(options, my_cards, dealer_cards) def parse_options(self, options_str: str) -> PlayOptions: @@ -86,24 +86,6 @@ class UnbelievaClient(discord.Client): # noinspection PyProtectedMember return PlayOptions._make(options) - def parse_cards(self, card_str: discord.embeds.EmbedProxy) -> Tuple[str, Optional[str], Tuple[int, bool]]: - """ - Parses a Embed Proxy - - :param card_str: - :return: [Card1, Card2?, [Value, isSoft]] - """ - emote_pattern = r'<:([A-z0-9]+):\d+>' - value_pattern = r'Value: (Soft )?(\d+)' - cards = list(re.finditer(emote_pattern, card_str.value)) - value = re.search(value_pattern, card_str.value) - - c2: Optional[str] - c1, c2 = cards[0].group(1), cards[1].group(1) - c2 = c2 if c2 != 'cardBack' else None - - return c1, c2, (int(value.group(2)), value.groups()[1] is None) - def handle_blackjack(self): embed = self.current_blackjack.embeds[0] options = self.parse_options(embed.description) @@ -114,9 +96,7 @@ class UnbelievaClient(discord.Client): @loop(seconds=1) async def check_task_available(self): - """ - Loop to run tasks as soon as they are available. - """ + """Loop to run tasks as soon as they are available.""" await self.wait_until_ready() for task, task_cooldown in self.tasks.items(): @@ -132,13 +112,3 @@ class UnbelievaClient(discord.Client): # Activate the cooldowns task_cooldown.hit() self.command_cooldown.hit() - - async def command_sleep(self): - """Sleep right before sending a command.""" - now = datetime.utcnow().timestamp() - time_between = now - self.last_message - wait_time = 6 - time_between - - if wait_time > 0: - logger.debug(f'Sleeping for {round(wait_time, 2)}s before sending a command...') - await asyncio.sleep(wait_time) diff --git a/bot/static/baseline_soft.dat b/bot/static/baseline_soft.dat index 8134025..692858d 100644 --- a/bot/static/baseline_soft.dat +++ b/bot/static/baseline_soft.dat @@ -3,5 +3,6 @@ SSSSDSSSSS DDDDDSSHHH HDDDDHHHHH HHDDDHHHHH +HHDDDHHHHH HHHDDHHHHH HHHDDHHHHH