This commit is contained in:
Juhani Krekelä 2019-05-10 19:25:55 +03:00
parent 34e397a9db
commit fab9ba14d4
2 changed files with 152 additions and 61 deletions

View File

@ -218,6 +218,30 @@ def parse_command(message, nick, irc):
else: else:
send('Subcommands: !deck add | remove | list') send('Subcommands: !deck add | remove | list')
elif c == '!bot':
if len(message) < 2:
send('Subcommands: !bot add | remove')
return
subc = message[1]
if subc == 'add':
arg = args(2, 2)
if arg is not None:
bot_type, name = arg
if bot_type == 'rando':
send_event((events.bot_add_rando, name))
else:
send('Allowed bot types: rando')
elif subc == 'remove':
arg = args(1, 2)
if arg is not None:
name, = arg
send_event((events.bot_remove, name))
else:
send('Subcommands: !bot add | remove')
elif c == '!limit': elif c == '!limit':
arg = args([0, 1, 2]) arg = args([0, 1, 2])
if arg is None: return if arg is None: return
@ -287,6 +311,8 @@ def parse_command(message, nick, irc):
send('Usage: [!card] <number> ...') send('Usage: [!card] <number> ...')
elif arg[0] == 'deck': elif arg[0] == 'deck':
send('Subcommands: !deck add | remove | list') send('Subcommands: !deck add | remove | list')
elif arg[0] == 'bot':
send('Subcommands: !bot add | remove')
elif arg[0] == 'limit': elif arg[0] == 'limit':
send('Usage: !limit [<number> [<type>]]') send('Usage: !limit [<number> [<type>]]')
else: else:
@ -301,6 +327,13 @@ def parse_command(message, nick, irc):
send('Usage: !deck list') send('Usage: !deck list')
else: else:
send('No such subcommand !%s %s' % (arg[0], arg[1])) send('No such subcommand !%s %s' % (arg[0], arg[1]))
elif arg[0] == 'bot':
if arg[1] == 'add':
send('Usage: !bot add <type> <name>')
elif arg[1] == 'remove':
send('Usage: !bot remove <name>')
else:
send('No such subcommand !%s %s' % (arg[0], arg[1]))
else: else:
send('No such subcommand !%s %s' % (arg[0], arg[1])) send('No such subcommand !%s %s' % (arg[0], arg[1]))

View File

@ -4,11 +4,14 @@ from collections import namedtuple
import cardcast_api import cardcast_api
# TODO: rando
# TODO: https://dl.puckipedia.com/colondeck{,/cards}
class events(enum.Enum): class events(enum.Enum):
quit, nick_change, status, start, ready, unready, kill, join, leave, players, kick, deck_add, deck_add_random, deck_remove, deck_list, limit, card, cards, origins = range(19) (quit, nick_change,
status, start, ready, unready, kill,
join, leave, players, kick,
deck_add, deck_add_random, deck_remove, deck_list,
bot_add_rando, bot_remove,
limit,
card, cards, origins) = range(21)
class limit_types(enum.Enum): class limit_types(enum.Enum):
points, rounds = range(2) points, rounds = range(2)
@ -35,6 +38,23 @@ class Player:
def __hash__(self): def __hash__(self):
return id(self) return id(self)
class Rando:
def __init__(self, name):
self.nick = '<%s>' % name
self.hand = []
self.points = 0
def num_need_cards(self, num_blanks):
return num_blanks - len(self.hand) + self.hand.count(None)
def give_cards(self, cards):
self.hand.extend(cards)
self.hand = [i for i in self.hand if i is not None]
def play(self, num_blanks):
return list(range(num_blanks))
class Error: pass class Error: pass
def game(send, notice, voice, devoice, get_event): def game(send, notice, voice, devoice, get_event):
@ -48,6 +68,12 @@ def game(send, notice, voice, devoice, get_event):
error(message % ('%s, %s' % (type(err), err))) error(message % ('%s, %s' % (type(err), err)))
return Error return Error
def players_bots():
nonlocal players, bots
yield from players.values()
yield from bots.values()
def add_player(nick): def add_player(nick):
nonlocal players nonlocal players
assert nick not in players assert nick not in players
@ -68,10 +94,9 @@ def game(send, notice, voice, devoice, get_event):
players[new] = player players[new] = player
def list_players(): def list_players():
nonlocal players nonlocal players, bots
send(', '.join(sorted(players))) send(', '.join(sorted(players) + sorted(i.nick for i in bots.values())))
# TODO: Add bots
def add_deck(code): def add_deck(code):
nonlocal decks nonlocal decks
@ -142,15 +167,13 @@ def game(send, notice, voice, devoice, get_event):
return code return code
def remove_deck(code): def remove_deck(code):
nonlocal players, 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 in players.values(): for player_bot in players_bots():
for index, card in enumerate(player.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.code == code:
player.hand[index] = None player_bot.hand[index] = None
# TODO: Remove from bots
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.code == code:
round_call_card = None round_call_card = None
@ -213,7 +236,7 @@ def game(send, notice, voice, devoice, get_event):
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))
def common_handler(event, args): def common_handler(event, args):
nonlocal players, decks, limit nonlocal players, bots, decks, limit
if event == events.kill: if event == events.kill:
send('Stopping game') send('Stopping game')
@ -279,6 +302,20 @@ def game(send, notice, voice, devoice, get_event):
elif event == events.deck_list: elif event == events.deck_list:
list_decks() list_decks()
elif event == events.bot_add_rando:
name, = args
if name not in bots:
bots[name] = Rando(name)
else:
send('Bot named %s already exists' % name)
elif event == events.bot_remove:
name, = args
if name in bots:
del bots[name]
else:
send('No such bot %s' % name)
elif event == events.limit: elif event == events.limit:
if len(args) == 0: if len(args) == 0:
limit_type = {limit_types.rounds: 'rounds', limit_types.points: 'points'}[limit.type] limit_type = {limit_types.rounds: 'rounds', limit_types.points: 'points'}[limit.type]
@ -310,11 +347,12 @@ def game(send, notice, voice, devoice, get_event):
error('Unknown event type: %s' % event) error('Unknown event type: %s' % event)
def no_game(): def no_game():
nonlocal players, decks, limit, round_number, round_call_card, czar, card_choices nonlocal players, bots, decks, limit, round_number, round_call_card, czar, card_choices
if players is not None: if players is not None:
devoice(players) devoice(players)
players = {} players = {}
bots = {}
decks = {} decks = {}
limit = Limit(limit_types.points, 5) limit = Limit(limit_types.points, 5)
round_number = 1 round_number = 1
@ -477,7 +515,7 @@ def game(send, notice, voice, devoice, get_event):
return responses return responses
def setup_round(): def setup_round():
nonlocal players, round_call_card, czar, card_choices nonlocal players, bots, round_call_card, czar, card_choices
# Select a czar randomly, if we need to # Select a czar randomly, if we need to
if czar not in players.values(): if czar not in players.values():
@ -505,7 +543,11 @@ def game(send, notice, voice, devoice, get_event):
if len(player.hand) < 10: if len(player.hand) < 10:
need_responses += 10 - len(player.hand) need_responses += 10 - len(player.hand)
need_responses += player.hand.count(None) need_responses += player.hand.count(None)
# TODO: Add bot hooks
# See note above num_blanks in top_of_round()
num_blanks = len(round_call_card.text) - 1
for bot in bots.values():
need_responses += bot.num_need_cards(num_blanks)
# If we don't have enough, kick back to setup # If we don't have enough, kick back to setup
available_responses = total_responses() available_responses = total_responses()
@ -527,7 +569,15 @@ def game(send, notice, voice, devoice, get_event):
for index in range(10): for index in range(10):
if player.hand[index] is None: if player.hand[index] is None:
player.hand[index] = responses.pop() player.hand[index] = responses.pop()
# TODO: Add bot hooks
# Give cards to bots
for bot in bots.values():
needed = bot.num_need_cards(num_blanks)
fed = responses[:needed]
responses = responses[needed:]
bot.give_cards(fed)
return top_of_round return top_of_round
@ -584,15 +634,25 @@ def game(send, notice, voice, devoice, get_event):
return ''.join(combined) return ''.join(combined)
def combine_played(call, player_bot, selected_cards):
return combine_cards(call.text, [player_bot.hand[i].text for i in selected_cards])
def top_of_round(): def top_of_round():
nonlocal players, round_number, round_call_card, czar, card_choices nonlocal players, bots, round_number, round_call_card, czar, card_choices
choosers = [i for i in players.values() if i is not czar] choosers = [i for i in players.values() if i is not czar]
send('Round %i. %s is czar. %s choose your cards' % (round_number, czar.nick, ', '.join(i.nick for i in choosers))) send('Round %i. %s is czar. %s choose your cards' % (round_number, czar.nick, ', '.join(i.nick for i in choosers)))
send('[%s]' % '_'.join(sanitize(part) for part in round_call_card.text)) send('[%s]' % '_'.join(sanitize(part) for part in round_call_card.text))
# TODO: Bot plays # Round call card has N parts. Between each of those parts
# goes one response card. Therefore there should be N - 1
# response cards
num_blanks = len(round_call_card.text) - 1
# Have bots choose first
for bot in bots.values():
card_choices[bot] = bot.play(num_blanks)
for nick in players: for nick in players:
if players[nick] is not czar: if players[nick] is not czar:
@ -633,10 +693,7 @@ def game(send, notice, voice, devoice, get_event):
notice(nick, 'You\'ll get to choose next round') notice(nick, 'You\'ll get to choose next round')
continue continue
# Round call card has N parts. Between each of if len(choices) != num_blanks:
# those parts goes one response card. Therefore
# there should be N - 1 response cards
if len(choices) != len(round_call_card.text) - 1:
notice(nick, 'Select %i card(s)' % (len(round_call_card.text) - 1)) notice(nick, 'Select %i card(s)' % (len(round_call_card.text) - 1))
continue continue
@ -659,7 +716,7 @@ def game(send, notice, voice, devoice, get_event):
card_choices[player] = selected_cards card_choices[player] = selected_cards
if player in choosers: if player in choosers:
choosers.remove(player) choosers.remove(player)
notice(nick, combine_cards(round_call_card.text, [player.hand[i].text for i in selected_cards])) notice(nick, combine_played(round_call_card, player, selected_cards))
elif event == events.cards: elif event == events.cards:
nick, = args nick, = args
@ -700,16 +757,15 @@ def game(send, notice, voice, devoice, get_event):
send('Lost a card from player\'s hand, restarting round') send('Lost a card from player\'s hand, restarting round')
return setup_round return setup_round
for player in card_choices: for player_bot in card_choices:
# We are checking all cards here, not # We are checking all cards here, not
# just the ones chosen. This is because # just the ones chosen. This is because
# a player may change their selection, # a player may change their selection,
# in which case we might hit a None # in which case we might hit a None
if None in player.hand: if None in player_bot.hand:
# Yes, restart round # Yes, restart round
send('Lost a card from player\'s hand, restarting round') send('Lost a card from player\'s hand, restarting round')
return setup_round return setup_round
# TODO: Consider bots
else: else:
r = common_handler(event, args) r = common_handler(event, args)
@ -724,9 +780,8 @@ def game(send, notice, voice, devoice, get_event):
# Display the cards # Display the cards
choosers = random.sample(card_choices.keys(), k = len(card_choices)) choosers = random.sample(card_choices.keys(), k = len(card_choices))
for index, player in enumerate(choosers): for index, player_bot in enumerate(choosers):
send('%i: %s' % (index, combine_cards(round_call_card.text, [player.hand[i].text for i in card_choices[player]]))) send('%i: %s' % (index, combine_played(round_call_card, player_bot, card_choices[player_bot])))
# TODO: Consider bots
while True: while True:
if len(players) < 2: if len(players) < 2:
@ -761,14 +816,17 @@ def game(send, notice, voice, devoice, get_event):
choice = choices[0] choice = choices[0]
if 0 <= choice < len(choosers): if 0 <= choice < len(choosers):
player = choosers[choice] player_bot = choosers[choice]
player.points += 1 player_bot.points += 1
# Winner is Czar semantics # Winner is Czar semantics if a
czar = player # player won, random otherwise
if player_bot in players.values():
czar = player_bot
else:
czar = None
send('The winner is %s with: %s' % (player.nick, combine_cards(round_call_card.text, [player.hand[i].text for i in card_choices[player]]))) send('The winner is %s with: %s' % (player_bot.nick, combine_played(round_call_card, player_bot, card_choices[player_bot])))
# TODO: Consider bots
break break
@ -778,9 +836,8 @@ def game(send, notice, voice, devoice, get_event):
elif len(choices) == 0: elif len(choices) == 0:
# Special case: award everyone a point # Special case: award everyone a point
# and randomize czar # and randomize czar
for player in card_choices: for player_bot in card_choices:
player.points += 1 player_bot.points += 1
# TODO: Consider bots
# If we set czar to None, setup_round() # If we set czar to None, setup_round()
# will handle ramdomizing it for us # will handle ramdomizing it for us
@ -801,10 +858,9 @@ def game(send, notice, voice, devoice, get_event):
else: else:
answers_origins = [] answers_origins = []
for index, player in enumerate(choosers): for index, player_bot in enumerate(choosers):
answer_origins = [player.hand[i].deck.code for i in card_choices[player]] answer_origins = [player_bot.hand[i].deck.code 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)))
# TODO: Consider bots
notice(nick, 'call: %s; %s' % (round_call_card.deck.code, '; '.join(answers_origins))) notice(nick, 'call: %s; %s' % (round_call_card.deck.code, '; '.join(answers_origins)))
@ -818,25 +874,23 @@ def game(send, notice, voice, devoice, get_event):
return setup_round return setup_round
# Did it affect any response cards on this round? # Did it affect any response cards on this round?
for player in card_choices: for player_bot in card_choices:
for index in card_choices[player]: for index in card_choices[player_bot]:
if player.hand[index] is None: if player_bot.hand[index] is None:
# Yes, restart round # Yes, restart round
send('Lost a card played this round, restarting round') send('Lost a card played this round, restarting round')
return setup_round return setup_round
# TODO: Consider bots
else: else:
r = common_handler(event, args) r = common_handler(event, args)
if r is not None: return r if r is not None: return r
points = [] points = []
for player in players.values(): for player_bot in players_bots():
if player in choosers: if player_bot in choosers:
points.append('%s: %i (%i)' % (player.nick, player.points, choosers.index(player))) points.append('%s: %i (%i)' % (player_bot.nick, player_bot.points, choosers.index(player_bot)))
else: else:
points.append('%s: %i' % (player.nick, player.points)) points.append('%s: %i' % (player_bot.nick, player_bot.points))
# TODO: Handle bots
send('Points: %s' % ' | '.join(points)) send('Points: %s' % ' | '.join(points))
@ -850,15 +904,13 @@ def game(send, notice, voice, devoice, get_event):
return end_game return end_game
elif limit.type == limit_types.points: elif limit.type == limit_types.points:
if max(i.points for i in players.values()) >= limit.number: if max(i.points for i in players_bots()) >= limit.number:
return end_game return end_game
# TODO: Handle bots
# Remove the cards that were played this round from hands # Remove the cards that were played this round from hands
for player in card_choices: for player_bot in card_choices:
for index in card_choices[player]: for index in card_choices[player_bot]:
player.hand[index] = None player_bot.hand[index] = None
# TODO: Consider bots
# Increase the number of the round and clear the call card # Increase the number of the round and clear the call card
# These are not done in setup_round() since we might want to # These are not done in setup_round() since we might want to
@ -872,10 +924,9 @@ def game(send, notice, voice, devoice, get_event):
def end_game(): def end_game():
nonlocal players nonlocal players
# TODO: Handle bots max_score = max(i.points for i in players_bots())
max_score = max(i.points for i in players.values())
winners = [i for i in players.values() if i.points == max_score] winners = [i for i in players_bots() if i.points == max_score]
send('We have a winner! %s' % ', '.join(i.nick for i in winners)) send('We have a winner! %s' % ', '.join(i.nick for i in winners))
@ -885,6 +936,7 @@ def game(send, notice, voice, devoice, get_event):
pass pass
players = None players = None
bots = None
decks = None decks = None
limit = None limit = None
@ -946,6 +998,12 @@ if __name__ == '__main__':
return (events.deck_remove, code) return (events.deck_remove, code)
elif t == 'deck list': elif t == 'deck list':
return (events.deck_list,) return (events.deck_list,)
elif t == 'bot add rando':
name = input('name> ')
return (events.bot_add_rando, name)
elif t == 'bot remove':
name = input('name> ')
return (events.bot_remove, name)
elif t == 'limit': elif t == 'limit':
return (events.limit,) return (events.limit,)
elif t == 'limit_set': elif t == 'limit_set':