diff --git a/games/consumers.py b/games/consumers.py index 825ffe5..5bac2ef 100644 --- a/games/consumers.py +++ b/games/consumers.py @@ -21,7 +21,6 @@ if TYPE_CHECKING: from .objects.pong.PongGame import PongGame from .objects.tictactoe.TicTacToeGame import TicTacToeGame - from .objects.tictactoe.TicTacToeSpectator import TicTacToeSpectator game_manager: GameManager = GameManager() diff --git a/games/models.py b/games/models.py index 573a891..edd26f7 100644 --- a/games/models.py +++ b/games/models.py @@ -21,43 +21,28 @@ class GameModel(models.Model): stop_timestamp = models.BigIntegerField(null = True, blank = True) game_type = models.CharField(max_length = 60, default = "pong") - def create(self, players: list[User]): + def create(self, players: set[User]) -> GameModel: self.save() for player in players: GameMembersModel(game = self, player=player).save() - return self.pk + return self def start(self): self.start_timestamp = round(time.time() * 1000, 1) self.started = True self.save() - - def get_tournament(self) -> None | TournamentGameModel: - - from tournament.models import TournamentGameModel - - query = TournamentGameModel.objects.filter(game=self) - if (not query.exists()): - return None - return query[0] - def finish(self, winner_id): - self.winner_id = winner_id + def finish(self, winner: User): + self.winner = winner self.finished = True self.stop_timestamp = round(time.time() * 1000, 1) self.save() - tournament = self.get_tournament() - if tournament is not None: - from tournament.consumers import tournament_manager - room = tournament_manager.get(tournament) - room.set_game_as_finished(self) - - def get_players(self) -> list[User]: - return [game_player.player for game_player in GameMembersModel.objects.filter(game = self)] + def get_players(self) -> set[User]: + return {game_player.player for game_player in GameMembersModel.objects.filter(game = self)} - def get_players_profiles(self) -> list[User]: - return [game_player.player.profilemodel for game_player in GameMembersModel.objects.filter(game = self)] + def get_players_profiles(self) -> set[User]: + return {game_player.player.profilemodel for game_player in GameMembersModel.objects.filter(game = self)} def get_score_by_player_id(self, player_id: int) -> list[int]: query: QuerySet = GameGoalModel.objects.filter(game_id = self.pk, player_id = player_id) diff --git a/games/objects/AGame.py b/games/objects/AGame.py index 6839a61..6fe0583 100644 --- a/games/objects/AGame.py +++ b/games/objects/AGame.py @@ -6,6 +6,8 @@ from .ASpectator import ASpectator from ..models import GameModel +from tournament.models import TournamentGameModel + from django.contrib.auth.models import User class AGame(AbstractRoom): @@ -16,7 +18,11 @@ class AGame(AbstractRoom): self.game_manager = game_manager - self.model: GameModel = GameModel.objects.get(pk = game_id, game_type = game_type) + query = TournamentGameModel.objects.filter(pk = game_id, game_type = game_type) + if (query.exists()): + self.model: TournamentGameModel | GameModel = query[0] + else: + self.model: TournamentGameModel | GameModel = GameModel.objects.get(pk = game_id, game_type = game_type) players: list[User] = self.model.get_players() diff --git a/games/objects/GameManager.py b/games/objects/GameManager.py index ee1ced8..94de967 100644 --- a/games/objects/GameManager.py +++ b/games/objects/GameManager.py @@ -3,6 +3,8 @@ from ..models import GameModel from .pong.PongGame import PongGame from .tictactoe.TicTacToeGame import TicTacToeGame +from tournament.models import TournamentGameModel + class GameManager(): def __init__(self) -> None: @@ -14,7 +16,7 @@ class GameManager(): self._game_list.remove(game) def get(self, game_id: int, game_type: str) -> TicTacToeGame | PongGame: - + if (not GameModel.objects.filter(pk=game_id, finished=False, game_type=game_type).exists()): return None diff --git a/games/serializers.py b/games/serializers.py index 114c82d..b0338bb 100644 --- a/games/serializers.py +++ b/games/serializers.py @@ -15,11 +15,11 @@ class GameSerializer(serializers.ModelSerializer): finished = serializers.ReadOnlyField() start_timestamp = serializers.ReadOnlyField() stop_timestamp = serializers.ReadOnlyField() - gamemode = serializers.ReadOnlyField() + game_type = serializers.ReadOnlyField() class Meta: model = GameModel - fields = ["id", "winner_id", "state", "started", "finished", "players", "start_timestamp", "stop_timestamp", "gamemode"] + fields = ["id", "winner_id", "state", "started", "finished", "players", "start_timestamp", "stop_timestamp", "game_type"] def get_state(self, instance: GameModel): if (instance.finished): diff --git a/matchmaking/consumers.py b/matchmaking/consumers.py index 331d391..9c65961 100644 --- a/matchmaking/consumers.py +++ b/matchmaking/consumers.py @@ -42,8 +42,8 @@ class MatchMaking(WebsocketConsumer): self.waiting_room.broadcast(f"{len(self.waiting_room)} / {self.waiting_room.mode}") if (len(self.waiting_room) == self.waiting_room.mode): - game_id: int = GameModel(game_type=self.game_type).create(self.waiting_room.get_members()) - self.waiting_room.broadcast("game_found", {"game_id": game_id, "game_type": self.game_type}) + game: GameModel = GameModel(game_type=self.game_type).create(self.waiting_room.get_members()) + self.waiting_room.broadcast("game_found", {"game_id": game.pk, "game_type": self.game_type}) def disconnect(self, close_code): super().disconnect(close_code) diff --git a/tournament/consumers.py b/tournament/consumers.py index 953b082..fe52b90 100644 --- a/tournament/consumers.py +++ b/tournament/consumers.py @@ -7,6 +7,7 @@ from django.db.models import QuerySet from django.utils.translation import gettext as _ from games.models import GameModel +from games.serializers import GameSerializer from profiles.models import ProfileModel from profiles.serializers.ProfileSerializer import ProfileSerializer from .models import TournamentModel @@ -83,6 +84,8 @@ class TournamentRoom: self._room_manager: TournamentRoomManager = room_manager self._member_list: set[TournamentMember] = set() self._model: TournamentModel = tournament + self._game_in_progress_list: set[GameModel] = set() + self._current_round = 0 def join(self, socket: TournamentWebConsumer) -> TournamentMember: @@ -92,21 +95,52 @@ class TournamentRoom: return member def set_game_as_finished(self, game: GameModel): - raise NotImplemented() + self._game_in_progress_list.remove(game) + + data: dict = GameSerializer(game).data + + data.update({"round": self._current_round}) + + self.broadcast("game_update", data) + + if len(self._game_in_progress_list) == 0: + self._round_finished() + + def _finish(self, winner: User): + self._model.finish(winner) + + def _round_finished(self): + + if self._current_round == self._model.round: + last_game: GameModel = self._model.get_games_by_round(self._current_round)[0] + self._finish(last_game.winner) + return + + self._start_round() + + def _start_round(self): + + self._current_round += 1 + + participant_list: set[User] = self._model.get_participants_by_round(self._current_round) + + self._game_in_progress_list = self._model.create_round(participant_list, self._current_round) + + for game in self._game_in_progress_list: + for player in game.get_players(): + participant: TournamentMember = self.get_participant_by_profile(player) + participant.send_goto(game) def get_participants_profiles(self) -> list[ProfileModel]: return [participant._socket.user.profilemodel for participant in self.get_participants()] def start(self) -> None: - games: list[GameModel] = self._model.start() + self._model.start() self.broadcast("start") - for game in games: - for player in game.get_players(): - participant: TournamentMember = self.get_participant_by_profile(player) - participant.send_goto(game) + self._start_round() def get_participant_by_profile(self, profile: ProfileModel): for participant in self.get_participants(): diff --git a/tournament/models.py b/tournament/models.py index 6a91526..cabc76d 100644 --- a/tournament/models.py +++ b/tournament/models.py @@ -19,24 +19,35 @@ class TournamentModel(models.Model): def _register_participant(self, participant: User) -> None: TournamentParticipantModel(participant=participant, tournament=self).save() - def start(self, participants: list[User]) -> None: + def create_round(self, participants: set[User], round: int) -> set[GameModel]: - games: list[GameModel] = [] + game_list: set[GameModel] = set() + + for i, (participant1, participant2) in enumerate(zip(participants[0::2], participants[1::2])): + game: GameModel = self.create_game([participant1, participant2], round, i) + game_list.add(game) + + return game_list + + def start(self, participant_list: set[User]) -> None: self.started = True + self.round = 1 - for player in participants: - self._register_participant(player) - - for (participant1, participant2) in zip(participants[0::2], participants[1::2]): - game: GameModel = self.create_game([participant1, participant2], round=1) - games.append(game) + for participant in participant_list: + self._register_participant(participant) self.save() - return games + def finish(self, winner: User): + + self.finished = True - def create_game(self, participants: list[User], round: int) -> GameModel: + self.winner = winner + + self.save() + + def create_game(self, participants: set[User], round: int, pos: int) -> GameModel: if (self.started == False): return None @@ -48,37 +59,47 @@ class TournamentModel(models.Model): game: GameModel = GameModel().create(participants) - TournamentGameModel(tournament=self, game=game, round=round).save() + TournamentGameModel(tournament=self, game=game, round=round, pos=pos).save() return game - def get_games(self) -> list[GameModel]: - return [games for games in self.get_games_by_round(i for i in range(1, self.round))] + def get_games(self) -> set[GameModel]: + return {games for games in self.get_games_by_round(i for i in range(1, self.round))} - def get_games_by_round(self, round: int) -> list[GameModel]: - return [tournament_game.game for tournament_game in TournamentGameModel.objects.filter(tournament=self, round=round)] + def get_games_by_round(self, round: int) -> set[GameModel]: + return {tournament_game for tournament_game in TournamentGameModel.objects.filter(tournament=self, round=round)} - def get_players_by_round(self, round: int) -> list[User]: - return [game.get_players() for game in self.get_games_by_round(round)] + def get_participants_by_round(self, round: int) -> set[User]: + if round == 1: + return self.get_participants() + return {game.winner for game in self.get_games_by_round(round - 1)} - def get_winners_by_round(self, round: int) -> list[User]: - return [game.winner for game in self.get_games_by_round(round)] + def get_winners_by_round(self, round: int) -> set[User]: + return {game.winner for game in self.get_games_by_round(round)} - def get_participants(self) -> list[TournamentParticipantModel]: - return TournamentParticipantModel.objects.filter(tournament=self.pk) + def get_participants(self) -> set[User]: + return {tournament_participant.participant for tournament_participant in TournamentParticipantModel.objects.filter(tournament=self.pk)} def get_state(self) -> str: return ("waiting to start", "in progress", "finish")[self.started + self.finished] - def is_participanting(self, profile: User) -> bool: + def is_participating(self, profile: User) -> bool: return TournamentParticipantModel.objects.filter(participant=profile, tournament=self).exists() class TournamentParticipantModel(models.Model): participant = models.ForeignKey(User, on_delete=CASCADE) tournament = models.ForeignKey(TournamentModel, on_delete=CASCADE) - -class TournamentGameModel(models.Model): + +class TournamentGameModel(GameModel): tournament = models.ForeignKey(TournamentModel, on_delete=CASCADE, null=True, blank=True) round = models.IntegerField() - game = models.ForeignKey(GameModel, on_delete=CASCADE) \ No newline at end of file + pos = models.IntegerField() + + def finish(self, winner_id): + super().finish(winner_id) + + from .consumers import tournament_manager + + room = tournament_manager.get(self.tournament) + room.set_game_as_finished(self) \ No newline at end of file diff --git a/tournament/serializers.py b/tournament/serializers.py index cabb773..012aab6 100644 --- a/tournament/serializers.py +++ b/tournament/serializers.py @@ -1,8 +1,7 @@ from rest_framework import serializers -from .models import TournamentModel +from .models import TournamentModel, TournamentGameModel -from profiles.models import ProfileModel from profiles.serializers.ProfileSerializer import ProfileSerializer from games.serializers import GameSerializer @@ -34,4 +33,29 @@ class TournamentSerializer(serializers.ModelSerializer): def validate_nb_participants(self, value: int): if (value not in nb_participants): raise serializers.ValidationError(f"The numbers of participants must be {str(nb_participants)}.") - return value \ No newline at end of file + return value + +class TournamentGameSerializer(serializers.ModelSerializer): + + players = serializers.SerializerMethodField() + winner_id = serializers.ReadOnlyField() + state = serializers.SerializerMethodField() + started = serializers.ReadOnlyField() + finished = serializers.ReadOnlyField() + start_timestamp = serializers.ReadOnlyField() + stop_timestamp = serializers.ReadOnlyField() + gamemode = serializers.ReadOnlyField() + + class Meta: + model = TournamentGameModel + fields = ["id", "winner_id", "state", "started", "finished", "players", "start_timestamp", "stop_timestamp", "game_type"] + + def get_state(self, instance: TournamentGameModel): + if (instance.finished): + return "finished" + if (instance.started): + return "started" + return "waiting" + + def get_players(self, instance: TournamentGameModel): + return ProfileSerializer(instance.get_players_profiles(), many=True).data \ No newline at end of file