116 lines
3.8 KiB
Python
116 lines
3.8 KiB
Python
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
|