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), ('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), ('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', '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 <code>', 'Remove the deck with the specified cardcast code.', 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), ('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', '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), ('bot', 'remove') : HelpEntry('!bot remove <name>', 'Remove the specified bot.', False),
@ -247,19 +247,19 @@ def parse_command(message, nick, irc):
subc = message[1] subc = message[1]
if subc == 'add': if subc == 'add':
args = arg(1, 2) args = arg(2, 2)
if args is not None: if args is not None:
code, = args namespace, code, = args
if code == 'random': if code == 'random':
send_event((events.deck_add_random,)) send_event((events.deck_add_random, namespace))
else: else:
send_event((events.deck_add, code)) send_event((events.deck_add, namespace, code))
elif subc == 'remove': elif subc == 'remove':
args = arg(1, 2) args = arg(2, 2)
if args is not None: if args is not None:
code, = args namespace, code, = args
send_event((events.deck_remove, code)) send_event((events.deck_remove, namespace, code))
elif subc == 'list': elif subc == 'list':
if arg(0, 2) is not None: if arg(0, 2) is not None:

View File

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