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 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.

View File

@@ -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)

View File

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