import random from .models import GameSession, Player, Question, RoundConfig, RoundQuestion, ScoreEvent def get_current_round_question(session: GameSession) -> RoundQuestion | None: return ( RoundQuestion.objects.filter(session=session, round_number=session.current_round) .select_related("question") .order_by("-id") .first() ) def select_round_question(session: GameSession, round_config: RoundConfig) -> RoundQuestion: existing_round_question = get_current_round_question(session) if existing_round_question is not None: return existing_round_question used_question_ids = RoundQuestion.objects.filter(session=session).values_list("question_id", flat=True) available_questions = Question.objects.filter( category=round_config.category, is_active=True, ).exclude(pk__in=used_question_ids) if not available_questions.exists(): raise ValueError("no_available_questions") question = random.choice(list(available_questions)) return RoundQuestion.objects.create( session=session, round_number=session.current_round, question=question, correct_answer=question.correct_answer, ) def prepare_mixed_answers(round_question: RoundQuestion) -> list[str]: deduped_answers = list(round_question.mixed_answers or []) if deduped_answers: return deduped_answers lie_texts = list(round_question.lies.values_list("text", flat=True)) seen = set() for text in [round_question.correct_answer, *lie_texts]: normalized = text.strip().casefold() if not normalized or normalized in seen: continue seen.add(normalized) deduped_answers.append(text.strip()) if len(deduped_answers) < 2: raise ValueError("not_enough_answers_to_mix") random.shuffle(deduped_answers) round_question.mixed_answers = deduped_answers round_question.save(update_fields=["mixed_answers"]) return deduped_answers def resolve_scores( session: GameSession, round_question: RoundQuestion, round_config: RoundConfig, ) -> tuple[list[ScoreEvent], list[dict]]: guesses = list(round_question.guesses.select_related("player")) if not guesses: raise ValueError("no_guesses_submitted") bluff_counts: dict[int, int] = {} for guess in guesses: if guess.fooled_player_id: bluff_counts[guess.fooled_player_id] = bluff_counts.get(guess.fooled_player_id, 0) + 1 score_events = [] for guess in guesses: if guess.is_correct: guess.player.score += round_config.points_correct guess.player.save(update_fields=["score"]) score_events.append( ScoreEvent( session=session, player=guess.player, delta=round_config.points_correct, reason="guess_correct", meta={"round_question_id": round_question.id, "guess_id": guess.id}, ) ) for player_id, fooled_count in bluff_counts.items(): delta = fooled_count * round_config.points_bluff player = Player.objects.get(pk=player_id, session=session) player.score += delta player.save(update_fields=["score"]) score_events.append( ScoreEvent( session=session, player=player, delta=delta, reason="bluff_success", meta={"round_question_id": round_question.id, "fooled_count": fooled_count}, ) ) ScoreEvent.objects.bulk_create(score_events) leaderboard = list( Player.objects.filter(session=session) .order_by("-score", "nickname") .values("id", "nickname", "score") ) return score_events, leaderboard