Add a concept of deck namespaces

This commit is contained in:
Juhani Krekelä 2020-04-09 22:08:16 +03:00
parent 8fedaf40d1
commit 4357ea6510
2 changed files with 97 additions and 78 deletions

View File

@ -145,8 +145,8 @@ def usage(command):
('limit',) : HelpEntry('!limit [<number> [<type>]]', 'Show or adjust the win limit. Type can be "r" for rounds and "p" for points.', False),
('help',) : HelpEntry('!help [command [subcommand]]', 'Show a synopsis and description for the specified command.', False),
('deck', 'add') : HelpEntry('!deck add <code> | random', 'Add the deck with the specified cardcast code (or pick one randomly).', False),
('deck', 'remove') : HelpEntry('!deck remove <code>', 'Remove the deck with the specified cardcast code.', False),
('deck', 'add') : HelpEntry('!deck add <namespace> <code> | random', 'Add the deck with the specified namespace and code (or pick one randomly).', False),
('deck', 'remove') : HelpEntry('!deck remove <namespace> <code>', 'Remove the deck with the namespace and code.', False),
('deck', 'list') : HelpEntry('!deck list', 'List selected decks.', False),
('bot', 'add') : HelpEntry('!bot add <type> [<name>]', 'Add a bot of the specified type and name. If the name is omitted, name the bot after its type.', False),
('bot', 'remove') : HelpEntry('!bot remove <name>', 'Remove the specified bot.', False),
@ -247,19 +247,19 @@ def parse_command(message, nick, irc):
subc = message[1]
if subc == 'add':
args = arg(1, 2)
args = arg(2, 2)
if args is not None:
code, = args
namespace, code, = args
if code == 'random':
send_event((events.deck_add_random,))
send_event((events.deck_add_random, namespace))
else:
send_event((events.deck_add, code))
send_event((events.deck_add, namespace, code))
elif subc == 'remove':
args = arg(1, 2)
args = arg(2, 2)
if args is not None:
code, = args
send_event((events.deck_remove, code))
namespace, code, = args
send_event((events.deck_remove, namespace, code))
elif subc == 'list':
if arg(0, 2) is not None:

View File

@ -16,12 +16,21 @@ class events(enum.Enum):
class limit_types(enum.Enum):
points, rounds = range(2)
Deck = namedtuple('Deck', ['code', 'name', 'author', 'call_count', 'response_count', 'calls', 'responses'])
# fqcode = fully qualified code = (namespace, code)
Deck = namedtuple('Deck', ['fqcode', 'name', 'author', 'call_count', 'response_count', 'calls', 'responses'])
Limit = namedtuple('Limit', ['type', 'number'])
Card = namedtuple('Card', ['deck', 'text'])
Namespace = namedtuple('Namespace', ['url', 'supports_random'])
deck_namespaces = {
'cardcast': Namespace(None, True), # Use the library's default URL
'bslsk05': Namespace('https://dl.puckipedia.com/', False)
}
class IRCFormattingState:
def __init__(self):
# 99 is the "client default colour"
@ -130,15 +139,11 @@ def game(send, notice, voice, devoice, get_event):
send(', '.join(sorted(players) + sorted(i.nick for i in bots.values())))
def add_deck(code):
def add_deck(namespace, code):
nonlocal decks
assert code not in decks
assert (namespace, code) not in decks
# Colondeck and offtopiadeck deck live elsewhere
if code in ('colondeck', 'offtopiadeck'):
base_url = 'https://dl.puckipedia.com/'
else:
base_url = None
base_url = deck_namespaces[namespace].url
# First get info for the deck we're adding
info = cardcast_api.info(code, base_url = base_url)
@ -192,8 +197,8 @@ def game(send, notice, voice, devoice, get_event):
responses[i] = responses[i][:159] + ''
# Add a new deck to list of decks
decks[code] = Deck(
code = code,
decks[(namespace, code)] = Deck(
fqcode = (namespace, code),
name = name,
author = author,
call_count = call_count,
@ -202,33 +207,32 @@ def game(send, notice, voice, devoice, get_event):
responses = responses
)
def get_random_deck_code():
nonlocal cardcast_deck_count
def get_random_deck_code(namespace):
nonlocal remote_deck_count
# Provide the count on subsequent calls
# First time around cardcast_deck_count will be None, so it
# gets requested from Cardcast, like if we didn't pass the
# `count` parameter
# This will update cardcast_deck_count for each call
# unnecessarily, but I think it simplifies the code and is not
# too bad
code, cardcast_deck_count = cardcast_api.random_code(count = cardcast_deck_count)
base_url = deck_namespaces[namespace].url
# Keep track of how many cards there are on the remote, so that we don't
# need to keep rerequesting that
if namespace not in remote_deck_count:
code, remote_deck_count[namespace] = cardcast_api.random_code(base_url = base_url)
else:
code, remote_deck_count[namespace] = cardcast_api.random_code(count = remote_deck_count[namespace], base_url = base_url)
return code
def remove_deck(code):
def remove_deck(namespace, code):
nonlocal decks, round_call_card
# Purge all the cards from the deck from the game
for player_bot in players_bots():
for index, card in enumerate(player_bot.hand):
if card is not None and card.deck.code == code:
if card is not None and card.deck.fqcode == (namespace, code):
player_bot.hand[index] = None
if round_call_card is not None and round_call_card.deck.code == code:
if round_call_card is not None and round_call_card.deck.fqcode == (namespace, code):
round_call_card = None
del decks[code]
del decks[(namespace, code)]
def list_decks():
nonlocal decks
@ -246,33 +250,45 @@ def game(send, notice, voice, devoice, get_event):
responses_left = len(deck.responses)
responses = str(response_count) if response_count == responses_left else '%i/%i' % (responses_left, response_count)
send('%s (%s, by %s, %s black, %s white)' % (
namespace, code = deck.fqcode
send('%s (%s %s, by %s, %s black, %s white)' % (
deck.name,
deck.code,
namespace,
code,
deck.author,
calls,
responses
))
def deck_add_handler(code):
def deck_add_handler(namespace, code):
nonlocal decks
if code not in decks:
errwrapper('Failure adding deck: %s (%%s)' % code, add_deck, code)
if namespace in deck_namespaces:
if (namespace, code) not in decks:
errwrapper('Failure adding deck: %s %s (%%s)' % (namespace, code), add_deck, namespace, code)
else:
send('Deck already added')
else:
send('Unknown deck namespace %s. Try one of: %s' % (namespace, ', '.join(deck_namespaces.keys())))
def deck_add_random_handler():
def deck_add_random_handler(namespace):
nonlocal decks
if namespace in deck_namespaces:
if deck_namespaces[namespace].supports_random:
# Let's hope this never bites us in the butt
while True:
code = errwrapper('Failure getting random code for a deck. (%s)', get_random_deck_code)
code = errwrapper('Failure getting random code for a deck. (%s)', get_random_deck_code, namespace)
if code is Error: return
if code not in decks: break
if (namespace, code) not in decks: break
send('That was weird, got %s randomly but it was already added' % code)
errwrapper('Failure adding deck: %s (%%s)' % code, add_deck, code)
send('Added deck %s (%s)' % (decks[code].name, code))
errwrapper('Failure adding deck: %s %s (%%s)' % (namespace, code), add_deck, namespace, code)
send('Added deck %s (%s %s)' % (decks[(namespace, code)].name, namespace, code))
else:
send('Namespace %s does\'t support adding a random deck. Try one of: %s' % (namespace, ', '.join(namespace for namespace in deck_namespaces.keys() if deck_namespaces[namespace].supports_random)))
else:
send('Unknown deck namespace %s. Try one of: %s' % (namespace, ', '.join(deck_namespaces.keys())))
def get_hand_origins(player):
hand_origins = []
@ -281,7 +297,7 @@ def game(send, notice, voice, devoice, get_event):
if card is None:
hand_origins.append('<empty>')
else:
hand_origins.append(card.deck.code)
hand_origins.append('%s %s' % card.deck.fqcode)
return ', '.join('%i: %s' % (index, i) for index, i in enumerate(hand_origins))
@ -348,18 +364,19 @@ def game(send, notice, voice, devoice, get_event):
send('%s has been removed from the game' % kickee)
elif event == events.deck_add:
code, = args
deck_add_handler(code)
namespace, code = args
deck_add_handler(namespace, code)
elif event == events.deck_add_random:
deck_add_random_handler()
namespace, = args
deck_add_random_handler(namespace)
elif event == events.deck_remove:
code, = args
if code in decks:
errwrapper('Failure removing deck %s (%%s)' % code, remove_deck, code)
namespace, code = args
if (namespace, code) in decks:
errwrapper('Failure removing deck %s (%%s)' % code, remove_deck, namespace, code)
else:
send('No such deck %s' % code)
send('No such deck %s %s' % (namespace, code))
elif event == events.deck_list:
list_decks()
@ -412,27 +429,26 @@ def game(send, notice, voice, devoice, get_event):
def start_game(rest):
if len(rest) == 0 or rest[0] == 'default':
send('Adding the default CAH deck (A5DCM)')
send('Adding the default CAH deck (cardcast A5DCM)')
deck_add_handler('A5DCM')
deck_add_handler('cardcast', 'A5DCM')
elif rest[0] == 'offtopia-random':
send('Adding the default CAH deck (A5DCM), offtopia injoke deck (offtopiadeck), :Deck (colondeck) and three random decks')
send('Adding the default CAH deck (cardcast A5DCM), offtopia injoke deck (bslsk05 offtopiadeck), :Deck (bslsk05 colondeck) and three random cardcast decks')
deck_add_handler('A5DCM')
deck_add_handler('offtopiadeck')
deck_add_handler('colondeck')
deck_add_handler('cardcast', 'A5DCM')
deck_add_handler('bslsk05', 'offtopiadeck')
deck_add_handler('bslsk05', 'colondeck')
deck_add_random_handler()
deck_add_random_handler()
deck_add_random_handler()
for _ in range(3):
deck_add_random_handler('cardcast')
elif rest[0] == 'offtopia':
send('Adding the default CAH deck (A5DCM), offtopia injoke deck (offtopiadeck), and :Deck (colondeck)')
send('Adding the default CAH deck (cardcast A5DCM), offtopia injoke deck (bslsk05 offtopiadeck), and :Deck (bslsk05 colondeck)')
deck_add_handler('A5DCM')
deck_add_handler('offtopiadeck')
deck_add_handler('colondeck')
deck_add_handler('cardcast', 'A5DCM')
deck_add_handler('bslsk05', 'offtopiadeck')
deck_add_handler('bslsk05', 'colondeck')
elif rest[0] != 'empty':
send('Unknown preset %s' % rest[0])
@ -1067,10 +1083,10 @@ def game(send, notice, voice, devoice, get_event):
nick, = args
if nick not in players:
notice(nick, 'call: %s' % round_call_card.deck.code)
notice(nick, 'call: %s %s ' % round_call_card.deck.fqcode)
else:
notice(nick, 'call: %s, %s' % (round_call_card.deck.code, get_hand_origins(players[nick])))
notice(nick, 'call: %s %s, %s' % (*round_call_card.deck.fqcode, get_hand_origins(players[nick])))
elif event == events.redeal:
nick, = args
@ -1204,15 +1220,15 @@ def game(send, notice, voice, devoice, get_event):
nick, = args
if nick not in players:
notice(nick, 'call: %s' % round_call_card.deck.code)
notice(nick, 'call: %s %s' % round_call_card.deck.fqcode)
else:
answers_origins = []
for index, player_bot in enumerate(choosers):
answer_origins = [i.deck.code for i in card_choices[player_bot]]
answer_origins = ['%s %s' % i.deck.fqcode for i in card_choices[player_bot]]
answers_origins.append('%i: %s' % (index, ', '.join(answer_origins)))
notice(nick, 'call: %s; %s' % (round_call_card.deck.code, '; '.join(answers_origins)))
notice(nick, 'call: %s %s; %s' % (*round_call_card.deck.fqcode, '; '.join(answers_origins)))
elif event == events.redeal:
nick, = args
@ -1314,7 +1330,7 @@ def game(send, notice, voice, devoice, get_event):
czar = None
card_choices = None
cardcast_deck_count = None
remote_deck_count = {}
state = no_game
while state != quit:
@ -1366,13 +1382,16 @@ if __name__ == '__main__':
kickee = input('kickee> ')
return (events.kick, kicker, kickee)
elif t == 'deck add':
namespace = input('namespace> ')
code = input('code> ')
return (events.deck_add, code)
return (events.deck_add, namespace, code)
elif t == 'deck add random':
return (events.deck_add_random,)
namespace = input('namespace> ')
return (events.deck_add_random, namespace)
elif t == 'deck remove':
namespace = input('namespace> ')
code = input('code> ')
return (events.deck_remove, code)
return (events.deck_remove, namespace, code)
elif t == 'deck list':
return (events.deck_list,)
elif t == 'bot add rando':