Files
Trivy/backend/app/services/room_manager.py
consultoria-as 112f489e40 feat: reconexión de sesión + 6 nuevas categorías + corrección de bugs
- Añade sistema de reconexión tras refresh/cierre del navegador
  - Persistencia de sesión en localStorage (3h TTL)
  - Banner de reconexión en Home
  - Evento rejoin_room en backend

- Nuevas categorías: Series TV, Marvel/DC, Disney, Memes, Pokémon, Mitología

- Correcciones de bugs:
  - Fix: juego bloqueado al fallar robo (steal decision)
  - Fix: jugador duplicado al cambiar de equipo
  - Fix: rotación incorrecta de turno tras fallo

- Config: soporte para Cloudflare tunnel (allowedHosts)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 01:53:32 +00:00

223 lines
6.4 KiB
Python

import json
import random
import string
from typing import Optional
import redis.asyncio as redis
from app.config import get_settings
settings = get_settings()
class RoomManager:
def __init__(self):
self.redis: Optional[redis.Redis] = None
async def connect(self):
if not self.redis:
self.redis = await redis.from_url(settings.redis_url)
async def disconnect(self):
if self.redis:
await self.redis.close()
def _generate_room_code(self) -> str:
"""Generate a 6-character room code."""
return ''.join(random.choices(string.ascii_uppercase + string.digits, k=6))
async def create_room(self, player_name: str, socket_id: str) -> dict:
"""Create a new game room."""
await self.connect()
# Generate unique room code
room_code = self._generate_room_code()
while await self.redis.exists(f"room:{room_code}"):
room_code = self._generate_room_code()
# Create room state
room_state = {
"code": room_code,
"status": "waiting",
"host": player_name,
"teams": {
"A": [],
"B": []
},
"current_team": None,
"current_player_index": {"A": 0, "B": 0},
"current_question": None,
"can_steal": False,
"scores": {"A": 0, "B": 0},
"questions_used": [],
"board": {}
}
# Save room state
await self.redis.setex(
f"room:{room_code}",
3600 * 3, # 3 hours TTL
json.dumps(room_state)
)
# Add player to room
room = await self.add_player(room_code, player_name, "A", socket_id)
return room
async def get_room(self, room_code: str) -> Optional[dict]:
"""Get room state by code."""
await self.connect()
data = await self.redis.get(f"room:{room_code}")
if data:
return json.loads(data)
return None
async def update_room(self, room_code: str, room_state: dict) -> bool:
"""Update room state."""
await self.connect()
await self.redis.setex(
f"room:{room_code}",
3600 * 3,
json.dumps(room_state)
)
return True
async def add_player(
self,
room_code: str,
player_name: str,
team: str,
socket_id: str
) -> Optional[dict]:
"""Add a player to a room."""
room = await self.get_room(room_code)
if not room:
return None
# Check if team is full
if len(room["teams"][team]) >= 4:
return None
# Check if name is taken
for t in ["A", "B"]:
for p in room["teams"][t]:
if p["name"].lower() == player_name.lower():
return None
# Add player
player = {
"name": player_name,
"team": team,
"position": len(room["teams"][team]),
"socket_id": socket_id
}
room["teams"][team].append(player)
# Save player mapping
await self.redis.setex(
f"player:{socket_id}",
3600 * 3,
json.dumps({"name": player_name, "room": room_code, "team": team})
)
await self.update_room(room_code, room)
return room
async def remove_player(self, socket_id: str) -> Optional[dict]:
"""Remove a player from their room."""
await self.connect()
# Get player info
player_data = await self.redis.get(f"player:{socket_id}")
if not player_data:
return None
player_info = json.loads(player_data)
room_code = player_info["room"]
team = player_info["team"]
# Get room
room = await self.get_room(room_code)
if not room:
return None
# Remove player from team
room["teams"][team] = [
p for p in room["teams"][team] if p["socket_id"] != socket_id
]
# Update positions
for i, p in enumerate(room["teams"][team]):
p["position"] = i
# Delete player mapping
await self.redis.delete(f"player:{socket_id}")
# If room is empty, delete it
if not room["teams"]["A"] and not room["teams"]["B"]:
await self.redis.delete(f"room:{room_code}")
return None
await self.update_room(room_code, room)
return room
async def get_player(self, socket_id: str) -> Optional[dict]:
"""Get player info by socket ID."""
await self.connect()
data = await self.redis.get(f"player:{socket_id}")
if data:
return json.loads(data)
return None
async def update_player(self, socket_id: str, updates: dict) -> Optional[dict]:
"""Update player info."""
await self.connect()
data = await self.redis.get(f"player:{socket_id}")
if not data:
return None
player = json.loads(data)
player.update(updates)
await self.redis.setex(
f"player:{socket_id}",
3600 * 3,
json.dumps(player)
)
return player
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()