Rando!
This commit is contained in:
parent
34e397a9db
commit
fab9ba14d4
2 changed files with 152 additions and 61 deletions
33
botcmd.py
33
botcmd.py
|
@ -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]))
|
||||
|
||||
|
|
180
gameloop.py
180
gameloop.py
|
@ -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':
|
||||
|
|
Loading…
Reference in a new issue