move client card parsing into Card.parse_cards method, fix access Union typing mistake, fix missing line in baseline_soft.dat, Card class documentation, remove unneeded Client vars/func

This commit is contained in:
Xevion
2021-01-24 18:09:44 -06:00
parent 1a1ae8baf2
commit e0e5636c5c
3 changed files with 46 additions and 55 deletions

View File

@@ -1,7 +1,10 @@
import logging import logging
import os import os
import re 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 from bot import exceptions, constants
@@ -10,9 +13,13 @@ logger.setLevel(constants.LOGGING_LEVEL)
class Card(object): class Card(object):
suits = {'h': 'Hearts', 's': 'Spades', 'c': 'Clubs', 'd': 'Diamonds'} _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', _symbols = {'10': 'Ten', '9': 'Nine', '8': 'Eight', '7': 'Seven', '6': 'Six', '5': 'Five', '4': 'Four',
'2': 'Two', 'k': 'King', 'q': 'Queen', 'j': 'Jack', 'a': 'Ace'} '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: def __init__(self, card: str) -> None:
if 4 <= len(card) <= 1: if 4 <= len(card) <= 1:
@@ -22,7 +29,7 @@ class Card(object):
self.symbol, self.suit = self.parts() self.symbol, self.suit = self.parts()
@property @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. Attempts to determine the numerical value of the card.
@@ -33,25 +40,24 @@ class Card(object):
raise exceptions.NoAceValue( raise exceptions.NoAceValue(
'The Ace has multiple values (1 and 11) in Blackjack. Special handling is required.') 'The Ace has multiple values (1 and 11) in Blackjack. Special handling is required.')
return 0 return 0
elif self.isFace():
if self.isFace():
return 10 return 10
elif self.isNumerical():
numeric_match = re.match(r'^(\d{1,2})', self.raw_card) return int(self.symbol)
if numeric_match is not None: elif safe:
return int(numeric_match.group(1))
if safe:
raise exceptions.IndetermineValue('Could not determine the numeric value of this card.') raise exceptions.IndetermineValue('Could not determine the numeric value of this card.')
return 0 return unsafe_default
def isAce(self) -> bool: def isAce(self) -> bool:
"""Returns whether or not the card is a Ace card."""
return self.symbol == 'a' return self.symbol == 'a'
def isNumerical(self) -> bool: def isNumerical(self) -> bool:
"""Returns whether or not the card is numerical (not face or ace)."""
return self.symbol.isnumeric() return self.symbol.isnumeric()
def isFace(self) -> bool: 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'] return self.symbol in ['q', 'k', 'j']
def parts(self) -> Tuple[str, str]: 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) match = re.match(r'^(\d{1,2}|[aqkj])([cdhs])$', self.raw_card, flags=re.IGNORECASE)
return match.group(1), match.group(2) 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: 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]: def generate_table_structure(filename: str, column_keys: List[str], row_keys: List[str]) -> Dict[Tuple[str, str], str]:
data = {} data = {}
logger.debug(f'Generating table structure with {filename}') logger.debug(f'Generating table structure with {filename}')
with open(os.path.join(constants.STATIC_DIR, filename)) as hard_file: 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 x, col_key in enumerate(column_keys):
for y, row_key in enumerate(row_keys): for y, row_key in enumerate(row_keys):
data[(col_key, row_key)] = raw_data[y][x] 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.""" """Converts the choice to the best possible choice based on the options given by the bot."""
@classmethod @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. Access table data given a column and row key.

View File

@@ -8,7 +8,8 @@ from typing import Optional, Tuple
import discord import discord
from discord.ext.tasks import loop 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 from bot.constants import PlayOptions
logger = logging.getLogger(__file__) logger = logging.getLogger(__file__)
@@ -20,10 +21,10 @@ class UnbelievaClient(discord.Client):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.last_message = -1 self.last_message = -1
# References
self.bot_id, self.channel_id = bot_id, channel_id self.bot_id, self.channel_id = bot_id, channel_id
self.channel: Optional[discord.TextChannel] = None self.channel: Optional[discord.TextChannel] = None
self.bot: Optional[discord.User] = None
self.tasks = { self.tasks = {
'$work': timings.Cooldown(5 * 60 + 2), '$work': timings.Cooldown(5 * 60 + 2),
'$slut': timings.Cooldown(13 * 60 + 2), '$slut': timings.Cooldown(13 * 60 + 2),
@@ -42,7 +43,6 @@ class UnbelievaClient(discord.Client):
await self.wait_until_ready() await self.wait_until_ready()
self.channel: discord.TextChannel = self.get_channel(self.channel_id) self.channel: discord.TextChannel = self.get_channel(self.channel_id)
self.bot = self.bot_id
self.check_task_available.start() self.check_task_available.start()
ctypes.windll.kernel32.SetConsoleTitleW(f"#{self.channel.name}/{self.channel.guild.name}") 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}') 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: if message.channel != self.channel or message.author == self.user:
return 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] embed = message.embeds[0]
is_self = embed.author.name == f'{self.user.name}#{self.user.discriminator}' is_self = embed.author.name == f'{self.user.name}#{self.user.discriminator}'
@@ -73,8 +73,8 @@ class UnbelievaClient(discord.Client):
# Handling for blackjack # Handling for blackjack
if embed.description.startswith('Type `hit` to draw another card'): if embed.description.startswith('Type `hit` to draw another card'):
options = self.parse_options(embed.description) options = self.parse_options(embed.description)
my_cards = self.parse_cards(embed.fields[0]) my_cards = Card.parse_cards(embed.fields[0])
dealer_cards = self.parse_cards(embed.fields[1]) dealer_cards = Card.parse_cards(embed.fields[1])
print(options, my_cards, dealer_cards) print(options, my_cards, dealer_cards)
def parse_options(self, options_str: str) -> PlayOptions: def parse_options(self, options_str: str) -> PlayOptions:
@@ -86,24 +86,6 @@ class UnbelievaClient(discord.Client):
# noinspection PyProtectedMember # noinspection PyProtectedMember
return PlayOptions._make(options) 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): def handle_blackjack(self):
embed = self.current_blackjack.embeds[0] embed = self.current_blackjack.embeds[0]
options = self.parse_options(embed.description) options = self.parse_options(embed.description)
@@ -114,9 +96,7 @@ class UnbelievaClient(discord.Client):
@loop(seconds=1) @loop(seconds=1)
async def check_task_available(self): 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() await self.wait_until_ready()
for task, task_cooldown in self.tasks.items(): for task, task_cooldown in self.tasks.items():
@@ -132,13 +112,3 @@ class UnbelievaClient(discord.Client):
# Activate the cooldowns # Activate the cooldowns
task_cooldown.hit() task_cooldown.hit()
self.command_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)

View File

@@ -3,5 +3,6 @@ SSSSDSSSSS
DDDDDSSHHH DDDDDSSHHH
HDDDDHHHHH HDDDDHHHHH
HHDDDHHHHH HHDDDHHHHH
HHDDDHHHHH
HHHDDHHHHH HHHDDHHHHH
HHHDDHHHHH HHHDDHHHHH