From 4357ea6510e437d566b9da865f8b53708fbdd6a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juhani=20Krekel=C3=A4?= Date: Thu, 9 Apr 2020 22:08:16 +0300 Subject: [PATCH] Add a concept of deck namespaces --- botcmd.py | 18 +++--- gameloop.py | 157 +++++++++++++++++++++++++++++----------------------- 2 files changed, 97 insertions(+), 78 deletions(-) diff --git a/botcmd.py b/botcmd.py index 575c193..00949bc 100644 --- a/botcmd.py +++ b/botcmd.py @@ -145,8 +145,8 @@ def usage(command): ('limit',) : HelpEntry('!limit [ []]', '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 | random', 'Add the deck with the specified cardcast code (or pick one randomly).', False), - ('deck', 'remove') : HelpEntry('!deck remove ', 'Remove the deck with the specified cardcast code.', False), + ('deck', 'add') : HelpEntry('!deck add | random', 'Add the deck with the specified namespace and code (or pick one randomly).', False), + ('deck', 'remove') : HelpEntry('!deck remove ', 'Remove the deck with the namespace and code.', False), ('deck', 'list') : HelpEntry('!deck list', 'List selected decks.', False), ('bot', 'add') : HelpEntry('!bot add []', '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 ', '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: diff --git a/gameloop.py b/gameloop.py index 06f316d..f4c9f44 100644 --- a/gameloop.py +++ b/gameloop.py @@ -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('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 - # 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) - if code is Error: return - if 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)) + 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, namespace) + if code is Error: return + 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 (%%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('') 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':