feat(phase3): Implement complete game logic with WebSocket events

Timer System:
- Add TimerManager service with asyncio for server-side timers
- Support steal time reduction (50% time)
- Automatic timer cancellation on answer

Question Loading:
- Add QuestionService to load daily questions from PostgreSQL
- Generate 8×5 board (categories × difficulties)
- Filter by date_active and approved status

Database Integration:
- Create GameSession in PostgreSQL when game starts
- Update scores during game and on finish
- Store db_session_id in Redis for cross-reference

Replay Integration:
- Save all game events: question_selected, answer_submitted, steal_attempted, steal_passed, game_finished
- Generate unique replay code on game finish

Achievement Integration:
- Initialize PlayerStats in Redis when joining room
- Update stats on every answer (streak, category, speed, etc.)
- Check achievements on game finish for all players

Game Finish:
- Automatic finish when all questions answered
- Manual finish by host
- Emit game_finished with winner, scores, replay_code, achievements

Phase 3 tasks completed:
- F3.1: Timer manager with asyncio
- F3.2: Question service for board loading
- F3.3: GameSession PostgreSQL integration
- F3.4: Replay event saving
- F3.5: Achievement stats tracking
- F3.6: Complete game finish flow

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-26 08:32:22 +00:00
parent 27ac4cb0cf
commit 0141153653
6 changed files with 578 additions and 0 deletions

View File

@@ -168,6 +168,38 @@ class RoomManager:
return json.loads(data)
return None
async def get_player_stats(self, room_code: str, player_name: str) -> Optional[dict]:
"""Obtiene stats de un jugador."""
await self.connect()
data = await self.redis.get(f"stats:{room_code}:{player_name}")
if data:
return json.loads(data)
return None
async def set_player_stats(self, room_code: str, player_name: str, stats: dict) -> None:
"""Guarda stats de un jugador."""
await self.connect()
await self.redis.setex(
f"stats:{room_code}:{player_name}",
3600 * 3,
json.dumps(stats)
)
async def init_player_stats(self, room_code: str, player_name: str) -> dict:
"""Inicializa stats para un nuevo jugador."""
stats = {
"player_name": player_name,
"current_streak": 0,
"total_correct": 0,
"total_steals": 0,
"successful_steals": 0,
"category_correct": {},
"fastest_answer_seconds": None,
"questions_500_correct": 0
}
await self.set_player_stats(room_code, player_name, stats)
return stats
# Singleton instance
room_manager = RoomManager()