2019-05-05 20:39:50 +00:00
|
|
|
import enum
|
|
|
|
import random
|
|
|
|
from collections import namedtuple
|
|
|
|
|
|
|
|
import cardcast_api
|
|
|
|
|
2019-05-06 11:57:04 +00:00
|
|
|
# TODO: rando
|
|
|
|
# TODO: $0 support
|
|
|
|
# TODO: collapse several _ in a row into one
|
|
|
|
# TODO: keep track of where cards come from purge from hand when deck remove
|
|
|
|
|
2019-05-05 20:39:50 +00:00
|
|
|
class events(enum.Enum):
|
2019-05-06 08:32:11 +00:00
|
|
|
quit, nick_change, status, start, ready, unready, kill, join, leave, players, deck_add, deck_add_random, deck_remove, deck_list, limit, card, cards = range(17)
|
2019-05-05 20:39:50 +00:00
|
|
|
|
|
|
|
class limit_types(enum.Enum):
|
|
|
|
points, rounds = range(2)
|
|
|
|
|
|
|
|
Deck = namedtuple('Deck', ['code', 'name', 'author', 'call_count', 'response_count', 'calls', 'responses'])
|
|
|
|
|
|
|
|
Limit = namedtuple('Limit', ['type', 'number'])
|
|
|
|
|
|
|
|
class Player:
|
|
|
|
def __init__(self, nick):
|
|
|
|
self.nick = nick
|
|
|
|
|
|
|
|
self.hand = []
|
|
|
|
self.points = 0
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
if __name__ == '__main__':
|
|
|
|
return 'Player(%s)' % repr(self.nick)
|
|
|
|
else:
|
|
|
|
return '%s.Player(%s)' % (__name__, repr(self.nick))
|
|
|
|
|
|
|
|
def __hash__(self):
|
|
|
|
return id(self)
|
|
|
|
|
|
|
|
def get_event():
|
|
|
|
while True:
|
|
|
|
try:
|
|
|
|
t = input('> ')
|
|
|
|
except EOFError:
|
|
|
|
return (events.quit,)
|
|
|
|
|
|
|
|
if t == 'nick':
|
|
|
|
old = input('old> ')
|
|
|
|
new = input('new> ')
|
|
|
|
return (events.nick_change, old, new)
|
2019-05-06 08:32:11 +00:00
|
|
|
elif t == 'start':
|
2019-05-05 20:39:50 +00:00
|
|
|
nick = input('nick> ')
|
|
|
|
return (events.start, nick)
|
2019-05-06 08:32:11 +00:00
|
|
|
elif t == 'ready':
|
|
|
|
nick = input('nick> ')
|
|
|
|
return (events.ready, nick)
|
|
|
|
elif t == 'unready':
|
|
|
|
nick = input('nick> ')
|
|
|
|
return (events.unready, nick)
|
2019-05-05 20:39:50 +00:00
|
|
|
elif t == 'status':
|
|
|
|
return (events.status,)
|
|
|
|
elif t == 'kill':
|
|
|
|
return (events.kill,)
|
|
|
|
elif t == 'join':
|
|
|
|
nick = input('nick> ')
|
|
|
|
return (events.join, nick)
|
|
|
|
elif t == 'leave':
|
|
|
|
nick = input('nick> ')
|
|
|
|
return (events.leave, nick)
|
|
|
|
elif t == 'players':
|
|
|
|
return (events.players,)
|
|
|
|
elif t == 'deck add':
|
|
|
|
code = input('code> ')
|
|
|
|
return (events.deck_add, code)
|
|
|
|
elif t == 'deck add random':
|
|
|
|
return (events.deck_add_random,)
|
|
|
|
elif t == 'deck remove':
|
|
|
|
code = input('code> ')
|
|
|
|
return (events.deck_remove, code)
|
|
|
|
elif t == 'deck list':
|
|
|
|
return (events.deck_list,)
|
|
|
|
elif t == 'limit':
|
|
|
|
return (events.limit,)
|
|
|
|
elif t == 'limit_set':
|
|
|
|
limit_type = {'r': limit_types.rounds, 'p': limit_types.points}[input('type (p/r)> ')]
|
|
|
|
number = int(input('limit> '))
|
|
|
|
return (events.limit, limit_type, number)
|
|
|
|
elif t == 'card':
|
|
|
|
nick = input('nick> ')
|
|
|
|
choice = [int(i) for i in input('choice> ').split()]
|
|
|
|
return (events.card, nick, choice)
|
2019-05-06 08:00:39 +00:00
|
|
|
elif t == 'cards':
|
|
|
|
nick = input('nick> ')
|
|
|
|
return (events.cards, nick)
|
2019-05-05 20:39:50 +00:00
|
|
|
else:
|
|
|
|
print('?')
|
|
|
|
|
2019-05-06 09:41:12 +00:00
|
|
|
def send(text):
|
|
|
|
print(text)
|
2019-05-05 20:39:50 +00:00
|
|
|
|
2019-05-06 09:41:12 +00:00
|
|
|
def notice(nick, text):
|
|
|
|
print('\t', nick, text)
|
2019-05-05 20:39:50 +00:00
|
|
|
|
2019-05-06 09:41:12 +00:00
|
|
|
class Error: pass
|
2019-05-05 20:39:50 +00:00
|
|
|
|
2019-05-06 09:41:12 +00:00
|
|
|
def game(send, notice, get_event):
|
2019-05-05 20:39:50 +00:00
|
|
|
def error(message):
|
|
|
|
send('Error: %s' % message)
|
|
|
|
|
|
|
|
def errwrapper(message, f, *args, **kwargs):
|
|
|
|
try:
|
|
|
|
return f(*args, **kwargs)
|
|
|
|
except Exception as err:
|
|
|
|
error(message % ('%s, %s' % (type(err), err)))
|
|
|
|
return Error
|
|
|
|
|
|
|
|
def add_player(nick):
|
|
|
|
nonlocal players
|
|
|
|
assert nick not in players
|
|
|
|
|
|
|
|
players[nick] = Player(nick)
|
|
|
|
|
|
|
|
def remove_player(nick):
|
|
|
|
nonlocal players
|
|
|
|
|
|
|
|
del players[nick]
|
|
|
|
|
|
|
|
def change_player_nick(old, new):
|
|
|
|
nonlocal players
|
|
|
|
|
|
|
|
player = players[old]
|
|
|
|
del players[old]
|
|
|
|
player.nick = new
|
|
|
|
players[new] = player
|
|
|
|
|
|
|
|
def list_players():
|
|
|
|
nonlocal players
|
|
|
|
|
|
|
|
send(', '.join(sorted(players)))
|
|
|
|
|
|
|
|
def add_deck(code):
|
|
|
|
nonlocal decks
|
|
|
|
assert code not in decks
|
|
|
|
|
|
|
|
# First get info for the deck we're adding
|
|
|
|
info = cardcast_api.info(code)
|
|
|
|
|
|
|
|
# Extract the information we want to keep of the deck
|
|
|
|
name = info['name']
|
|
|
|
author = info['author']['username']
|
|
|
|
call_count = int(info['call_count'])
|
|
|
|
response_count = int(info['response_count'])
|
|
|
|
|
|
|
|
# Get cards
|
|
|
|
calls, responses = cardcast_api.cards(code)
|
|
|
|
|
|
|
|
# Add a new deck to list of decks
|
|
|
|
decks[code] = Deck(
|
|
|
|
code = code,
|
|
|
|
name = name,
|
|
|
|
author = author,
|
|
|
|
call_count = call_count,
|
|
|
|
response_count = response_count,
|
|
|
|
calls = calls,
|
|
|
|
responses = responses
|
|
|
|
)
|
|
|
|
|
|
|
|
def get_random_deck_code():
|
|
|
|
nonlocal cardcast_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)
|
|
|
|
|
|
|
|
return code
|
|
|
|
|
|
|
|
def remove_deck(code):
|
|
|
|
nonlocal decks
|
|
|
|
|
|
|
|
del decks[code]
|
|
|
|
|
|
|
|
def list_decks():
|
|
|
|
nonlocal decks
|
|
|
|
|
|
|
|
if len(decks) == 0:
|
|
|
|
send('No decks')
|
|
|
|
return
|
|
|
|
|
|
|
|
for deck in decks.values():
|
|
|
|
call_count = deck.call_count
|
|
|
|
calls_left = len(deck.calls)
|
|
|
|
calls = str(call_count) if call_count == calls_left else '%i/%i' % (calls_left, call_count)
|
|
|
|
|
|
|
|
response_count = deck.response_count
|
|
|
|
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)' % (
|
|
|
|
deck.name,
|
|
|
|
deck.code,
|
|
|
|
deck.author,
|
|
|
|
calls,
|
|
|
|
responses
|
|
|
|
))
|
|
|
|
|
2019-05-06 11:29:34 +00:00
|
|
|
def deck_add_handler(code):
|
|
|
|
nonlocal decks
|
|
|
|
|
|
|
|
if code not in decks:
|
|
|
|
errwrapper('Failure adding deck: %s (%%s)' % code, add_deck, code)
|
|
|
|
else:
|
|
|
|
send('Deck already added')
|
|
|
|
|
|
|
|
def deck_add_random_handler():
|
|
|
|
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)
|
|
|
|
|
2019-05-05 20:39:50 +00:00
|
|
|
def common_handler(event, args):
|
|
|
|
nonlocal players, decks, limit
|
|
|
|
|
|
|
|
if event == events.kill:
|
|
|
|
send('Stopping game')
|
|
|
|
return no_game
|
|
|
|
|
|
|
|
elif event == events.quit:
|
|
|
|
return quit
|
|
|
|
|
|
|
|
elif event == events.nick_change:
|
|
|
|
old, new = args
|
2019-05-06 10:48:04 +00:00
|
|
|
if old in players:
|
|
|
|
change_player_nick(old, new)
|
2019-05-05 20:39:50 +00:00
|
|
|
|
|
|
|
elif event == events.join:
|
|
|
|
nick, = args
|
|
|
|
if nick not in players:
|
|
|
|
add_player(nick)
|
|
|
|
send('%s has joined' % nick)
|
|
|
|
else:
|
|
|
|
send('%s has already joined' % nick)
|
|
|
|
|
|
|
|
elif event == events.leave:
|
|
|
|
nick, = args
|
|
|
|
if nick not in players:
|
|
|
|
send('No such player %s' % nick)
|
|
|
|
|
|
|
|
elif errwrapper('Could not remove player %s (%%s)' % nick, remove_player, nick) is not Error:
|
|
|
|
send('%s has left the game' % nick)
|
|
|
|
|
|
|
|
elif event == events.players:
|
|
|
|
list_players()
|
|
|
|
|
|
|
|
elif event == events.deck_add:
|
|
|
|
code, = args
|
2019-05-06 11:29:34 +00:00
|
|
|
deck_add_handler(code)
|
2019-05-05 20:39:50 +00:00
|
|
|
|
|
|
|
elif event == events.deck_add_random:
|
2019-05-06 11:29:34 +00:00
|
|
|
deck_add_random_handler()
|
2019-05-05 20:39:50 +00:00
|
|
|
|
|
|
|
elif event == events.deck_remove:
|
|
|
|
code, = args
|
|
|
|
if code in decks:
|
|
|
|
errwrapper('Failure removing deck %s (%%s)' % code, remove_deck, code)
|
|
|
|
else:
|
|
|
|
send('No such deck %s' % code)
|
|
|
|
|
|
|
|
elif event == events.deck_list:
|
|
|
|
list_decks()
|
|
|
|
|
|
|
|
elif event == events.limit:
|
|
|
|
if len(args) == 0:
|
|
|
|
limit_type = {limit_types.rounds: 'rounds', limit_types.points: 'points'}[limit.type]
|
|
|
|
send('Limit is %i %s' % (limit.number, limit_type))
|
|
|
|
|
|
|
|
else:
|
|
|
|
limit_type, number = args
|
|
|
|
limit = Limit(limit_type, number)
|
|
|
|
limit_type = {limit_types.rounds: 'rounds', limit_types.points: 'points'}[limit.type]
|
|
|
|
send('Limit set to %i %s' % (limit.number, limit_type))
|
|
|
|
|
2019-05-06 08:32:11 +00:00
|
|
|
elif event == events.card or event == events.cards:
|
|
|
|
# Ignore selecting and listing cards if it's not available
|
2019-05-05 20:39:50 +00:00
|
|
|
pass
|
|
|
|
|
2019-05-06 08:32:11 +00:00
|
|
|
elif event == events.ready or event == events.unready:
|
|
|
|
# Ignore readiness commands by default
|
2019-05-06 08:00:39 +00:00
|
|
|
pass
|
|
|
|
|
2019-05-05 20:39:50 +00:00
|
|
|
else:
|
|
|
|
error('Unknown event type: %s' % event)
|
|
|
|
|
|
|
|
def no_game():
|
|
|
|
nonlocal players, decks, limit, round_number, round_call_card, czar, card_choices
|
|
|
|
players = {}
|
|
|
|
decks = {}
|
|
|
|
limit = Limit(limit_types.points, 5)
|
|
|
|
round_number = 1
|
|
|
|
round_call_card = None
|
|
|
|
czar = None
|
|
|
|
card_choices = None
|
|
|
|
|
|
|
|
while True:
|
|
|
|
event, *args = get_event()
|
|
|
|
|
|
|
|
if event == events.status:
|
|
|
|
send('Idle')
|
|
|
|
|
|
|
|
elif event == events.start:
|
2019-05-06 11:29:34 +00:00
|
|
|
nick, *rest = args
|
|
|
|
|
2019-05-05 20:39:50 +00:00
|
|
|
add_player(nick)
|
|
|
|
|
|
|
|
send('%s started a game, !join to join!' % nick)
|
|
|
|
|
2019-05-06 11:29:34 +00:00
|
|
|
expert = False
|
|
|
|
if len(rest) == 0 or rest[0] == 'default':
|
|
|
|
send('Adding the default CAH deck (A5DCM)')
|
|
|
|
|
|
|
|
deck_add_handler('A5DCM')
|
|
|
|
|
|
|
|
elif rest[0] == 'offtopia':
|
|
|
|
send('Adding the default CAH deck (A5DCM), offtopia injoke deck (PXWKC), and three random decks')
|
|
|
|
|
|
|
|
deck_add_handler('A5DCM')
|
|
|
|
deck_add_handler('PXWKC')
|
|
|
|
|
|
|
|
deck_add_random_handler()
|
|
|
|
deck_add_random_handler()
|
|
|
|
deck_add_random_handler()
|
|
|
|
|
|
|
|
elif rest[0] == 'expert':
|
|
|
|
expert = True
|
|
|
|
|
|
|
|
else:
|
|
|
|
send('Unknown preset %s' % rest[0])
|
|
|
|
|
|
|
|
if not expert:
|
|
|
|
send('Once you are ready to start the game, everyone send !ready')
|
|
|
|
|
2019-05-05 20:39:50 +00:00
|
|
|
return game_setup
|
|
|
|
|
|
|
|
elif event == events.quit:
|
|
|
|
return quit
|
|
|
|
|
|
|
|
else:
|
|
|
|
send('Start with !start')
|
|
|
|
|
|
|
|
def game_setup():
|
|
|
|
nonlocal players
|
|
|
|
|
2019-05-06 08:32:11 +00:00
|
|
|
players_ready = set()
|
|
|
|
|
2019-05-05 20:39:50 +00:00
|
|
|
while True:
|
|
|
|
if len(players) == 0:
|
|
|
|
send('Lost all players, quiting game setup')
|
|
|
|
return no_game
|
|
|
|
|
2019-05-06 10:48:04 +00:00
|
|
|
players_unready = [i for i in players.values() if i not in players_ready]
|
|
|
|
if len(players_unready) == 0: break
|
|
|
|
|
2019-05-05 20:39:50 +00:00
|
|
|
event, *args = get_event()
|
|
|
|
|
|
|
|
if event == events.status:
|
2019-05-06 08:32:11 +00:00
|
|
|
if len(players_ready) == 0:
|
|
|
|
send('Game setup')
|
|
|
|
else:
|
|
|
|
send('Game setup, waiting for %s to be ready' % ', '.join(i.nick for i in players_unready))
|
2019-05-05 20:39:50 +00:00
|
|
|
|
|
|
|
elif event == events.start:
|
2019-05-06 08:32:11 +00:00
|
|
|
break
|
|
|
|
|
|
|
|
elif event == events.ready:
|
|
|
|
nick, = args
|
|
|
|
|
|
|
|
# Ignore if not in the game
|
|
|
|
if nick not in players:
|
|
|
|
continue
|
|
|
|
|
|
|
|
player = players[nick]
|
|
|
|
|
|
|
|
if player not in players_ready:
|
|
|
|
players_ready.add(player)
|
|
|
|
|
|
|
|
elif event == events.unready:
|
|
|
|
nick, = args
|
|
|
|
|
|
|
|
# Ignore if not in the game
|
|
|
|
if nick not in players:
|
|
|
|
continue
|
|
|
|
|
|
|
|
player = players[nick]
|
|
|
|
|
|
|
|
if player in players_ready:
|
|
|
|
players_ready.remove(player)
|
2019-05-05 20:39:50 +00:00
|
|
|
|
|
|
|
else:
|
|
|
|
r = common_handler(event, args)
|
|
|
|
if r is not None: return r
|
|
|
|
|
2019-05-06 08:32:11 +00:00
|
|
|
if len(players) < 2:
|
|
|
|
send('Not enough players')
|
|
|
|
return game_setup
|
|
|
|
else:
|
|
|
|
return setup_round
|
|
|
|
|
2019-05-05 20:39:50 +00:00
|
|
|
def total_calls():
|
|
|
|
nonlocal decks
|
|
|
|
|
|
|
|
return sum(len(deck.calls) for deck in decks.values())
|
|
|
|
|
|
|
|
def total_responses():
|
|
|
|
nonlocal decks
|
|
|
|
|
|
|
|
return sum(len(deck.responses) for deck in decks.values())
|
|
|
|
|
|
|
|
def deal_call():
|
|
|
|
nonlocal decks
|
|
|
|
|
|
|
|
deck_objs = list(decks.values())
|
|
|
|
while True:
|
|
|
|
deck = random.choice(deck_objs)
|
|
|
|
if len(deck.calls) != 0: break
|
|
|
|
|
|
|
|
# See comment about mutation in deal_responses()
|
|
|
|
index = random.randrange(len(deck.calls))
|
|
|
|
return deck.calls.pop(index)
|
|
|
|
|
|
|
|
def deal_responses(need_responses):
|
|
|
|
nonlocal decks
|
|
|
|
|
|
|
|
responses = []
|
|
|
|
deck_objs = list(decks.values())
|
|
|
|
for i in range(need_responses):
|
|
|
|
while True:
|
|
|
|
deck = random.choice(deck_objs)
|
|
|
|
if len(deck.responses) != 0: break
|
|
|
|
|
|
|
|
# We generate an index and pop that, since that makes
|
|
|
|
# it easier to mutate the list in place
|
|
|
|
index = random.randrange(len(deck.responses))
|
|
|
|
responses.append(deck.responses.pop(index))
|
|
|
|
|
|
|
|
# Shuffle the responses at the end, as otherwise the first
|
|
|
|
# cards are more likely to have come from small decks than
|
|
|
|
# the last cards
|
|
|
|
random.shuffle(responses)
|
|
|
|
|
|
|
|
return responses
|
|
|
|
|
|
|
|
def setup_round():
|
|
|
|
nonlocal players, round_call_card, czar, card_choices
|
|
|
|
|
2019-05-06 08:07:26 +00:00
|
|
|
# Select a czar randomly, if we need to
|
|
|
|
if czar not in players.values():
|
|
|
|
czar = random.choice(list(players.values()))
|
|
|
|
|
2019-05-05 20:39:50 +00:00
|
|
|
# Clear out previous round's cards
|
|
|
|
card_choices = {}
|
|
|
|
|
|
|
|
# Check that we have a call card for next round, should we need one
|
|
|
|
if round_call_card is None:
|
|
|
|
available_calls = total_calls()
|
|
|
|
if available_calls == 0:
|
|
|
|
send('Need a black card, none available. Add decks and continue with !start')
|
|
|
|
return game_setup
|
|
|
|
|
|
|
|
# Select call card for the next round
|
|
|
|
round_call_card = deal_call()
|
|
|
|
|
|
|
|
# Find out how many response cards we need
|
|
|
|
need_responses = 0
|
|
|
|
for player in players.values():
|
2019-05-06 08:07:26 +00:00
|
|
|
# Don't deal cards to the czar this round
|
|
|
|
if player is czar: continue
|
|
|
|
|
2019-05-05 20:39:50 +00:00
|
|
|
if len(player.hand) < 10:
|
|
|
|
need_responses += 10 - len(player.hand)
|
|
|
|
need_responses += player.hand.count(None)
|
|
|
|
|
|
|
|
# If we don't have enough, kick back to setup
|
|
|
|
available_responses = total_responses()
|
|
|
|
if available_responses < need_responses:
|
|
|
|
send('Need %i white cards, only %i available. Add decks and continue with !start' % (need_responses, available_responses))
|
|
|
|
return game_setup
|
|
|
|
|
|
|
|
# Get the cards
|
|
|
|
responses = deal_responses(need_responses)
|
|
|
|
|
|
|
|
# Add responses to players' inventories
|
|
|
|
for player in players.values():
|
2019-05-06 08:07:26 +00:00
|
|
|
# We skipped the czar in the counts, so skip here too
|
|
|
|
if player is czar: continue
|
|
|
|
|
2019-05-05 20:39:50 +00:00
|
|
|
while len(player.hand) < 10:
|
|
|
|
player.hand.append(responses.pop())
|
|
|
|
|
|
|
|
for index in range(10):
|
|
|
|
if player.hand[index] is None:
|
|
|
|
player.hand[index] = responses.pop()
|
|
|
|
|
|
|
|
return top_of_round
|
|
|
|
|
|
|
|
def sanitize(text):
|
|
|
|
return ''.join(i if ord(i) >= 32 and ord(i) != 127 else '^' + chr(ord(i) ^ 64) for i in text)
|
|
|
|
|
|
|
|
def send_cards(nick):
|
|
|
|
nonlocal players
|
|
|
|
|
|
|
|
cards = ' | '.join('%i: [%s]' % (index, sanitize(card)) for index, card in enumerate(players[nick].hand))
|
|
|
|
|
|
|
|
notice(nick, cards)
|
|
|
|
|
|
|
|
def combine_cards(call, responses):
|
|
|
|
combined = [sanitize(call[0])]
|
|
|
|
|
|
|
|
for i in range(len(call) - 1):
|
|
|
|
combined.append('[' + sanitize(responses[i]) + ']')
|
|
|
|
combined.append(sanitize(call[i + 1]))
|
|
|
|
|
|
|
|
return ''.join(combined)
|
|
|
|
|
|
|
|
def top_of_round():
|
|
|
|
nonlocal players, round_number, round_call_card, czar, card_choices
|
|
|
|
|
|
|
|
choosers = [i for i in players.values() if i is not czar]
|
|
|
|
|
|
|
|
send('Round %i. %s choose your cards' % (round_number, ', '.join(i.nick for i in choosers)))
|
|
|
|
send('[%s]' % '_'.join(sanitize(part) for part in round_call_card))
|
|
|
|
|
|
|
|
for nick in players:
|
|
|
|
if players[nick] is not czar:
|
|
|
|
send_cards(nick)
|
|
|
|
|
|
|
|
while len(choosers) > 0:
|
|
|
|
# Make sure that if a chooser leaves, they won't be waited on
|
|
|
|
choosers = [i for i in choosers if i in players.values()]
|
|
|
|
|
|
|
|
if len(players) < 2:
|
|
|
|
send('Not enough players to continue, quiting game')
|
|
|
|
|
|
|
|
if czar not in players.values():
|
|
|
|
send('Czar left the game, restarting round')
|
|
|
|
return setup_round
|
|
|
|
|
|
|
|
event, *args = get_event()
|
|
|
|
|
|
|
|
if event == events.status:
|
|
|
|
send('Waiting for %s to choose' % ', '.join(i.nick for i in choosers))
|
|
|
|
|
|
|
|
elif event == events.start:
|
|
|
|
send('Game already in progress')
|
|
|
|
|
|
|
|
elif event == events.card:
|
|
|
|
nick, choices = args
|
|
|
|
|
|
|
|
# Ignore those not in the game
|
|
|
|
if nick not in players:
|
|
|
|
continue
|
|
|
|
|
|
|
|
player = players[nick]
|
|
|
|
if player is czar:
|
|
|
|
notice(nick, 'Czar can\'t choose now')
|
|
|
|
continue
|
2019-05-06 08:04:32 +00:00
|
|
|
elif player not in choosers and player not in card_choices:
|
2019-05-05 20:39:50 +00:00
|
|
|
notice(nick, 'You\'ll get to choose next round')
|
|
|
|
continue
|
|
|
|
|
2019-05-06 11:57:04 +00:00
|
|
|
# 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) - 1:
|
|
|
|
notice(nick, 'Select %i card(s)' % (len(round_call_card) - 1))
|
|
|
|
continue
|
|
|
|
|
2019-05-05 20:39:50 +00:00
|
|
|
selected_cards = []
|
|
|
|
for choice in choices:
|
|
|
|
if 0 <= choice < len(player.hand):
|
2019-05-06 11:57:04 +00:00
|
|
|
if choice not in selected_cards:
|
|
|
|
selected_cards.append(choice)
|
|
|
|
else:
|
|
|
|
notice(nick, 'Can\'t play the same card twice')
|
|
|
|
break
|
2019-05-05 20:39:50 +00:00
|
|
|
else:
|
|
|
|
notice(nick, '%i not in your hand' % choice)
|
|
|
|
break
|
|
|
|
|
|
|
|
if len(selected_cards) != len(choices):
|
|
|
|
# Failed to use some choice
|
|
|
|
continue
|
|
|
|
|
|
|
|
card_choices[player] = selected_cards
|
2019-05-06 08:04:32 +00:00
|
|
|
if player in choosers:
|
|
|
|
choosers.remove(player)
|
2019-05-05 20:39:50 +00:00
|
|
|
notice(nick, combine_cards(round_call_card, [player.hand[i] for i in selected_cards]))
|
|
|
|
|
2019-05-06 08:00:39 +00:00
|
|
|
elif event == events.cards:
|
|
|
|
nick, = args
|
|
|
|
|
|
|
|
if nick not in players:
|
|
|
|
# Ignore those not in the game
|
|
|
|
continue
|
|
|
|
|
|
|
|
player = players[nick]
|
|
|
|
|
2019-05-06 08:04:32 +00:00
|
|
|
if player in choosers or player in card_choices:
|
2019-05-06 08:00:39 +00:00
|
|
|
send_cards(nick)
|
|
|
|
else:
|
|
|
|
notice(nick, 'You can\'t choose now')
|
|
|
|
|
2019-05-05 20:39:50 +00:00
|
|
|
else:
|
|
|
|
r = common_handler(event, args)
|
|
|
|
if r is not None: return r
|
|
|
|
|
|
|
|
return bottom_of_round
|
|
|
|
|
|
|
|
def bottom_of_round():
|
|
|
|
nonlocal players, round_call_card, czar, card_choices
|
|
|
|
|
|
|
|
send('Everyone has chosen. %s, now\'s your time to choose.' % czar.nick)
|
|
|
|
|
|
|
|
# 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, [player.hand[i] for i in card_choices[player]])))
|
|
|
|
|
|
|
|
while True:
|
|
|
|
if len(players) < 2:
|
|
|
|
send('Not enough players to continue, quiting game')
|
|
|
|
|
|
|
|
if czar not in players.values():
|
|
|
|
send('Czar left the game, restarting round')
|
|
|
|
return setup_round
|
|
|
|
|
|
|
|
event, *args = get_event()
|
|
|
|
|
|
|
|
if event == events.status:
|
|
|
|
send('Waiting for czar %s to choose' % czar.nick)
|
|
|
|
|
|
|
|
elif event == events.start:
|
|
|
|
send('Game already in progress')
|
|
|
|
|
|
|
|
elif event == events.card:
|
|
|
|
nick, choices = args
|
|
|
|
|
|
|
|
# Ignore those not in the game
|
|
|
|
if nick not in players:
|
|
|
|
continue
|
|
|
|
|
|
|
|
player = players[nick]
|
|
|
|
if player is not czar:
|
|
|
|
notice(nick, 'Only the czar can choose now')
|
|
|
|
continue
|
|
|
|
|
|
|
|
if len(choices) != 1:
|
|
|
|
notice(nick, 'Select one choice')
|
|
|
|
continue
|
|
|
|
choice = choices[0]
|
|
|
|
|
|
|
|
if 0 <= choice < len(choosers):
|
|
|
|
player = choosers[choice]
|
|
|
|
player.points += 1
|
|
|
|
|
|
|
|
# Winner is Czar semantics
|
|
|
|
czar = player
|
|
|
|
|
|
|
|
send('The winner is %s with: %s' % (player.nick, combine_cards(round_call_card, [player.hand[i] for i in card_choices[player]])))
|
|
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
else:
|
|
|
|
notice(nick, '%i not in range' % choice)
|
|
|
|
|
|
|
|
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)))
|
|
|
|
else:
|
|
|
|
points.append('%s: %i' % (player.nick, player.points))
|
|
|
|
|
|
|
|
send('Points: %s' % ' | '.join(points))
|
|
|
|
|
|
|
|
return teardown_round
|
|
|
|
|
|
|
|
def teardown_round():
|
|
|
|
nonlocal players, limit, round_number, round_call_card, card_choices
|
|
|
|
|
|
|
|
if limit.type == limit_types.rounds:
|
|
|
|
if round_number >= limit.number:
|
|
|
|
return end_game
|
|
|
|
|
|
|
|
elif limit.type == limit_types.points:
|
|
|
|
if max(i.points for i in players.values()) >= limit.number:
|
|
|
|
return end_game
|
|
|
|
|
|
|
|
# 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
|
|
|
|
|
|
|
|
# Increase the number of the round and clear the call card
|
|
|
|
# These are not done in setup_round() since we might want to
|
|
|
|
# restart a round in case the czar leaves
|
|
|
|
round_number += 1
|
|
|
|
round_call_card = None
|
|
|
|
|
|
|
|
return setup_round
|
|
|
|
|
|
|
|
|
|
|
|
def end_game():
|
|
|
|
nonlocal players
|
|
|
|
|
|
|
|
max_score = max(i.points for i in players.values())
|
|
|
|
|
|
|
|
winners = [i for i in players.values() if i.points == max_score]
|
|
|
|
|
|
|
|
send('We have a winner! %s' % ', '.join(i.nick for i in winners))
|
|
|
|
|
|
|
|
return no_game
|
|
|
|
|
|
|
|
def quit():
|
|
|
|
pass
|
|
|
|
|
|
|
|
players = None
|
|
|
|
decks = None
|
|
|
|
limit = None
|
|
|
|
|
|
|
|
round_number = None
|
|
|
|
round_call_card = None
|
|
|
|
czar = None
|
|
|
|
card_choices = None
|
|
|
|
|
|
|
|
cardcast_deck_count = None
|
|
|
|
|
|
|
|
state = no_game
|
|
|
|
while state != quit:
|
|
|
|
state = state()
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
2019-05-06 09:41:12 +00:00
|
|
|
game(send, notice, get_event)
|