mirror of
https://github.com/Xevion/unbelievaselfbot.git
synced 2025-12-10 18:09:05 -06:00
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:
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
|
||||||
|
|||||||
@@ -3,5 +3,6 @@ SSSSDSSSSS
|
|||||||
DDDDDSSHHH
|
DDDDDSSHHH
|
||||||
HDDDDHHHHH
|
HDDDDHHHHH
|
||||||
HHDDDHHHHH
|
HHDDDHHHHH
|
||||||
|
HHDDDHHHHH
|
||||||
HHHDDHHHHH
|
HHHDDHHHHH
|
||||||
HHHDDHHHHH
|
HHHDDHHHHH
|
||||||
|
|||||||
Reference in New Issue
Block a user