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()