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:
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':
arg = args([0, 1, 2])
if arg is None: return
@ -287,6 +311,8 @@ def parse_command(message, nick, irc):
send('Usage: [!card] <number> ...')
elif arg[0] == 'deck':
send('Subcommands: !deck add | remove | list')
elif arg[0] == 'bot':
send('Subcommands: !bot add | remove')
elif arg[0] == 'limit':
send('Usage: !limit [<number> [<type>]]')
else:
@ -301,6 +327,13 @@ def parse_command(message, nick, irc):
send('Usage: !deck list')
else:
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:
send('No such subcommand !%s %s' % (arg[0], arg[1]))

View File

@ -4,11 +4,14 @@ from collections import namedtuple
import cardcast_api
# TODO: rando
# TODO: https://dl.puckipedia.com/colondeck{,/cards}
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):
points, rounds = range(2)
@ -35,6 +38,23 @@ class Player:
def __hash__(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
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)))
return Error
def players_bots():
nonlocal players, bots
yield from players.values()
yield from bots.values()
def add_player(nick):
nonlocal players
assert nick not in players
@ -68,10 +94,9 @@ def game(send, notice, voice, devoice, get_event):
players[new] = player
def list_players():
nonlocal players
nonlocal players, bots
send(', '.join(sorted(players)))
# TODO: Add bots
send(', '.join(sorted(players) + sorted(i.nick for i in bots.values())))
def add_deck(code):
nonlocal decks
@ -142,15 +167,13 @@ def game(send, notice, voice, devoice, get_event):
return 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
for player in players.values():
for index, card in enumerate(player.hand):
for player_bot in players_bots():
for index, card in enumerate(player_bot.hand):
if card is not None and card.deck.code == code:
player.hand[index] = None
# TODO: Remove from bots
player_bot.hand[index] = None
if round_call_card is not None and round_call_card.deck.code == code:
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))
def common_handler(event, args):
nonlocal players, decks, limit
nonlocal players, bots, decks, limit
if event == events.kill:
send('Stopping game')
@ -279,6 +302,20 @@ def game(send, notice, voice, devoice, get_event):
elif event == events.deck_list:
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:
if len(args) == 0:
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)
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:
devoice(players)
players = {}
bots = {}
decks = {}
limit = Limit(limit_types.points, 5)
round_number = 1
@ -477,7 +515,7 @@ def game(send, notice, voice, devoice, get_event):
return responses
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
if czar not in players.values():
@ -505,7 +543,11 @@ def game(send, notice, voice, devoice, get_event):
if len(player.hand) < 10:
need_responses += 10 - len(player.hand)
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
available_responses = total_responses()
@ -527,7 +569,15 @@ def game(send, notice, voice, devoice, get_event):
for index in range(10):
if player.hand[index] is None:
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
@ -584,15 +634,25 @@ def game(send, notice, voice, devoice, get_event):
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():
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]
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))
# 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:
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')
continue
# Round call card has N parts. Between each of
# those parts goes one response card. Therefore
# there should be N - 1 response cards
if len(choices) != len(round_call_card.text) - 1:
if len(choices) != num_blanks:
notice(nick, 'Select %i card(s)' % (len(round_call_card.text) - 1))
continue
@ -659,7 +716,7 @@ def game(send, notice, voice, devoice, get_event):
card_choices[player] = selected_cards
if player in choosers:
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:
nick, = args
@ -700,16 +757,15 @@ def game(send, notice, voice, devoice, get_event):
send('Lost a card from player\'s hand, restarting round')
return setup_round
for player in card_choices:
for player_bot in card_choices:
# We are checking all cards here, not
# just the ones chosen. This is because
# a player may change their selection,
# in which case we might hit a None
if None in player.hand:
if None in player_bot.hand:
# Yes, restart round
send('Lost a card from player\'s hand, restarting round')
return setup_round
# TODO: Consider bots
else:
r = common_handler(event, args)
@ -724,9 +780,8 @@ def game(send, notice, voice, devoice, get_event):
# Display the cards
choosers = random.sample(card_choices.keys(), k = len(card_choices))
for index, player in enumerate(choosers):
send('%i: %s' % (index, combine_cards(round_call_card.text, [player.hand[i].text for i in card_choices[player]])))
# TODO: Consider bots
for index, player_bot in enumerate(choosers):
send('%i: %s' % (index, combine_played(round_call_card, player_bot, card_choices[player_bot])))
while True:
if len(players) < 2:
@ -761,14 +816,17 @@ def game(send, notice, voice, devoice, get_event):
choice = choices[0]
if 0 <= choice < len(choosers):
player = choosers[choice]
player.points += 1
player_bot = choosers[choice]
player_bot.points += 1
# Winner is Czar semantics
czar = player
# Winner is Czar semantics if a
# 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]])))
# TODO: Consider bots
send('The winner is %s with: %s' % (player_bot.nick, combine_played(round_call_card, player_bot, card_choices[player_bot])))
break
@ -778,9 +836,8 @@ def game(send, notice, voice, devoice, get_event):
elif len(choices) == 0:
# Special case: award everyone a point
# and randomize czar
for player in card_choices:
player.points += 1
# TODO: Consider bots
for player_bot in card_choices:
player_bot.points += 1
# If we set czar to None, setup_round()
# will handle ramdomizing it for us
@ -801,10 +858,9 @@ def game(send, notice, voice, devoice, get_event):
else:
answers_origins = []
for index, player in enumerate(choosers):
answer_origins = [player.hand[i].deck.code for i in card_choices[player]]
for index, player_bot in enumerate(choosers):
answer_origins = [player_bot.hand[i].deck.code for i in card_choices[player_bot]]
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)))
@ -818,25 +874,23 @@ def game(send, notice, voice, devoice, get_event):
return setup_round
# Did it affect any response cards on this round?
for player in card_choices:
for index in card_choices[player]:
if player.hand[index] is None:
for player_bot in card_choices:
for index in card_choices[player_bot]:
if player_bot.hand[index] is None:
# Yes, restart round
send('Lost a card played this round, restarting round')
return setup_round
# TODO: Consider bots
else:
r = common_handler(event, args)
if r is not None: return r
points = []
for player in players.values():
if player in choosers:
points.append('%s: %i (%i)' % (player.nick, player.points, choosers.index(player)))
for player_bot in players_bots():
if player_bot in choosers:
points.append('%s: %i (%i)' % (player_bot.nick, player_bot.points, choosers.index(player_bot)))
else:
points.append('%s: %i' % (player.nick, player.points))
# TODO: Handle bots
points.append('%s: %i' % (player_bot.nick, player_bot.points))
send('Points: %s' % ' | '.join(points))
@ -850,15 +904,13 @@ def game(send, notice, voice, devoice, get_event):
return end_game
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
# TODO: Handle bots
# Remove the cards that were played this round from hands
for player in card_choices:
for index in card_choices[player]:
player.hand[index] = None
# TODO: Consider bots
for player_bot in card_choices:
for index in card_choices[player_bot]:
player_bot.hand[index] = None
# Increase the number of the round and clear the call card
# 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():
nonlocal players
# TODO: Handle bots
max_score = max(i.points for i in players.values())
max_score = max(i.points for i in players_bots())
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))
@ -885,6 +936,7 @@ def game(send, notice, voice, devoice, get_event):
pass
players = None
bots = None
decks = None
limit = None
@ -946,6 +998,12 @@ if __name__ == '__main__':
return (events.deck_remove, code)
elif t == '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':
return (events.limit,)
elif t == 'limit_set':