class ProtoPlayer(db.Model): """This class will link player objects to a user object, without the player object having to know anything about Users. This is an extra level of indirection, but without it, player objects, and therefore the whole game engine would need to know about Users""" __tablename__ = "proto_players" id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey(User.id)) user = db.relationship("User") player_id = db.Column(db.Integer, db.ForeignKey(Player.id)) player = db.relationship("Player", uselist=False) proto_game_id = db.Column(db.Integer, db.ForeignKey(ProtoGame.id), nullable=False) def __init__(self, u): self.user_id = u.id # self.proto_game_id = pg_id def __repr__(self): return "ProtoPlayer(user_id=%s)" % self.user_id
class User(db.Model): __tablename__ = "users" id = db.Column(db.Integer, primary_key=True) nickname = db.Column(db.String(64), unique=True) email = db.Column(db.String(120), unique=True) proto_players = db.relationship("ProtoPlayer") def is_authenticated(self): return True def is_active(self): return True def is_anonymous(self): return False def get_id(self): return str(self.id) def __repr__(self): return '<User %r>' % (self.nickname)
class BuildingDeck(db.Model): __tablename__ = "buildingdecks" id = db.Column(db.Integer, primary_key=True) game_state_id = db.Column(db.Integer, db.ForeignKey("gamestates.id")) template = db.Column(db.String) cards = db.Column(MutableList.as_mutable(JSONEncoded)) card_map = None full_cards = None def __init__(self, template): """ template is a file name for now. Might be a database id later """ self.template = template self._construct_card_map() self.cards = list(self.card_map.keys()) def _construct_card_map(self): if self.full_cards is None: self.full_cards = self._create_deck_from_csv(self.template) if self.card_map is None: self.card_map = dict([(c.id, c) for c in self.full_cards]) def card_for_id(self, card_id): if self.card_map is None: self._construct_card_map() return self.card_map[card_id] def _create_deck_from_csv(self, filename): def card_from_line(line): return Building(int(line[0]), line[1], int(line[2]), line[3]) with open(filename, 'r') as myfile: lines = csv.reader(myfile) return [card_from_line(line) for line in lines]
class Role(db.Model): __tablename__ = 'roles' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String) num = db.Column(db.Integer)
class ProtoGame(db.Model): WAITING_FOR_PLAYERS = 1 WAITING_TO_START = 2 PLAYING = 3 FINISHED = 4 _status_descriptions = {(WAITING_FOR_PLAYERS, "Waiting For Players"), (WAITING_TO_START, "Waiting To Start"), (FINISHED, "Finished"), (PLAYING, "In Progress")} __tablename__ = "proto_games" id = db.Column(db.Integer, primary_key=True) status = db.Column(db.String) num_players = db.Column(db.Integer) real_game = db.Column(db.Integer, db.ForeignKey(GameState.id)) proto_players = db.relationship("ProtoPlayer") owner_id = db.Column(db.Integer, db.ForeignKey(User.id)) def __init__(self, n, owner): self.owner_id = owner.id self.num_players = n pp = ProtoPlayer(owner) self.proto_players = [pp] self.status = self.WAITING_FOR_PLAYERS def can_user_join(self, user): if self.is_full(): return False #TODO: Should this be done via a sql query or a for loop like this? for pp in self.proto_players: if user.id == pp.user_id: return False return True def join_user(self, user): if self.can_user_join(user): pp = ProtoPlayer(user) self.proto_players.append(pp) if self.is_full(): self.status = self.WAITING_TO_START logging.info( "user %s has been added as protoplayer %s to protogame %s", user, pp, self) else: logging.warn("user %s can't join protogame %s but was trying to.", user, self) raise FatalPlyusError( "Can't join this game. maybe because game is full?") def can_user_start(self, user): if self.is_full() and user.id == self.owner_id: return True return False def is_full(self): return len(self.proto_players) >= self.num_players def get_status_desc(self): return self._status_descriptions[self.status] def num_joined_players(self): return len(self.proto_players)
class GameState(db.Model): __tablename__ = 'gamestates' id = db.Column(db.Integer, primary_key=True) stage = db.Column(db.String) step = db.Column(db.String) phase = db.Column(db.String) players = db.relationship("Player", order_by="Player.position") base_seed = db.Column(db.Integer) building_card_deck = db.relationship(BuildingDeck, uselist=False) num_players = db.Column(db.Integer) round_num = db.Column(db.Integer) cur_player_index = db.Column(db.Integer) player_with_crown_token = db.Column(db.Integer) winner = db.Column(db.Integer) round = db.relationship("Round", uselist=False, backref="game_state") created_by = db.relationship("Player", uselist=False) def __init__(self, base_seed, created_by, num_players, deck_template=None): self.stage = Stage.PRE_GAME self.step = Step.NO_STEP self.base_seed = base_seed if deck_template is None: deck_template = 'decks/deck_test_60.csv' self.building_card_deck = BuildingDeck(deck_template) self.round_num = -1 self.num_players = num_players self.players = [created_by] self.winner = None self.cur_player_index = 0 def add_player(self, player): if self.stage != Stage.PRE_GAME: raise FatalPlyusError("Can't add players except in stage PRE_GAME") if len(self.players) >= self.num_players: raise FatalPlyusError("Game already has %s players." % self.num_players) self.players.append(player) def start_game(self): if len(self.players) < self.num_players: logging.error( "trying to start game %s with only %s players, but expecting %s players" % (self.id, len(self.players), self.num_players)) raise FatalPlyusError("Not all players have joined yet") if self.stage != Stage.PRE_GAME: raise FatalPlyusError( "Can't start game except from stage PRE_GAME (in stage %s now)" % (self.stage, )) rand_gen = self.get_random_gen() rand_gen.shuffle(self.players) rand_gen.shuffle(self.building_card_deck.cards) for i, p in enumerate(self.players): p.set_position(i) #TODO: replace magic number with actual number of cards in starting hand. p.buildings_in_hand.extend( util.draw_n(self.building_card_deck.cards, 4)) self.round = Round(self) self.player_with_crown_token = 0 #this player gets to go first when picking a role self.stage = Stage.PLAYING self.start_new_round() def to_dict_for_public(self): d = {} fields_to_copy = [ 'round_num', 'player_with_crown_token', 'stage', 'phase', 'step', 'cur_player_index', 'num_players', 'winner' ] for k in fields_to_copy: if k in self.__dict__: d[k] = self.__dict__[k] if self.id: d['id'] = self.id d['players'] = [ p.to_dict_for_public(self.building_card_deck) for p in self.players ] d['building_card_deck_len'] = len(self.building_card_deck.cards) r = {} if self.round: r = self.round.to_dict_for_public() d['round'] = r return d def to_dict_for_player(self, player): d = self.to_dict_for_public() r = self.round.to_dict_for_public() logging.debug("in todictforplayer %s" % (player)) if player.position == self.cur_player_index and self.phase == Phase.PICK_ROLES: logging.debug("assigning role_draw_pile now") r['role_draw_pile'] = self.round.role_draw_pile else: logging.debug(" %s not equal %s" % (player.position, self.cur_player_index)) logging.debug("or %s not equal %s" % (self.phase, Phase.PICK_ROLES)) d['round'] = r return d def advance_cur_player_index(self): self.cur_player_index = (self.cur_player_index + 1) % self.num_players def __repr__(self): return ("phase=%s, step=%s, cur_player_index: %s, round=%s" % (self.phase, self.step, self.cur_player_index, self.round)) def finish_round(self): logging.debug("made it to finish_round with stage as %s" % self.stage) #TODO: announce dead player if any # if we are in the end_game (someone built 8 things) # and we are done with the round, game is over if self.stage == Stage.END_GAME: self.do_game_over_calculations() self.stage = Stage.GAME_OVER logging.info("after end game check, stage is %s" % self.stage) #if the konig was around, give that player the crown m = self.round.gen_role_to_plyr_map() if 4 in m: #TODO: announce which player now has the crown self.player_with_crown_token = m[4] def start_new_round(self): logging.info("starting new round") self.round = Round(self) self.cur_player_index = self.player_with_crown_token self.phase = Phase.PICK_ROLES self.step = Step.PICK_ROLE self.round_num += 1 for p in self.players: p.revealed_roles = [] def get_cur_plyr(self): return self.players[self.cur_player_index] # this should only be called at the end of a round, after # all players have taken their turn and we are in the # END_GAME stage def do_game_over_calculations(self): rankings = [] for p in self.players: basic_points = 0 bonus_points = 0 colors = {} buildings = map(self.building_card_deck.card_for_id, p.buildings_on_table) for d in buildings: basic_points += d.points colors[d.color] = True if len(colors.keys()) == 5: p.rainbow_bonus = True bonus_points += 3 if len(p.buildings_on_table) >= 8: bonus_points += 2 if p.first_to_eight_buildings: bonus_points += 2 p.points = basic_points + bonus_points p.ranking = (p.points, p.gold, basic_points) #TODO: implement official tiebreaker ranked_players = sorted(self.players, key=lambda p: p.ranking, reverse=True) self.winner = ranked_players[0].name def get_random_gen(self): cur_plyr = self.get_cur_plyr() seed = str(self.base_seed) + str(self.round_num * 117) + str( self.cur_player_index * 13) seed = seed + self.step str(cur_plyr.name) + str(cur_plyr.buildings_in_hand) return random.Random(seed)
class Round(db.Model): __tablename__ = 'rounds' id = db.Column(db.Integer, primary_key=True) game_state_id = db.Column(db.Integer, db.ForeignKey('gamestates.id')) has_used_power = db.Column(MutableList.as_mutable(JSONEncoded)) has_taken_bonus = db.Column(MutableList.as_mutable(JSONEncoded)) num_seven_builds_left = db.Column(db.Integer) dead_role = db.Column(db.Integer) mugged_role = db.Column(db.Integer) role_draw_pile = db.Column(MutableList.as_mutable(JSONEncoded)) face_up_roles = db.Column(MutableList.as_mutable(JSONEncoded)) face_down_roles = db.Column(MutableList.as_mutable(JSONEncoded)) def gen_plyr_to_role_map(self): m = {} for p in self.game_state.players: l = [] l.extend(p.roles) m[p.position] = l return m def gen_role_to_plyr_map(self): m = {} for p in self.game_state.players: for r in p.roles: m[r] = p.position return m def __init__(self, game_state): self.game_state = game_state self.has_used_power = [] self.has_taken_bonus = [] self.num_seven_builds_left = 3 self.dead_role = None self.mugged_role = None players = game_state.players for p in players: self.has_used_power.append(False) self.has_taken_bonus.append(False) p.roles = [] p.cur_role = None face_up_num, face_down_num = self.role_setup_for_n_players(len(players)) self.role_draw_pile = [1, 2, 3, 4, 5, 6, 7, 8] game_state.get_random_gen().shuffle(self.role_draw_pile) self.face_up_roles = util.draw_n(self.role_draw_pile, face_up_num) self.face_down_roles = util.draw_n(self.role_draw_pile, face_down_num) def mark_role_picked(self, role, player_id): if player_id not in range(0, self.game_state.num_players): raise FatalPlyusError("bad player-id") logging.info("marking %s as picked by %s" % (role, player_id)) # logging.info("before appending, plyr_to_role_map is %s" % (repr(self.plyr_to_role_map))) # self.plyr_to_role_map[player_id].append(role) # logging.info("after appending, plyr_to_role_map is %s" % (repr(self.plyr_to_role_map))) # self.role_to_plyr_map[role] = player_id self.role_draw_pile.remove(role) def role_setup_for_n_players(self, n): # return (num_face_up, num_face_down) if n == 2: return (0, 1) if n == 3: return (0, 1) if n == 4: return (2, 1) if n == 5: return (1, 1) if n == 6: return (0, 1) raise FatalPlyusError("Wrong number of players: %s" % n) def done_picking(self): #if no player still needs to choose, we're done picking # easy case - if everyone has picked one role we're done roles_picked = util.flatten(self.gen_plyr_to_role_map().values()) logging.info("roles_picked is %s" % roles_picked) num_roles_picked = len(roles_picked) num_players = len(self.game_state.players) num_roles_per_player = 1 if num_players <= 3: num_roles_per_player = 2 return num_roles_picked >= num_players * num_roles_per_player def __repr__(self): return "Round(draw:%s, up:%s, down:%s\n plyr_to_role:%s role_to_plyr:%s" % (self.role_draw_pile, self.face_up_roles, self.face_down_roles, self.gen_plyr_to_role_map(), self.gen_role_to_plyr_map()) def to_dict_for_public(self): d = {} fields_to_copy = ['face_up_roles', 'has_used_power', 'has_taken_bonus', 'num_seven_builds_left'] for k in fields_to_copy: d[k] = self.__dict__[k] return d
class Player(db.Model): __tablename__ = 'players' id = db.Column(db.Integer, primary_key=True) gamestate_id = db.Column(db.Integer, db.ForeignKey(GameState.id), nullable=False) name = db.Column(db.String) position = db.Column(db.Integer) gold = db.Column(db.Integer) buildings_on_table = db.Column(MutableList.as_mutable(JSONEncoded)) buildings_in_hand = db.Column(MutableList.as_mutable(JSONEncoded)) buildings_buffer = db.Column(MutableList.as_mutable(JSONEncoded)) cur_role = db.Column(db.Integer) roles = db.Column(MutableList.as_mutable(JSONEncoded)) revealed_roles = db.Column(MutableList.as_mutable(JSONEncoded)) rainbow_bonus = db.Column(db.Boolean) first_to_eight_buildings = db.Column(db.Boolean) points = db.Column(db.Integer) def __init__(self, n): self.name = n self.position = None self.gold = 2 self.buildings_on_table = [] self.buildings_in_hand = [] self.buildings_buffer = [] self.cur_role = None self.roles = [] self.revealed_roles = [] self.rainbow_bonus = False self.first_to_eight_buildings = False self.points = None #current player chooses to get gold def take_gold(self): self.gold += 2 #when current player chooses to draw cards def take_cards(self, cards): if len(cards) < 2: #TODO: figure out and implement rule on reshuffling building cards raise FatalPlyusError("Building deck is out of cards.") self.buildings_buffer = draw_n(cards, 2) def set_position(self, i): self.position = i def __repr__(self): return "Player(name=%s, pos=%s, cur_role= %s, roles=%s, gold=%s, hand=%r, dists=%s)" % ( self.name, self.position, self.cur_role, self.roles, self.gold, self.buildings_in_hand, self.buildings_on_table) def to_dict_for_public(self, deck): d = {} fields_to_copy = [ 'name', 'position', 'gold', 'points', 'revealed_roles' ] for k in fields_to_copy: d[k] = self.__dict__[k] d['buildings_on_table'] = [ deck.card_for_id(i) for i in self.buildings_on_table ] d['num_cards_in_hand'] = len(self.buildings_in_hand) return d def to_dict_for_private(self, deck): d = self.to_dict_for_public(deck) fields_to_copy = ['cur_role', 'roles'] for k in fields_to_copy: d[k] = self.__dict__[k] d['buildings_in_hand'] = [ deck.card_for_id(i) for i in self.buildings_in_hand ] d['buildings_buffer'] = [ deck.card_for_id(i) for i in self.buildings_buffer ] return d