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:
61
realtime/consumers.py
Normal file
61
realtime/consumers.py
Normal file
@@ -0,0 +1,61 @@
|
||||
import json
|
||||
from urllib.parse import parse_qs
|
||||
|
||||
from channels.generic.websocket import AsyncJsonWebsocketConsumer
|
||||
|
||||
from fupogfakta.models import Player
|
||||
|
||||
|
||||
class GameConsumer(AsyncJsonWebsocketConsumer):
|
||||
"""
|
||||
WebSocket consumer for a game session.
|
||||
|
||||
URL: ws/game/<session_code>/
|
||||
|
||||
Query params:
|
||||
- session_token: player session token (players only)
|
||||
- role=host: skip token check for host in MVP
|
||||
"""
|
||||
|
||||
async def connect(self):
|
||||
self.session_code = self.scope["url_route"]["kwargs"]["session_code"].upper()
|
||||
self.group_name = f"game_{self.session_code}"
|
||||
|
||||
query_string = self.scope.get("query_string", b"").decode()
|
||||
params = parse_qs(query_string)
|
||||
|
||||
role = params.get("role", [None])[0]
|
||||
session_token = params.get("session_token", [None])[0]
|
||||
|
||||
if role != "host":
|
||||
if not session_token:
|
||||
await self.close(code=4001)
|
||||
return
|
||||
|
||||
try:
|
||||
self.player = await Player.objects.aget(
|
||||
session_token=session_token,
|
||||
session__code=self.session_code,
|
||||
)
|
||||
except Player.DoesNotExist:
|
||||
await self.close(code=4003)
|
||||
return
|
||||
else:
|
||||
self.player = None
|
||||
|
||||
await self.channel_layer.group_add(self.group_name, self.channel_name)
|
||||
await self.accept()
|
||||
|
||||
async def disconnect(self, close_code):
|
||||
if hasattr(self, "group_name"):
|
||||
await self.channel_layer.group_discard(self.group_name, self.channel_name)
|
||||
|
||||
async def receive_json(self, content, **kwargs):
|
||||
if content.get("type") == "ping":
|
||||
await self.send_json({"type": "pong"})
|
||||
|
||||
# --- Group message handlers ---
|
||||
|
||||
async def phase_event(self, event):
|
||||
"""Forward any phase_event broadcast to the WebSocket client."""
|
||||
await self.send_json(event["payload"])
|
||||
Reference in New Issue
Block a user