diff --git a/backend/app/services/game_manager.py b/backend/app/services/game_manager.py index 3c5ed63..407f3d1 100644 --- a/backend/app/services/game_manager.py +++ b/backend/app/services/game_manager.py @@ -37,6 +37,8 @@ class GameManager: room["current_player_index"] = {"A": 0, "B": 0} room["board"] = board room["scores"] = {"A": 0, "B": 0} + room["current_round"] = 1 + room["round1_categories"] = [int(cat_id) for cat_id in board.keys()] await room_manager.update_room(room_code, room) return room @@ -153,14 +155,20 @@ class GameManager: # Switch to other team for potential steal room["current_team"] = "B" if failed_team == "A" else "A" - # Check if game is over (all questions answered) + # Check if round is over (all questions answered) all_answered = all( q["answered"] for questions in room["board"].values() for q in questions ) if all_answered: - room["status"] = "finished" + current_round = room.get("current_round", 1) + if current_round == 1: + # Round 1 finished - need to start round 2 + room["round_finished"] = True + else: + # Round 2 finished - game over + room["status"] = "finished" await room_manager.update_room(room_code, room) @@ -189,6 +197,19 @@ class GameManager: room["current_question"] = None room["can_steal"] = False + # Check if round is over + all_answered = all( + q["answered"] + for questions in room["board"].values() + for q in questions + ) + if all_answered: + current_round = room.get("current_round", 1) + if current_round == 1: + room["round_finished"] = True + else: + room["status"] = "finished" + await room_manager.update_room(room_code, room) return room @@ -205,6 +226,45 @@ class GameManager: index = room["current_player_index"][team] return players[index % len(players)] + async def start_round_2( + self, + db: AsyncSession, + room_code: str + ) -> Optional[dict]: + """ + Start round 2 with different categories and double points. + """ + room = await room_manager.get_room(room_code) + if not room: + return None + + # Get categories used in round 1 + round1_categories = room.get("round1_categories", []) + + # Get new board excluding round 1 categories, with 2x points + new_board = await question_service.get_board_for_game( + db, + exclude_categories=round1_categories, + point_multiplier=2 + ) + + if not new_board: + # Not enough categories for round 2 - end game + room["status"] = "finished" + await room_manager.update_room(room_code, room) + return room + + # Update room for round 2 + room["board"] = new_board + room["current_round"] = 2 + room["round_finished"] = False + room["current_question"] = None + room["can_steal"] = False + # Keep current_team - winner of last question picks first + + await room_manager.update_room(room_code, room) + return room + def calculate_timer_end(self, time_seconds: int, is_steal: bool = False) -> datetime: """Calculate when the timer should end.""" if is_steal: diff --git a/backend/app/services/question_service.py b/backend/app/services/question_service.py index ccef0d1..1fec340 100644 --- a/backend/app/services/question_service.py +++ b/backend/app/services/question_service.py @@ -65,12 +65,20 @@ class QuestionService: async def get_board_for_game( self, db: AsyncSession, - target_date: Optional[date] = None + target_date: Optional[date] = None, + exclude_categories: Optional[List[int]] = None, + point_multiplier: int = 1 ) -> Dict[str, List[dict]]: """ Genera el tablero 5×5 para el juego. Selecciona 5 categorías aleatorias y 1 pregunta por dificultad. + Args: + db: Database session + target_date: Date for questions (default: today) + exclude_categories: Category IDs to exclude (for round 2) + point_multiplier: Multiply points by this value (for round 2) + Returns: Dict con category_id como string (para JSON) -> lista de preguntas """ @@ -82,6 +90,15 @@ class QuestionService: # Get available category IDs that have questions available_categories = list(full_board.keys()) + # Exclude categories from previous round + if exclude_categories: + available_categories = [ + c for c in available_categories if c not in exclude_categories + ] + + if not available_categories: + return {} + # Select random categories (up to CATEGORIES_PER_GAME) num_categories = min(CATEGORIES_PER_GAME, len(available_categories)) selected_categories = random.sample(available_categories, num_categories) @@ -104,7 +121,10 @@ class QuestionService: for difficulty in range(1, 6): # 1-5 if difficulty in questions_by_difficulty: questions = questions_by_difficulty[difficulty] - selected_q = random.choice(questions) + selected_q = random.choice(questions).copy() + # Apply point multiplier for round 2 + if point_multiplier > 1: + selected_q["points"] = selected_q["points"] * point_multiplier selected_questions.append(selected_q) if selected_questions: diff --git a/backend/app/sockets/game_events.py b/backend/app/sockets/game_events.py index 8820549..85d3f65 100644 --- a/backend/app/sockets/game_events.py +++ b/backend/app/sockets/game_events.py @@ -358,9 +358,28 @@ def register_socket_events(sio: socketio.AsyncServer): points_earned=result["points_earned"] ) - # Verificar si el juego termino (todas las preguntas respondidas) - if room_data.get("status") == "finished": - # Disparar finalizacion automatica + # Verificar si terminó la ronda o el juego + if room_data.get("round_finished"): + # Ronda 1 terminada - iniciar ronda 2 + async with await get_db_session() as db: + new_room = await game_manager.start_round_2(db, room_code) + if new_room: + if new_room.get("status") == "finished": + # No hay suficientes categorías para ronda 2 + await finish_game_internal(room_code) + else: + # Emitir evento de nueva ronda + await sio.emit( + "round_started", + { + "room": new_room, + "round": 2, + "message": "¡Ronda 2! Puntos dobles" + }, + room=room_code + ) + elif room_data.get("status") == "finished": + # Juego terminado await finish_game_internal(room_code) @sio.event @@ -395,6 +414,26 @@ def register_socket_events(sio: socketio.AsyncServer): team=player["team"], question_id=question_id ) + + # Verificar si terminó la ronda o el juego + if room.get("round_finished"): + async with await get_db_session() as db: + new_room = await game_manager.start_round_2(db, room_code) + if new_room: + if new_room.get("status") == "finished": + await finish_game_internal(room_code) + else: + await sio.emit( + "round_started", + { + "room": new_room, + "round": 2, + "message": "¡Ronda 2! Puntos dobles" + }, + room=room_code + ) + elif room.get("status") == "finished": + await finish_game_internal(room_code) else: # Will attempt steal - just notify, answer comes separately room = await room_manager.get_room(room_code) diff --git a/frontend/src/hooks/useSocket.ts b/frontend/src/hooks/useSocket.ts index 787032b..894492a 100644 --- a/frontend/src/hooks/useSocket.ts +++ b/frontend/src/hooks/useSocket.ts @@ -85,6 +85,14 @@ export function useSocket() { soundPlayer.play('game_start', volume) }) + socket.on('round_started', (data: { room: GameRoom; round: number; message: string }) => { + setRoom(data.room) + setCurrentQuestion(null) + // Play sound for new round + const volume = useSoundStore.getState().volume + soundPlayer.play('game_start', volume) + }) + socket.on('question_selected', (data: { room: GameRoom; question_id: number }) => { setRoom(data.room) // Find the question in the board and set it as current diff --git a/frontend/src/pages/Game.tsx b/frontend/src/pages/Game.tsx index 7a0ad05..03a3c1c 100644 --- a/frontend/src/pages/Game.tsx +++ b/frontend/src/pages/Game.tsx @@ -157,7 +157,7 @@ export default function Game() { return (