Skip to content

Commit

Permalink
Working version
Browse files Browse the repository at this point in the history
  • Loading branch information
arvoelke committed Feb 15, 2014
0 parents commit 5a937d2
Show file tree
Hide file tree
Showing 761 changed files with 114,360 additions and 0 deletions.
1 change: 1 addition & 0 deletions LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/ or send a letter to Creative Commons, 444 Castro Street, Suite 900, Mountain View, California, 94041, USA.
Empty file added README.md
Empty file.
32 changes: 32 additions & 0 deletions chat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import time

from utils import hash_obj

class ChatLog(object):

MAX_HISTORY = 1024

def __init__(self, max_history=MAX_HISTORY):
self.log = [None]*max_history
self.size = max_history
self.i = 0

def add(self, name, msg):
t = time.time()
self.log[self.i] = {
'user' : name,
'mid' : hash_obj(t, add_random=True),
'msg' : msg,
't' : t
}
self.i = (self.i + 1) % self.size

def dump_until(self, t):
j = (self.i - 1) % self.size
dump = []
while self.log[j] and self.log[j]['t'] > t:
dump.append(self.log[j])
j = (j - 1) % self.size
if j == self.i: # infinity loop
break
return list(reversed(dump))
43 changes: 43 additions & 0 deletions codes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
class APIError(Exception):

def __init__(self, code, data=None):
self.code = code
self.data = data

def __str__(self):
tag = ': "%s"' % self.data if self.data is not None else ''
return '%s%s' % (self.code, tag)

class Codes(object):

JOIN_FULL_ROOM = 0
JOIN_BANNED = 1
NOT_ENOUGH_PLAYERS = 2

KICK_BAD_STATE = 3
KICK_UNKNOWN_USER = 4

BEGIN_BAD_STATE = 5

CLUE_BAD_STATE = 6
CLUE_NOT_TURN = 7
CLUE_TOO_LONG = 8
CLUE_TOO_SHORT = 9

PLAY_BAD_STATE = 10
PLAY_NOT_TURN = 11
PLAY_UNKNOWN_USER = 12

VOTE_BAD_STATE = 13
VOTE_NOT_TURN = 14
VOTE_UNKNOWN_USER = 15
VOTE_INVALID = 16

NOT_HAVE_CARD = 17

DECK_TOO_SMALL = 18
COLOUR_TAKEN = 19

NOT_AN_INTEGER = 20
ILLEGAL_RANGE = 21
NOT_A_COLOUR = 22
272 changes: 272 additions & 0 deletions core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
from collections import defaultdict
import random
import time

from codes import APIError, Codes
from deck import Deck

from utils import INFINITY


class Limits(object):

MIN_NAME = 3
MAX_NAME = 20

MIN_PLAYERS = 3
MAX_PLAYERS = 6

MIN_SCORE = 1
MAX_SCORE = INFINITY

MIN_CLUE_LENGTH = 5
MAX_CLUE_LENGTH = 100000

MAX_MESSAGE = 1024


class States(object):

BEGIN = 0
CLUE = 1
PLAY = 2
VOTE = 3
END = 4


class StringClue(object):

def __init__(self, clue):
self.clue = clue

def __str__(self):
return self.clue.encode('utf-8')

def __len__(self):
return len(self.clue)


class Player(object):
"""Manages a user with respect to a particular game (hand/score)."""

def __init__(self, user):
self.user = user
self.hand = []
self.score = 0

def deal(self, card):
if card is not None:
self.hand.append(card)

def remove_card(self, card):
if not card in self.hand:
raise APIError(Codes.NOT_HAVE_CARD)
self.hand.remove(card)


class Round(object):
"""Manages all player states across one turn (state.VOTE -> state.VOTE)."""

def __init__(self, players, clue, clue_maker):
self.players = players
self.clue = clue
self.clue_maker = clue_maker
self.cards = []
self.user_to_card = dict()
self.user_to_vote = dict()
self.card_to_voted_users = defaultdict(list) # ignores self-votes
self.scores = defaultdict(int)

# Determine the random card order ahead of time
self.card_index_to_user = players.keys()
random.shuffle(self.card_index_to_user)

@classmethod
def make_zeroeth(cls):
# Use a non-empty list of players so that has_everyone_* returns False.
return cls({None: None}, None, None)

def play_card(self, user, card):
self.players[user].remove_card(card)
self.user_to_card[user] = card

def cast_vote(self, user, card):
self.user_to_vote[user] = card
if card != self.user_to_card[user]:
self.card_to_voted_users[card].append(user)

def has_card(self, card):
return card in self.user_to_card.itervalues()

def has_played(self, user):
return self.user_to_card.has_key(user)

def has_voted(self, user):
return self.user_to_vote.has_key(user)

def has_everyone_played(self):
return len(self.user_to_card) == len(self.players)

def has_everyone_voted(self):
return len(self.user_to_vote) == len(self.players)

def get_cards(self):
return [self.user_to_card[user] for user in self.card_index_to_user]

def score(self, user, score):
self.players[user].score += score
self.scores[user] += score


class Game(object):

CARDS_PER_PERSON = 6
SCORE_FOR_TRICK = 1
SCORE_FOR_LOSS = 2
SCORE_FOR_CORRECT = 3

def __init__(self, host, card_sets, password, name, max_players,
max_score, max_clue_length):
self.host = host
self.deck = Deck(card_sets)
self.password = password
self.name = name
self.max_players = max_players
self.max_score = max_score
self.max_clue_length = max_clue_length

self.players = {}
self.order = []
self.colours = dict()
self.perma_banned = set()
self.init_game()
self.ping()

def ping(self):
self.last_active = time.time()

def add_player(self, user, colour):
if self.state != States.BEGIN:
raise APIError(Codes.BEGIN_BAD_STATE)
if len(self.players) >= self.max_players:
raise APIError(Codes.JOIN_FULL_ROOM)
if colour in self.colours.values():
raise APIError(Codes.COLOUR_TAKEN)
if user in self.perma_banned:
raise APIError(Codes.JOIN_BANNED)
if not user in self.players: # idempotent
self.players[user] = Player(user)
self.order.append(user)
self.colours[user] = colour # alow colour changing

def kick_player(self, user, is_permanent=False):
if not user in self.players:
raise APIError(Codes.KICK_UNKNOWN_USER)
if len(self.players) <= Limits.MIN_PLAYERS and self.state != States.BEGIN:
raise APIError(Codes.NOT_ENOUGH_PLAYERS)
if self.state in (States.PLAY, States.VOTE):
raise APIError(Codes.KICK_BAD_STATE)
self.players.pop(user)
turn = self.order.index(user)
self.order.remove(user)
# readjust turn in case game is currently running
if self.turn > turn:
self.turn -= 1
self.turn %= len(self.players)
if is_permanent:
self.perma_banned.add(user)

def init_game(self):
self.state = States.BEGIN
self.round = Round.make_zeroeth()
self.turn = 0
self.deck.reset()

def clue_maker(self):
return self.order[self.turn]

def get_card(self, cid):
return self.deck.get_card(cid)

def start_game(self):
if self.state != States.BEGIN:
raise APIError(Codes.BEGIN_BAD_STATE)
if len(self.players) < Limits.MIN_PLAYERS:
raise APIError(Codes.NOT_ENOUGH_PLAYERS)
random.shuffle(self.order)
for user in self.players:
for i in range(self.CARDS_PER_PERSON):
card = self.deck.deal()
if card is None:
self.init_game() # rewind the dealing
raise APIError(Codes.DECK_TOO_SMALL)
self.players[user].deal(card)
self.state = States.CLUE
self.ping()

def create_clue(self, user, clue, card):
if self.state != States.CLUE:
raise APIError(Codes.CLUE_BAD_STATE)
if user != self.clue_maker():
raise APIError(Codes.CLUE_NOT_TURN)
if len(clue) < Limits.MIN_CLUE_LENGTH:
raise APIError(Codes.CLUE_TOO_SHORT)
if len(clue) > self.max_clue_length:
raise APIError(Codes.CLUE_TOO_LONG)
self.round = Round(self.players, clue, self.clue_maker())
self.round.play_card(user, card)
self.players[user].deal(self.deck.deal())
self.state = States.PLAY
self.ping()

def play_card(self, user, card):
if self.state != States.PLAY:
raise APIError(Codes.PLAY_BAD_STATE)
if user == self.clue_maker():
raise APIError(Codes.PLAY_NOT_TURN)
if not user in self.players:
raise APIError(Codes.PLAY_UNKNOWN_USER)
self.round.play_card(user, card)
self.players[user].deal(self.deck.deal())
if self.round.has_everyone_played():
self.round.cast_vote(self.clue_maker(),
self.round.user_to_card[self.clue_maker()])
self.state = States.VOTE
self.ping()

def cast_vote(self, user, card):
if self.state != States.VOTE:
raise APIError(Codes.VOTE_BAD_STATE)
if user == self.clue_maker():
raise APIError(Codes.VOTE_NOT_TURN)
if not user in self.players:
raise APIError(Codes.VOTE_UNKNOWN_USER)
if not self.round.has_card(card):
raise APIError(Codes.VOTE_INVALID)
self.round.cast_vote(user, card)
if self.round.has_everyone_voted():
# Remember this in case someone gets kicked
self._do_scoring()
self.state = States.CLUE
self.turn = (self.turn + 1) % len(self.players)
if self.deck.is_empty():
self.state = States.END
for p in self.players.itervalues():
if p.score >= self.max_score:
self.state = States.END
self.ping()

def _do_scoring(self):
for user in self.players:
v = self.round.card_to_voted_users[self.round.user_to_card[user]]
if user == self.clue_maker():
if len(v) == 0 or len(v) == len(self.players) - 1:
for u in self.players:
if u != user:
self.round.score(u, self.SCORE_FOR_LOSS)
else:
self.round.score(user, self.SCORE_FOR_CORRECT)
for u in v:
self.round.score(u, self.SCORE_FOR_CORRECT)
else:
self.round.score(user, self.SCORE_FOR_TRICK*len(v))
Loading

0 comments on commit 5a937d2

Please sign in to comment.