Ejemplo n.º 1
0
class User(AbstractBaseUser):
    id = ShortUUIDField(prefix="usr", max_length=128, primary_key=True)
    username = models.CharField(max_length=39, unique=True)
    email = models.CharField(max_length=512)

    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = []

    objects = UserManager()

    class Meta:
        app_label = 'authentication'

    @property
    def is_admin(self):
        return self.username.lower() in [
            "dlsteuer", "joram", "brandonb927", "coldog", "matthieudolci",
            "codeallthethingz"
        ]

    def assigned_to_team(self):
        from apps.tournament.models import TeamMember
        try:
            team = TeamMember.objects.get(user_id=self.id).team
            return True
        except TeamMember.DoesNotExist:
            pass

        return False
Ejemplo n.º 2
0
class Team(models.Model):
    id = ShortUUIDField(prefix="tem", max_length=128, primary_key=True)
    name = models.CharField(max_length=128)
    description = models.TextField()
    snake = models.ForeignKey(Snake, on_delete=models.CASCADE)

    @property
    def snakes(self):
        users = [tm.user for tm in TeamMember.objects.filter(team=self)]
        snakes = [us.snake for us in UserSnake.objects.filter(user__in=users)]
        return snakes

    @property
    def tournament_snakes(self):
        from apps.tournament.models import TournamentSnake
        return TournamentSnake.objects.filter(snake__in=self.snakes)

    @property
    def available_tournaments(self):
        from apps.tournament.models import Tournament
        return Tournament.objects.filter(
            status=Tournament.REGISTRATION).exclude(
                id__in=[ts.tournament.id for ts in self.tournament_snakes])

    class Meta:
        app_label = "tournament"
Ejemplo n.º 3
0
class Team(models.Model):
    id = ShortUUIDField(prefix="tem", max_length=128, primary_key=True)
    name = models.CharField(max_length=128)
    description = models.TextField(verbose_name="Back Story")
    can_register_in_tournaments = models.BooleanField(default=False)
    team_members = models.ManyToManyField(Profile)

    @property
    def snakes(self):
        return [s for s in Snake.objects.filter(profile__in=self.profiles)]

    @property
    def profiles(self):
        return [profile for profile in self.team_members.all()]

    @property
    def tournament_snakes(self):
        from apps.tournament.models import TournamentSnake

        return TournamentSnake.objects.filter(snake__in=self.snakes)

    @property
    def available_tournaments(self):
        from apps.tournament.models import Tournament

        return Tournament.objects.filter(
            status=Tournament.REGISTRATION).exclude(
                id__in=[ts.tournament.id for ts in self.tournament_snakes])

    def __str__(self):
        return self.name

    class Meta:
        app_label = "tournament"
Ejemplo n.º 4
0
class Snake(models.Model):
    id = ShortUUIDField(prefix="snk", max_length=128, primary_key=True)
    name = models.CharField(max_length=128)
    url = models.CharField(max_length=128)

    class Meta:
        app_label = 'snake'
Ejemplo n.º 5
0
class Tournament(models.Model):
    LOCKED = "LO"  # Not started, but nobody can register
    HIDDEN = "HI"  # Able to add snakes manually (invite-only)
    REGISTRATION = "RE"  # Publicly viewable and opt-in-able
    IN_PROGRESS = "PR"
    COMPLETE = "JR"
    STATUSES = (
        (LOCKED, "Locked"),
        (HIDDEN, "Hidden"),
        (REGISTRATION, "Registration"),
        (IN_PROGRESS, "In Progress"),
        (COMPLETE, "Complete"),
    )

    id = ShortUUIDField(prefix="trn", max_length=128, primary_key=True)
    casting_uri = models.CharField(default="", max_length=1024)
    status = models.CharField(max_length=2, choices=STATUSES, default=LOCKED)
    name = models.CharField(max_length=256)
    date = models.DateField()
    snakes = models.ManyToManyField(Snake,
                                    through="TournamentSnake",
                                    through_fields=("tournament", "snake"))
    engine_url = models.CharField(default=settings.ENGINE_URL, max_length=128)

    @property
    def brackets(self):
        return TournamentBracket.objects.filter(tournament=self)

    @property
    def is_registration_open(self):
        return self.status == self.REGISTRATION

    def __str__(self):
        return f"Tournament {self.name}"
Ejemplo n.º 6
0
class GameSnake(BaseModel):
    id = ShortUUIDField(prefix="gs", max_length=128, primary_key=True)
    snake = models.ForeignKey(Snake, on_delete=models.CASCADE)
    game = models.ForeignKey(Game, on_delete=models.CASCADE)
    death = models.CharField(default="pending", max_length=128)
    turns = models.IntegerField(default=0)
    name = models.CharField(default="", max_length=128)
Ejemplo n.º 7
0
class User(AbstractBaseUser, PermissionsMixin):
    id = ShortUUIDField(prefix="usr", max_length=128, primary_key=True)
    username = models.CharField(
        max_length=39, unique=True)  # 39 is max GitHub username length
    email = models.CharField(max_length=512)
    is_staff = models.BooleanField(default=False)
    is_superuser = models.BooleanField(default=False)
    is_commentator = models.BooleanField(default=False)

    USERNAME_FIELD = "username"
    REQUIRED_FIELDS = []

    objects = UserManager()

    class Meta:
        app_label = "authentication"

    @property
    def is_admin(self):
        return self.is_superuser

    def assigned_to_team(self):
        from apps.tournament.models import TeamMember

        return TeamMember.objects.filter(user_id=self.id).exists()
Ejemplo n.º 8
0
class User(AbstractBaseUser, PermissionsMixin):
    id = ShortUUIDField(prefix="usr", max_length=128, primary_key=True)
    username = models.CharField(
        max_length=39, unique=True
    )  # 39 is max GitHub username length
    email = models.CharField(max_length=512)
    is_staff = models.BooleanField(default=False)
    is_superuser = models.BooleanField(default=False)

    USERNAME_FIELD = "username"
    REQUIRED_FIELDS = []

    objects = UserManager()

    class Meta:
        app_label = "authentication"

    @property
    def is_admin(self):
        return self.username.lower() in [
            "brandonb927",
            "bvanvugt",
            "codeallthethingz",
            "coldog",
            "dlsteuer",
            "joram",
            "matthieudolci",
            "tristan-swu",
        ]

    def assigned_to_team(self):
        from apps.tournament.models import TeamMember

        return TeamMember.objects.filter(user_id=self.id).exists()
Ejemplo n.º 9
0
class Team(models.Model):
    id = ShortUUIDField(prefix="tem", max_length=128, primary_key=True)
    name = models.CharField(max_length=128)
    description = models.TextField()
    snake = models.ForeignKey(Snake, on_delete=models.CASCADE)

    class Meta:
        app_label = 'tournament'
Ejemplo n.º 10
0
class GameSnake(BaseModel):
    id = ShortUUIDField(prefix='gs', max_length=128, primary_key=True)
    snake = models.ForeignKey(Snake, on_delete=models.CASCADE)
    game = models.ForeignKey(Game, on_delete=models.CASCADE)
    death = models.CharField(default='pending', max_length=128)
    turns = models.IntegerField(default=0)

    class Meta:
        app_label = 'game'
Ejemplo n.º 11
0
class Snake(BaseModel):
    id = ShortUUIDField(prefix="snk", max_length=128, primary_key=True)
    name = models.CharField(max_length=128)
    url = models.CharField(max_length=128)

    def __str__(self):
        return f'{self.name}'

    class Meta:
        app_label = 'snake'
Ejemplo n.º 12
0
class Team(models.Model):
    id = ShortUUIDField(prefix="tem", max_length=128, primary_key=True)
    name = models.CharField(max_length=128)
    description = models.TextField()
    snake = models.ForeignKey(Snake, on_delete=models.CASCADE)

    @property
    def snakes(self):
        users = [tm.user for tm in TeamMember.objects.filter(team=self)]
        snakes = [us.snake for us in UserSnake.objects.filter(user__in=users)]
        return snakes

    class Meta:
        app_label = 'tournament'
Ejemplo n.º 13
0
class HeatGame(models.Model):
    UNWATCHED = "UW"
    WATCHING = "W"
    WATCHED = "WD"
    STATUSES = (
        (UNWATCHED, "Not Casted Yet"),
        (WATCHING, "Casting"),
        (WATCHED, "Casted"),
    )

    id = ShortUUIDField(prefix="hga", max_length=128, primary_key=True)
    status = models.CharField(max_length=2,
                              choices=STATUSES,
                              default=UNWATCHED)
    number = models.IntegerField(default=1)
    heat = models.ForeignKey(Heat, on_delete=models.CASCADE)
    game = models.ForeignKey("core.Game", on_delete=models.DO_NOTHING)

    objects = HeatGameManager()

    @property
    def snakes(self):
        if self.previous is None:
            return self.heat.snakes
        return [
            s for s in self.previous.snakes
            if s.snake != self.previous.winner.snake
        ]

    @property
    def winner(self):
        if not hasattr(self, "_winner") or self._winner is None:
            self._winner = self.game.winner()
        return self._winner

    @property
    def human_readable_status(self):
        for (short, name) in self.STATUSES:
            if self.status == short:
                return name

    @property
    def previous(self):
        if self.number == 1:
            return None
        return HeatGame.objects.get(number=self.number - 1, heat=self.heat)
Ejemplo n.º 14
0
class Snake(BaseModel):
    id = ShortUUIDField(prefix="snk", max_length=128, primary_key=True)
    name = models.CharField(
        max_length=128, validators=[MinLengthValidator(3), MaxLengthValidator(50)]
    )
    url = models.CharField(max_length=128)
    healthy = models.BooleanField(
        default=False, verbose_name="Did this snake respond to /ping"
    )
    profile = models.ForeignKey(Profile, on_delete=models.CASCADE)
    is_public = models.BooleanField(
        default=False, verbose_name="Allow anyone to add this snake to a game"
    )

    objects = SnakeManager()

    @property
    def public_name(self):
        return f"{self.profile.username} / {self.name}"

    def update_healthy(self):
        url = str(self.url)
        if not url.endswith("/"):
            url = url + "/"
        ping_url = urljoin(url, "ping")
        self.healthy = False
        try:
            status_code = self.make_ping_request(ping_url)
            if status_code == 200:
                self.healthy = True
        except Exception as e:
            logger.warning(f'Failed to ping "{self}": {e}')

        self.save(update_fields=["healthy"])

    def __str__(self):
        return f"{self.public_name}"

    def make_ping_request(self, ping_url):
        response = requests.post(ping_url, timeout=1, verify=False)
        status_code = response.status_code
        return status_code

    @property
    def games(self):
        return self.game_set.all()
Ejemplo n.º 15
0
class SnakeLeaderboard(BaseModel):
    """ Tracks a snakes involvement in the leaderboard. """
    def __init__(self, *args, **kwargs):
        self._rank = False
        super().__init__(*args, **kwargs)

    id = ShortUUIDField(prefix="slb", max_length=128, primary_key=True)
    snake = models.ForeignKey(Snake, null=True, on_delete=models.CASCADE)
    mu = models.FloatField(null=True)
    sigma = models.FloatField(null=True)
    unhealthy_counter = models.IntegerField(null=True)

    @property
    def rank(self):
        return self.mu or 25

    @classmethod
    def ranked(cls):
        snakes = list(SnakeLeaderboard.objects.all())
        return sorted(snakes, key=lambda s: s.rank, reverse=True)

    def __str__(self):
        return f"{self.snake.name}"

    def reset_unhealthy_counter(self):
        if self.unhealthy_counter is not None and self.unhealthy_counter > 0:
            self.unhealthy_counter = 0
            self.save(update_fields=["unhealthy_counter"])

    def is_unhealthy(self):
        if self.unhealthy_counter is not None and self.unhealthy_counter > 5:
            return True
        return False

    def increase_unhealthy_counter(self):
        if self.unhealthy_counter is None:
            self.unhealthy_counter = 0
        self.unhealthy_counter += 1
        self.save(update_fields=["unhealthy_counter"])

    class Meta:
        app_label = "leaderboard"
Ejemplo n.º 16
0
class User(AbstractBaseUser):
    id = ShortUUIDField(prefix="usr", max_length=128, primary_key=True)
    username = models.CharField(max_length=39, unique=True)
    email = models.CharField(max_length=512)

    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = []

    objects = UserManager()

    class Meta:
        app_label = 'authentication'

    def assigned_to_team(self):
        from apps.tournament.models import TeamMember
        try:
            team = TeamMember.objects.get(user_id=self.id).team
            return True
        except TeamMember.DoesNotExist:
            pass

        return False
Ejemplo n.º 17
0
class TournamentBracket(models.Model):
    id = ShortUUIDField(prefix="tbr", max_length=128, primary_key=True)
    name = models.CharField(max_length=256)
    tournament = models.ForeignKey(Tournament, on_delete=models.CASCADE)
    board_width = models.IntegerField(default=11)
    board_height = models.IntegerField(default=11)
    board_food = models.IntegerField(default=2)
    board_max_turns_to_next_food_spawn = models.IntegerField(null=True,
                                                             blank=True,
                                                             default=15)
    snakes = models.ManyToManyField(Snake,
                                    through="TournamentSnake",
                                    through_fields=("bracket", "snake"))

    cached_rounds = None

    header_row = ["Round", "Heat", "Team Name", "Team ID", "Team Snake URL"]

    def update_heat_games(self):
        if self.latest_round is not None:
            for heat in self.latest_round.heats:
                for hg in heat.games:
                    if hg.game.engine_id is not None:
                        hg.game.update_from_engine()
                        hg.game.save()

    def create_next_round(self):
        if self.latest_round is not None:
            if self.latest_round.status != "complete":
                raise RoundNotCompleteException
            if (self.latest_round.heats.count() == 1
                    and len(self.latest_round.snakes) == 2):
                raise TournamentBracketCompleteException

        num = max([r.number for r in self.rounds] + [0]) + 1
        self.cached_rounds = None  # clear out cached data
        return Round.objects.create(number=num, tournament_bracket=self)

    @property
    def rounds(self):
        if self.cached_rounds is None:
            self.cached_rounds = list(self.round_set.all().prefetch_related(
                "heat_set__heatgame_set__game",
                "heat_set__heatgame_set__game__gamesnake_set",
                "heat_set__snakeheat_set__snake",
            ).order_by("number"))
        return self.cached_rounds

    @property
    def latest_round(self):
        if len(self.rounds) == 0:
            return None
        return self.rounds[-1]

    def get_complete_final_round(self):
        if self.latest_round is None:
            return None

        # the final round has exactly one heat
        if self.latest_round.heats.count() != 1:
            return None
        last_heat = self.latest_round.heats.first()

        if len(last_heat.games) != 1:
            return None
        if self.latest_round.status != "complete":
            return None
        if len(last_heat.games[-1].snakes) != 2:
            return None
        return self.latest_round

    @property
    def winners(self):
        last_round = self.get_complete_final_round()
        if last_round is None:
            return False

        first_place_game = last_round.heats[0].games[0]

        # It's a tie!
        if first_place_game.winner is None:
            return []

        # first snake
        first_place = first_place_game.winner

        # second snake
        top_two_snakes = first_place_game.game.game_snakes
        second_place = [
            snake for snake in top_two_snakes if snake != first_place
        ][0]

        third_place_round = last_round.previous
        if third_place_round is None:
            return [first_place, second_place]

        # third snake
        third_place_game = third_place_round.heats[0].games[0]
        third_place = [
            gs for gs in third_place_game.game.game_snakes
            if gs.snake not in [first_place.snake, second_place.snake]
        ][0]

        return [first_place, second_place, third_place]

    @property
    def runner_ups(self):
        winners = self.winners
        if winners is False:
            return False

        round = self.latest_round
        if round is None:
            return []
        while True:
            if len(round.snakes) >= 4:
                break
            if round.previous is None:
                break
            round = round.previous

        final_six_game = round.heats[0].games[0]
        final_six = final_six_game.game.game_snakes
        game_snakes = final_six.exclude(
            snake_id__in=[w.snake_id for w in winners])
        return game_snakes

    @property
    def snake_count(self):
        return self.snakes.count()

    def game_details(self):
        games = []
        for r in self.rounds:
            for heat in r.heats:
                for hg in heat.games:
                    status = hg.game.status if hg.game is not None else None
                    games.append({
                        "id": hg.game.id,
                        "url": generate_game_url(hg.game),
                        "status": status,
                        "round": r.number,
                        "heat": heat.number,
                        "heat_game": hg.number,
                    })
        return games

    def export(self):
        rows = [self.header_row]
        for r in self.rounds:
            for heat in r.heats:
                for snake in heat.snakes:
                    row = [
                        f"Round {r.number}",
                        f"Heat {heat.number}",
                        snake.team.name,
                        snake.team.id,
                        snake.snake.url,
                    ]
                    rows.append(row)
        return rows

    def __str__(self):
        return f"[{self.tournament.name}] {self.name}"

    class Meta:
        app_label = "tournament"
Ejemplo n.º 18
0
class Game(BaseModel):
    """
    Game tracks a game started on the engine locally in the snake database. You
    can initialize a game through this model and call run() to start the game.
    Then, you can also call update_from_engine() at any point to refresh the
    game state from the engine onto this model.

    Creating a game looks like:

        game = Game(...) # instance created with config, ready to go
        game.create()    # game snakes created, and any other future pre-game things
        game.run()       # (OPTIONAL) sent to engine, and now it's running!
    """
    class NotCreatedError(Exception):
        pass

    class Status:
        PENDING = "pending"
        CREATED = "created"
        RUNNING = "running"
        ERROR = "error"
        STOPPED = "stopped"
        COMPLETE = "complete"

    id = ShortUUIDField(prefix="gam", max_length=128, primary_key=True)
    engine_id = models.CharField(null=True, max_length=128)
    status = models.CharField(default=Status.PENDING, max_length=30)
    turn = models.IntegerField(default=0)
    width = models.IntegerField()
    height = models.IntegerField()
    max_turns_to_next_food_spawn = models.IntegerField(default=15)
    snakes = models.ManyToManyField(Snake)
    engine_url = models.CharField(null=True, max_length=128)

    objects = GameQuerySet.as_manager()

    def config(self):
        """ Fetch the engine configuration. """
        config = {
            "width":
            self.width,
            "height":
            self.height,
            "maxTurnsToNextFoodSpawn":
            self.max_turns_to_next_food_spawn,
            "food":
            self.snakes.count(),
            "snakeTimeout":
            500,
            "snakes": [{
                "name": gs.name if len(gs.name) > 0 else gs.snake.public_name,
                "url": gs.snake.url,
                "id": gs.id,
            } for gs in self.gamesnake_set.all()],
        }
        return config

    @transaction.atomic
    def create(self):
        """ Call the engine to create the game. Returns the game id. """
        config = self.config()
        self.engine_id = engine.create(config, self.engine_url)
        self.status = Game.Status.CREATED
        self.save()
        return self.engine_id

    def run(self):
        """ Call the engine to start the game. Returns the game id. """
        engine.run(self.engine_id, self.engine_url)
        return self.engine_id

    def engine_status(self):
        return engine.status(self.engine_id, self.engine_url)

    def update_from_engine(self):
        """ Update the status and snake statuses from the engine. """
        if self.engine_id is None:
            raise self.NotCreatedError("Game is not created")
        with transaction.atomic():
            status = engine.status(self.engine_id, self.engine_url)
            self.status = status["status"]
            self.turn = status["turn"]

            for game_snake in self.gamesnake_set.all():
                snake_status = status["snakes"][game_snake.id]
                game_snake.death = snake_status["death"]
                game_snake.save()

            self.save()
            return status

    @property
    def game_snakes(self):
        return self.gamesnake_set.all()

    def alive_game_snakes(self):
        return self.game_snakes.filter(death="pending")

    def winner(self):
        if self.status == self.Status.COMPLETE:
            living_snakes = self.alive_game_snakes()
            if living_snakes.count() == 1:
                return living_snakes.first()
Ejemplo n.º 19
0
class Round(models.Model):
    NAME_FINAL_6 = "The Final 6"
    NAME_FINAL_3 = "The Final 3"
    NAME_FINAL_2 = "The Final 2"

    id = ShortUUIDField(prefix="rnd", max_length=128, primary_key=True)
    number = models.IntegerField(default=1)
    tournament_bracket = models.ForeignKey(TournamentBracket,
                                           on_delete=models.CASCADE)

    objects = RoundManager()

    @property
    def previous(self):
        try:
            return Round.objects.get(
                number=self.number - 1,
                tournament_bracket=self.tournament_bracket)
        except Round.DoesNotExist:
            return None

    @property
    def snakes(self):
        if self.number == 1:
            return [s for s in self.tournament_bracket.snakes.all()]
        return [s.snake for s in self.previous.winners]

    @property
    def _heat_count(self):
        if not hasattr(self, "__heat_count"):
            self.__heat_count = self.heats.count()
        return self.__heat_count

    @property
    def name(self):
        if self._heat_count > 1:
            return f"Round {self.number}"

        num_snakes = self.snake_count
        if num_snakes == 2:
            return self.NAME_FINAL_2

        if num_snakes == 3:
            return self.NAME_FINAL_3

        return self.NAME_FINAL_6

    @property
    def snake_count(self):
        return len(self.snakes)

    @property
    def winners(self):
        winners = []
        for heat in self.heats:
            winners += heat.winners
        return winners

    @property
    def heats(self):
        return (self.heat_set.all().order_by("number").prefetch_related(
            "heatgame_set", "snakeheat_set"))

    @property
    def status(self):
        for heat in self.heats:
            if heat.status != "complete":
                return heat.status
        return "complete"

    def __str__(self):
        return f"[{self.tournament_bracket.tournament.name}:{self.tournament_bracket.name}] {self.name}"

    class Meta:
        app_label = "tournament"
        unique_together = ("number", "tournament_bracket")
Ejemplo n.º 20
0
class Game(BaseModel):
    """
    Game tracks a game started on the engine locally in the snake database. You
    can initialize a game through this model and call run() to start the game.
    Then, you can also call update_from_engine() at any point to refresh the
    game state from the engine onto this model.

    Creating a game looks like:

        game = Game(...) # instance created with config, ready to go
        game.create()    # game snakes created, and any other future pre-game things
        game.run()       # sent to engine, and now it's running!
    """
    class Status:
        PENDING = 'pending'
        RUNNING = 'running'
        ERROR = 'error'
        STOPPED = 'stopped'
        COMPLETE = 'complete'

    id = ShortUUIDField(prefix='gam', max_length=128, primary_key=True)
    team = models.ForeignKey(Team, on_delete=models.SET_NULL, null=True)
    engine_id = models.CharField(null=True, max_length=128)
    status = models.CharField(default=Status.PENDING, max_length=30)
    turn = models.IntegerField(default=0)
    width = models.IntegerField()
    height = models.IntegerField()
    food = models.IntegerField()

    def __init__(self, *args, **kwargs):
        self.snakes = kwargs.get('snakes', [])
        if 'snakes' in kwargs:
            del kwargs['snakes']
        super().__init__(*args, **kwargs)

    def config(self):
        """ Fetch the engine configuration. """
        config = {
            'width': self.width,
            'height': self.height,
            'food': self.food,
            'snakes': [],
        }
        for snake in self.get_snakes():
            config['snakes'].append({
                'name': snake.snake.name,
                'url': snake.snake.url,
                'id': snake.id,
            })
        return config

    def create(self):
        with transaction.atomic():
            # Note: Creating GameSnake
            # objects used to happen in the overridden save model function.
            # Saving the game here ensures there is an ID to use when
            # creating GameSnake objects. This is a bit of a hack because
            # of the way Game was implemented initially and then adapted to
            # support multiple of the same Snake in a Game.
            self.save()

            for s in self.snakes:
                snake = Snake.objects.get(id=s['id'])
                GameSnake.objects.create(snake=snake, game=self)

    def run(self):
        """ Call the engine to start the game. Returns the game id. """
        config = self.config()
        self.engine_id = engine.run(config)
        self.save()
        return self.engine_id

    def update_from_engine(self):
        """ Update the status and snake statuses from the engine. """
        with transaction.atomic():
            status = engine.status(self.engine_id)
            self.status = status['status']
            self.turn = status['turn']

            for game_snake in self.get_snakes():
                snake_status = status['snakes'][game_snake.id]
                game_snake.death = snake_status['death']
                game_snake.save()

            self.save()
            return status

    def get_snakes(self):
        return GameSnake.objects.filter(
            game_id=self.id).prefetch_related('snake')

    def alive_game_snakes(self):
        return self.get_snakes().filter(death="pending")

    def winner(self):
        if self.status == self.Status.COMPLETE:
            living_snakes = self.alive_game_snakes()
            if len(living_snakes) == 1:
                return self.alive_game_snakes()[0]

    @property
    def leaderboard_game(self):
        from apps.leaderboard.models import GameLeaderboard
        try:
            return self.gameleaderboard
        except GameLeaderboard.DoesNotExist:
            return None

    class Meta:
        app_label = 'game'
Ejemplo n.º 21
0
class Heat(models.Model):
    id = ShortUUIDField(prefix="hea", max_length=128, primary_key=True)
    number = models.IntegerField(default=1)
    round = models.ForeignKey(Round, on_delete=models.CASCADE)
    desired_games = models.IntegerField(default=2)

    cached_heatgames = None

    def __str__(self):
        return f"[{self.round.tournament_bracket.tournament.name}:{self.round.tournament_bracket.name}:{self.round.name}] Heat {self.number}"

    @property
    def snakes(self):
        return self.snakeheat_set.all().prefetch_related(
            "snake", "heat", "heat__heatgame_set")

    @property
    def ordered_snakes(self):
        snakes = list(self.snakes)
        ordered_snakes = []
        for s in snakes:
            if s.first:
                ordered_snakes.append(s)
                snakes.remove(s)
                break
        for s in snakes:
            if s.second:
                ordered_snakes.append(s)
                snakes.remove(s)
                break
        for s in snakes:
            if s.third:
                ordered_snakes.append(s)
                snakes.remove(s)
                break
        for s in snakes:
            ordered_snakes.append(s)
        return ordered_snakes

    @property
    def games(self):
        if self.cached_heatgames is None:
            self.cached_heatgames = list(
                self.heatgame_set.all().order_by("number"))
        return self.cached_heatgames

    @property
    def latest_game(self):
        if len(self.games) == 0:
            return None
        return self.games[0]

    @property
    def winners(self):
        winners = []
        for game in self.games:
            if game.winner is not None:
                winners.append(game.winner)

        return winners

    @property
    def status(self):
        if len(self.games) < self.desired_games:
            return "running"

        for hg in self.games:
            if hg.game.status is not Game.Status.COMPLETE:
                return hg.game.status
        return "complete"

    def create_next_game(self):
        if len(self.games) >= self.desired_games:
            raise DesiredGamesReachedValidationError()

        n = len(self.games) + 1

        if (self.latest_game is not None
                and self.latest_game.game.status != Game.Status.COMPLETE):
            raise Exception("can't create next game")
        hg = HeatGame.objects.create(heat=self, number=n)
        return hg

    class Meta:
        app_label = "tournament"