class Card(db.Model):
    __tablename__ = 'cards'

    id = db.Column(db.Integer, primary_key=True)

    type = db.Column(db.Enum('white', 'black', name='card_type'))
    text = db.Column(db.String)

    deleted_at = db.Column(db.DateTime)

    api_client_id = db.Column(db.Integer,
                              db.ForeignKey('api_clients.id',
                                            use_alter=True,
                                            ondelete='CASCADE',
                                            name='api_client_cards'),
                              nullable=True)

    def serialize(self):
        return {'type': self.type, 'text': self.text, 'id': self.id}
Exemple #2
0
class ApiClient(db.Model):
    __tablename__ = 'api_clients'

    id = db.Column(db.Integer, primary_key=True)

    game_id = db.Column(db.Integer,
                        db.ForeignKey('games.id', ondelete="SET NULL"),
                        nullable=True)
    game = db.relationship("Game", uselist=False)

    shared_secret = db.Column(db.String(length=88))

    cards = db.relationship("Card",
                            lazy='dynamic',
                            foreign_keys='Card.api_client_id',
                            backref="api_client",
                            cascade="delete",
                            order_by='Card.id')

    def cards_info(self):
        return [
            c.serialize() for c in self.cards.filter(Card.deleted_at == None)
        ]
class Player(db.Model):
    __tablename__ = 'players'

    id = db.Column(db.String, primary_key=True)
    order_id = db.Column(db.Integer, db.Sequence('order_seq'))

    left_at = db.Column(db.DateTime)

    game_id = db.Column(
        db.Integer,
        db.ForeignKey('games.id',
                      use_alter=True,
                      ondelete='CASCADE',
                      name='player_game'))
    cards = db.relationship("Card", secondary=PlayerCard, order_by=Card.id)

    played_card_id = db.Column(db.Integer,
                               db.ForeignKey('cards.id'),
                               nullable=True)
    played_card = db.relationship("Card", uselist=False)

    score = db.Column(db.Integer, default=0)

    def cards_text(self):
        return [c.text for c in self.cards]

    def played_card_info(self):
        if self.played_card is not None and self.played_card in self.cards:
            return {
                'id': self.cards.index(self.played_card),
                'text': self.played_card.text
            }

    def serialize(self, **kwargs):
        return dict(
            {
                'id': self.id,
                'cards': self.cards_text(),
                'played_card': self.played_card_info(),
                'score': self.score
            }, **kwargs)

    def pick_cards(self):
        cards = self.game.pick_white_cards(MAX_CARD_COUNT - len(self.cards))
        for card in cards:
            self.cards.append(card)
from hah import db
from .card import Card

PlayerCard = db.Table(
    'player_cards', db.Model.metadata,
    db.Column('player_id', db.String,
              db.ForeignKey('players.id', ondelete='CASCADE')),
    db.Column('card_id', db.Integer, db.ForeignKey('cards.id')))

MAX_CARD_COUNT = 10


class Player(db.Model):
    __tablename__ = 'players'

    id = db.Column(db.String, primary_key=True)
    order_id = db.Column(db.Integer, db.Sequence('order_seq'))

    left_at = db.Column(db.DateTime)

    game_id = db.Column(
        db.Integer,
        db.ForeignKey('games.id',
                      use_alter=True,
                      ondelete='CASCADE',
                      name='player_game'))
    cards = db.relationship("Card", secondary=PlayerCard, order_by=Card.id)

    played_card_id = db.Column(db.Integer,
                               db.ForeignKey('cards.id'),
                               nullable=True)
class Game(db.Model):
    __tablename__ = 'games'

    id =        db.Column(db.Integer, primary_key=True)

    turn_started_at = db.Column(db.DateTime)
    turn_locked_at = db.Column(db.DateTime)
    game_stopped_at = db.Column(db.DateTime)

    players =   db.relationship(
            "Player",
            lazy='dynamic',
            foreign_keys='Player.game_id',
            backref="game",
            cascade="delete",
            order_by='Player.order_id')

    active_player_id = db.Column(
            db.String,
            db.ForeignKey('players.id'),
            nullable=True)
    active_player = db.relationship(
            "Player",
            uselist=False,
            foreign_keys=[active_player_id],
            post_update=True)

    turn =	    db.Column(db.Integer, default=0)
    black_cards_picked = db.Column(db.Integer, default=0)
    white_cards_picked = db.Column(db.Integer, default=0)
    deck_size =     db.Column(db.Integer, nullable=False)
    deck_seed =     db.Column(db.Integer, nullable=False)

    def __init__(self, **kw):
        self.deck_size = Card.query.count()
        self.deck_seed = struct.unpack('i', os.urandom(4))[0]
        super().__init__(**kw)

    def players_info(self):
        return [p.serialize() for p in self.players.all()
        ]

    def active_card(self):
        if self.active_player and self.active_player.played_card:
            return self.active_player.played_card.text

    def playing_players(self):
        players = [p for p in self.players.all() \
                if p != self.active_player and p.played_card]
        random.seed(self.deck_seed + self.turn)
        random.shuffle(players)
        return players

    def played_cards(self):
        return [p.played_card.text for p in self.playing_players()]

    def lock_turn(self):
        self.turn_locked_at = datetime.utcnow()

    def turn_locked(self):
        return self.turn_locked_at is not None

    def get_active_player(self):
        return self.active_player.id if self.active_player else None

    def serialize(self, **kwargs):
        return dict({
            'id': self.id,
            'turn': self.turn,
            'active_player': self.get_active_player(),
            'active_card': self.active_card(),
            'played_cards': self.played_cards(),
            'players': self.players_info()
        }, **kwargs)

    def start_turn(self):
        for p in self.players.all():
            if p.played_card is not None and p != self.active_player:
                p.cards.remove(p.played_card)
                p.pick_cards()
            p.played_card = None

        self.rotate_active_player()
        self.turn_started_at = datetime.utcnow()
        self.turn_locked_at = None

    def check_turn_ready(self):
        players = self.players.all()
        if len([p for p in players if p.played_card]) <= 2:
            raise errors.NotEnoughPlayers()
        if datetime.utcnow() - self.turn_started_at < TURN_DURATION and \
                any(p.played_card is None for p in self.players.all()):
            raise errors.TooEarly()
        return True

    def is_turn_ready(self):
        try:
            self.check_turn_ready()
            return True
        except:
            return False

    def rotate_active_player(self):
        players = self.players.all();
        if self.active_player is None:
            self.set_active_player(players[0])
        else:
            self.set_active_player(
                    players[(players.index(self.active_player)+1) % len(players)])

    def pick_white_cards(self, count):
        cards = list(range(1, self.deck_size+1))
        random.seed(self.deck_seed)
        random.shuffle(cards)
        cards = cards[self.white_cards_picked:]

        picked_cards = []
        while len(picked_cards) < count and len(cards):
            card = Card.query.get(cards.pop(0))
            if card.deleted_at is None and card.type == 'white':
                picked_cards.append(card)
            self.white_cards_picked += 1

        return picked_cards

    def draw_black_card(self):
        cards = Card.query.filter(Card.id<=self.deck_size).all()
        random.seed(self.deck_seed)
        random.shuffle(cards)
        cards = cards[self.black_cards_picked:]

        while len(cards):
            self.black_cards_picked += 1
            card = cards.pop(0)
            if card.deleted_at is None and card.type == 'black':
                return card
        return None

    def set_active_player(self, player):
        self.active_player = player
        card = self.draw_black_card()
        if card is None:
            self.end_game()
        else:
            player.played_card = card

    def end_game(self):
        self.game_stopped_at = datetime.utcnow()

    def vote(self, card_index):
        self.check_turn_ready()
        if not self.turn_locked():
            raise errors.TurnNotLocked()

        player = self.playing_players()[card_index]
        player.score += 1
        return player