docs: design doc for fup og fakta game engine + platform architecture
Captures all brainstormed decisions: - Pluggable game cartridge platform (GameDriver interface) - Celery + Redis timer-driven phase transitions - Session owner play/pause/exit controls (no skip) - Escalating scoring per round, incremental reveal scoring - Emoji reactions during guess phase → post-game awards - Relational per-user config presets with game-specific models - Ephemeral game state (no persistence after exit/finish) - Full WebSocket event reference and data lifecycle Also: updated TODO.md (WebSocket done, persisted answers done), created CLAUDE.md, and PROMPT.md for ralph-loop. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,8 @@ from fupogfakta.models import (
|
||||
ScoreEvent,
|
||||
)
|
||||
|
||||
from realtime.broadcast import sync_broadcast_phase_event
|
||||
|
||||
from .i18n import api_error, lobby_i18n_errors
|
||||
|
||||
SESSION_CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"
|
||||
@@ -321,6 +323,16 @@ def start_round(request: HttpRequest, code: str) -> JsonResponse:
|
||||
session.status = GameSession.Status.LIE
|
||||
session.save(update_fields=["status"])
|
||||
|
||||
sync_broadcast_phase_event(
|
||||
session.code,
|
||||
"phase.lie_started",
|
||||
{
|
||||
"round_number": session.current_round,
|
||||
"category": {"slug": round_config.category.slug, "name": round_config.category.name},
|
||||
"lie_seconds": round_config.lie_seconds,
|
||||
},
|
||||
)
|
||||
|
||||
return JsonResponse(
|
||||
{
|
||||
"session": {
|
||||
@@ -407,6 +419,18 @@ def show_question(request: HttpRequest, code: str) -> JsonResponse:
|
||||
|
||||
lie_deadline_at = round_question.shown_at + timedelta(seconds=round_config.lie_seconds)
|
||||
|
||||
sync_broadcast_phase_event(
|
||||
session.code,
|
||||
"phase.question_shown",
|
||||
{
|
||||
"round_question_id": round_question.id,
|
||||
"prompt": question.prompt,
|
||||
"shown_at": round_question.shown_at.isoformat(),
|
||||
"lie_deadline_at": lie_deadline_at.isoformat(),
|
||||
"lie_seconds": round_config.lie_seconds,
|
||||
},
|
||||
)
|
||||
|
||||
return JsonResponse(
|
||||
{
|
||||
"round_question": {
|
||||
@@ -575,6 +599,22 @@ def mix_answers(request: HttpRequest, code: str, round_question_id: int) -> Json
|
||||
locked_session.status = GameSession.Status.GUESS
|
||||
locked_session.save(update_fields=["status"])
|
||||
|
||||
try:
|
||||
_guess_config = RoundConfig.objects.get(session=session, number=session.current_round)
|
||||
_guess_seconds = _guess_config.guess_seconds
|
||||
except RoundConfig.DoesNotExist:
|
||||
_guess_seconds = None
|
||||
|
||||
sync_broadcast_phase_event(
|
||||
session.code,
|
||||
"phase.guess_started",
|
||||
{
|
||||
"round_question_id": round_question.id,
|
||||
"answers": [{"text": t} for t in deduped_answers],
|
||||
"guess_seconds": _guess_seconds,
|
||||
},
|
||||
)
|
||||
|
||||
return JsonResponse(
|
||||
{
|
||||
"session": {
|
||||
@@ -719,6 +759,12 @@ def reveal_scoreboard(request: HttpRequest, code: str) -> JsonResponse:
|
||||
.values("id", "nickname", "score")
|
||||
)
|
||||
|
||||
sync_broadcast_phase_event(
|
||||
session.code,
|
||||
"phase.scoreboard",
|
||||
{"leaderboard": list(leaderboard), "current_round": session.current_round},
|
||||
)
|
||||
|
||||
return JsonResponse(
|
||||
{
|
||||
"session": {
|
||||
@@ -792,6 +838,12 @@ def finish_game(request: HttpRequest, code: str) -> JsonResponse:
|
||||
|
||||
winner = leaderboard[0] if leaderboard else None
|
||||
|
||||
sync_broadcast_phase_event(
|
||||
session.code,
|
||||
"phase.game_over",
|
||||
{"winner": winner, "leaderboard": list(leaderboard)},
|
||||
)
|
||||
|
||||
return JsonResponse(
|
||||
{
|
||||
"session": {
|
||||
@@ -898,6 +950,21 @@ def calculate_scores(request: HttpRequest, code: str, round_question_id: int) ->
|
||||
.values("id", "nickname", "score")
|
||||
)
|
||||
|
||||
score_deltas = [
|
||||
{"player_id": ev.player_id, "delta": ev.delta, "reason": ev.reason}
|
||||
for ev in score_events
|
||||
]
|
||||
|
||||
sync_broadcast_phase_event(
|
||||
session.code,
|
||||
"phase.scores_calculated",
|
||||
{
|
||||
"round_question_id": round_question.id,
|
||||
"score_deltas": score_deltas,
|
||||
"leaderboard": list(leaderboard),
|
||||
},
|
||||
)
|
||||
|
||||
return JsonResponse(
|
||||
{
|
||||
"session": {
|
||||
|
||||
Reference in New Issue
Block a user