import random import string from django.db import models, transaction from django.utils import timezone def generate_code(length): return "".join([random.choice(string.ascii_lowercase) for i in range(length)]) class Game(models.Model): ACCESS_CODE_LENGTH = 6 access_code = models.CharField(db_index=True, unique=True, max_length=ACCESS_CODE_LENGTH) GAME_PHASE_LOBBY = 0 GAME_PHASE_PROPOSE = 1 GAME_PHASE_CLUE = 2 GAME_PHASE_SOLVE = 3 GAME_PHASE_END = 4 game_phase = models.IntegerField(default=GAME_PHASE_LOBBY) default_cards_per_player = models.IntegerField() num_initial_free_clues = models.IntegerField() num_unlock_clues_per_player = models.IntegerField() num_unlockable_clues = models.IntegerField() remaining_deck_order = models.CharField(max_length=64) created = models.DateTimeField() ended = models.DateTimeField(null=True, default=None) next_game = models.OneToOneField('self', null=True, default=None, related_name='previous_game') # from http://stackoverflow.com/a/11821832 def save(self, *args, **kwargs): # object is being created, thus no primary key field yet if not self.pk: # Make sure access_code is unique before using it. access_code = generate_code(Game.ACCESS_CODE_LENGTH) while Game.objects.filter(access_code=access_code).exists(): access_code = generate_code(Game.ACCESS_CODE_LENGTH) self.access_code = access_code self.created = timezone.now() if self.ended is None and self.game_phase == Game.GAME_PHASE_END: self.ended = timezone.now() super(Game, self).save(*args, **kwargs) _game_phase_strings = { GAME_PHASE_LOBBY: 'lobby', GAME_PHASE_PROPOSE: 'propose', GAME_PHASE_CLUE: 'clue', GAME_PHASE_SOLVE: 'solve', GAME_PHASE_END: 'end', } def game_phase_string(self): return Game._game_phase_strings[self.game_phase] def num_players(self): return self.player_set.filter(is_player=True).count() def num_non_player_stands(self): return self.player_set.filter(is_player=False).count() @transaction.atomic def create_or_get_next_game(self): if self.next_game is None and self.game_phase == self.GAME_PHASE_END: self.next_game = Game.objects.create() self.save() return self.next_game class Player(models.Model): game = models.ForeignKey(Game, on_delete=models.CASCADE, db_index=True) SECRET_ID_LENGTH = 8 secret_id = models.CharField(db_index=True, max_length=SECRET_ID_LENGTH) name = models.CharField(max_length=80) player_num = models.IntegerField() is_player = models.BooleanField() # as opposed to non-player stack ready = models.BooleanField(default=False) # num_cards is redundant with cards, but used during setup. num_cards = models.IntegerField() cards = models.CharField(max_length=16) ready = models.BooleanField(default=False) joined = models.DateTimeField() last_accessed = models.DateTimeField() # names are unique in a game unique_together = (("game", "name"), ("game", "secret_id")) class Turn(models.Model): game = models.ForeignKey(Game, on_delete=models.CASCADE, db_index=True) turn_num = models.IntegerField() visible_cards = models.CharField(max_length=64) clue = models.OneToOneField('ClueProposal', null=True) class ClueProposal(models.Model): turn = models.ForeignKey(Turn, on_delete=models.CASCADE, db_index=True) player = models.ForeignKey(Player, on_delete=models.CASCADE, db_index=True) card_indexes = models.CharField(max_length=80) class ClueProposalVote(models.Model): turn = models.ForeignKey(Turn, on_delete=models.CASCADE, db_index=True) player = models.ForeignKey(Player, on_delete=models.CASCADE, db_index=True) clue_proposal = models.OneToOneField(ClueProposal) class AdvanceDecision(models.Model): turn = models.ForeignKey(Turn, on_delete=models.CASCADE, db_index=True) player = models.ForeignKey(Player, on_delete=models.CASCADE, db_index=True) advance_card = models.BooleanField(null=True) guess = models.CharField(null=True, max_length=1) class BonusCardGuess(models.Model): turn = models.ForeignKey(Turn, on_delete=models.CASCADE, db_index=True) player = models.ForeignKey(Player, on_delete=models.CASCADE, db_index=True) guess = models.CharField(null=True, max_length=1) class Notes(models.Model): turn = models.ForeignKey(Turn, on_delete=models.CASCADE, db_index=True) player = models.ForeignKey(Player, on_delete=models.CASCADE, db_index=True) notes = models.CharField(max_length=80) class Solve(models.Model): player = models.ForeignKey(Player, on_delete=models.CASCADE, db_index=True) card_indexes = models.CharField(max_length=80)