from datetime import timedelta from .models import GameSession, Player, RoundConfig, RoundQuestion def build_player_ref(player: Player | None) -> dict | None: if player is None: return None return { "player_id": player.id, "nickname": player.nickname, } def build_round_question_payload(round_question: RoundQuestion | None) -> dict | None: if round_question is None: return None return { "id": round_question.id, "round_number": round_question.round_number, "prompt": round_question.question.prompt, "shown_at": round_question.shown_at.isoformat(), "answers": [{"text": text} for text in (round_question.mixed_answers or [])], } def build_reveal_payload(round_question: RoundQuestion | None) -> dict | None: if round_question is None: return None lies = [ { **build_player_ref(lie.player), "text": lie.text, "created_at": lie.created_at.isoformat(), } for lie in round_question.lies.select_related("player").order_by("created_at", "id") ] guesses = [] for guess in round_question.guesses.select_related("player", "fooled_player").order_by("created_at", "id"): guess_payload = { **build_player_ref(guess.player), "selected_text": guess.selected_text, "is_correct": guess.is_correct, "created_at": guess.created_at.isoformat(), "fooled_player_id": guess.fooled_player_id, } if guess.fooled_player is not None: guess_payload["fooled_player_nickname"] = guess.fooled_player.nickname guesses.append(guess_payload) return { "round_question_id": round_question.id, "round_number": round_question.round_number, "prompt": round_question.question.prompt, "correct_answer": round_question.correct_answer, "lies": lies, "guesses": guesses, } def build_leaderboard(session: GameSession) -> list[dict]: return list( Player.objects.filter(session=session) .order_by("-score", "nickname") .values("id", "nickname", "score") ) def build_lie_started_payload(session: GameSession, round_config: RoundConfig, round_question: RoundQuestion) -> dict: lie_deadline_at = round_question.shown_at + timedelta(seconds=round_config.lie_seconds) return { "round_number": session.current_round, "category": {"slug": round_config.category.slug, "name": round_config.category.name}, "round_question_id": round_question.id, "prompt": round_question.question.prompt, "shown_at": round_question.shown_at.isoformat(), "lie_deadline_at": lie_deadline_at.isoformat(), "lie_seconds": round_config.lie_seconds, } def build_phase_view_model(session: GameSession, *, players_count: int, has_round_question: bool) -> dict: status = session.status in_lobby = status == GameSession.Status.LOBBY in_lie = status == GameSession.Status.LIE in_guess = status == GameSession.Status.GUESS in_scoreboard = status == GameSession.Status.SCOREBOARD in_finished = status == GameSession.Status.FINISHED min_players_reached = players_count >= 3 max_players_allowed = players_count <= 5 return { "status": status, "current_phase": status, "round_number": session.current_round, "players_count": players_count, "constraints": { "min_players_to_start": 3, "max_players_mvp": 5, "min_players_reached": min_players_reached, "max_players_allowed": max_players_allowed, }, "readiness": { "question_ready": has_round_question, "scoreboard_ready": status in {GameSession.Status.REVEAL, GameSession.Status.SCOREBOARD, GameSession.Status.FINISHED}, "can_advance_to_next_round": in_scoreboard, }, "host": { "can_start_round": in_lobby and min_players_reached and max_players_allowed, "can_show_question": False, "can_mix_answers": False, "can_calculate_scores": False, "can_reveal_scoreboard": False, "can_start_next_round": in_scoreboard, "can_finish_game": in_scoreboard, }, "player": { "can_join": status in { GameSession.Status.LOBBY, GameSession.Status.LIE, GameSession.Status.GUESS, GameSession.Status.REVEAL, GameSession.Status.SCOREBOARD, }, "can_submit_lie": in_lie and has_round_question, "can_submit_guess": in_guess and has_round_question, "can_view_final_result": in_finished, }, } def build_start_next_round_response( session: GameSession, round_config: RoundConfig, round_question: RoundQuestion, ) -> dict: lie_started_payload = build_lie_started_payload(session, round_config, round_question) return { "session": { "code": session.code, "status": session.status, "current_round": session.current_round, }, "round": { "number": round_config.number, "category": { "slug": round_config.category.slug, "name": round_config.category.name, }, }, "round_question": { "id": round_question.id, "prompt": round_question.question.prompt, "round_number": round_question.round_number, "shown_at": round_question.shown_at.isoformat(), "lie_deadline_at": lie_started_payload["lie_deadline_at"], }, "config": { "lie_seconds": round_config.lie_seconds, }, } def build_start_next_round_phase_event( session: GameSession, round_config: RoundConfig, round_question: RoundQuestion, ) -> dict: return { "name": "phase.lie_started", "payload": build_lie_started_payload(session, round_config, round_question), } def build_scoreboard_phase_event(session: GameSession, leaderboard: list[dict] | None = None) -> dict: return { "name": "phase.scoreboard", "payload": { "leaderboard": leaderboard if leaderboard is not None else build_leaderboard(session), "current_round": session.current_round, }, } def build_reveal_scoreboard_response(session: GameSession, leaderboard: list[dict]) -> dict: return { "session": { "code": session.code, "status": session.status, "current_round": session.current_round, }, "leaderboard": leaderboard, } def build_finish_game_phase_event(session: GameSession) -> dict: leaderboard = build_leaderboard(session) winner = leaderboard[0] if leaderboard else None return { "name": "phase.game_over", "payload": {"winner": winner, "leaderboard": leaderboard}, } def build_finish_game_response(session: GameSession) -> dict: finish_event = build_finish_game_phase_event(session) return { "session": { "code": session.code, "status": GameSession.Status.FINISHED, "current_round": session.current_round, }, "winner": finish_event["payload"]["winner"], "leaderboard": finish_event["payload"]["leaderboard"], }