diff --git a/backend/app/api/routers/rooms.py b/backend/app/api/routers/rooms.py index c9850a0..c97f752 100644 --- a/backend/app/api/routers/rooms.py +++ b/backend/app/api/routers/rooms.py @@ -26,8 +26,11 @@ async def broadcast_filtered_states(room_id: str, service: GameService): # Fetch presence for all players once presence_map = await service.get_all_player_presence(room_id, list(game.players.keys())) + # Optimize: Pre-calculate full schema once + full_schema = game.to_schema() + async def get_view(player_id: str): - return await service.get_player_view(game, player_id, presence_map) + return await service.get_player_view(game, player_id, presence_map, full_schema=full_schema) await websocket_manager.broadcast_filtered_game_states(room_id, get_view) diff --git a/backend/app/models/game.py b/backend/app/models/game.py index b43d2d9..d64fa22 100644 --- a/backend/app/models/game.py +++ b/backend/app/models/game.py @@ -152,12 +152,16 @@ def to_schema(self) -> GameStateSchema: ) # ===== View Logic ===== - def get_view_for_player(self, viewer_id: str) -> GameStateSchema: + def get_view_for_player( + self, viewer_id: str, full_schema: GameStateSchema | None = None + ) -> GameStateSchema: """ Create a filtered view of the game state for a specific player. Hides roles and actions based on game rules. """ - full_schema = self.to_schema() + if full_schema is None: + full_schema = self.to_schema() + is_game_over = full_schema.phase == GamePhase.GAME_OVER viewer = self.players.get(viewer_id) @@ -258,11 +262,8 @@ def get_view_for_player(self, viewer_id: str) -> GameStateSchema: self._state, pid ) - full_schema.players = filtered_players - # Never expose the raw seer reveal map - full_schema.seer_reveals = {} - - return full_schema + # Create a copy with filtered players and clean sensitive data + return full_schema.model_copy(update={"players": filtered_players, "seer_reveals": {}}) def auto_balance_roles(self): """Automatically set default role distribution based on player count.""" diff --git a/backend/app/services/game_service.py b/backend/app/services/game_service.py index b45c062..7657b1f 100644 --- a/backend/app/services/game_service.py +++ b/backend/app/services/game_service.py @@ -38,11 +38,15 @@ async def get_all_player_presence(self, room_id: str, player_ids: list[str]) -> return {pid: presence_results[i] is not None for i, pid in enumerate(player_ids)} async def get_player_view( - self, game: Game, player_id: str, presence_map: dict[str, bool] | None = None + self, + game: Game, + player_id: str, + presence_map: dict[str, bool] | None = None, + full_schema: GameStateSchema | None = None, ) -> GameStateSchema: """Return game state with other players' roles hidden unless revealed.""" # Delegate logic to model - view = game.get_view_for_player(player_id) + view = game.get_view_for_player(player_id, full_schema=full_schema) player_ids = list(view.players.keys()) # Optimize: if no players, skip redis