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>
This commit is contained in:
@@ -141,9 +141,17 @@ class GameManager:
|
||||
|
||||
else:
|
||||
# Original team failed - enable steal
|
||||
failed_team = room["current_team"]
|
||||
room["can_steal"] = True
|
||||
|
||||
# Advance failed team's player index (they had their turn)
|
||||
team_players = room["teams"][failed_team]
|
||||
room["current_player_index"][failed_team] = (
|
||||
room["current_player_index"][failed_team] + 1
|
||||
) % len(team_players)
|
||||
|
||||
# Switch to other team for potential steal
|
||||
room["current_team"] = "B" if room["current_team"] == "A" else "A"
|
||||
room["current_team"] = "B" if failed_team == "A" else "A"
|
||||
|
||||
# Check if game is over (all questions answered)
|
||||
all_answered = all(
|
||||
|
||||
@@ -168,6 +168,23 @@ class RoomManager:
|
||||
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()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import socketio
|
||||
import time
|
||||
import json
|
||||
from datetime import datetime
|
||||
from app.services.room_manager import room_manager
|
||||
from app.services.game_manager import game_manager
|
||||
@@ -80,6 +81,105 @@ def register_socket_events(sio: socketio.AsyncServer):
|
||||
# Notify all players
|
||||
await sio.emit("player_joined", {"room": room}, room=room_code)
|
||||
|
||||
@sio.event
|
||||
async def rejoin_room(sid, data):
|
||||
"""Rejoin an existing room after disconnect/refresh."""
|
||||
room_code = data.get("room_code", "").upper()
|
||||
player_name = data.get("player_name", "")
|
||||
team = data.get("team", "A")
|
||||
|
||||
if not room_code or not player_name:
|
||||
await sio.emit(
|
||||
"rejoin_failed",
|
||||
{"message": "Missing room code or player name"},
|
||||
to=sid
|
||||
)
|
||||
return
|
||||
|
||||
room = await room_manager.get_room(room_code)
|
||||
if not room:
|
||||
await sio.emit(
|
||||
"rejoin_failed",
|
||||
{"message": "Room not found or expired"},
|
||||
to=sid
|
||||
)
|
||||
return
|
||||
|
||||
# Check if player was in this room (by name)
|
||||
player_found = False
|
||||
player_team = None
|
||||
for t in ["A", "B"]:
|
||||
for i, p in enumerate(room["teams"][t]):
|
||||
if p["name"] == player_name:
|
||||
# Update socket_id for this player
|
||||
room["teams"][t][i]["socket_id"] = sid
|
||||
player_found = True
|
||||
player_team = t
|
||||
break
|
||||
if player_found:
|
||||
break
|
||||
|
||||
if not player_found:
|
||||
# Player not found, try to add them back to their preferred team
|
||||
if len(room["teams"][team]) >= 4:
|
||||
# Try other team
|
||||
other_team = "B" if team == "A" else "A"
|
||||
if len(room["teams"][other_team]) >= 4:
|
||||
await sio.emit(
|
||||
"rejoin_failed",
|
||||
{"message": "Room is full"},
|
||||
to=sid
|
||||
)
|
||||
return
|
||||
team = other_team
|
||||
|
||||
room["teams"][team].append({
|
||||
"name": player_name,
|
||||
"team": team,
|
||||
"position": len(room["teams"][team]),
|
||||
"socket_id": sid
|
||||
})
|
||||
player_team = team
|
||||
|
||||
# Update room and player records
|
||||
await room_manager.update_room(room_code, room)
|
||||
await room_manager.update_player(sid, {
|
||||
"name": player_name,
|
||||
"room": room_code,
|
||||
"team": player_team
|
||||
})
|
||||
|
||||
# Also set new player record if it doesn't exist
|
||||
existing = await room_manager.get_player(sid)
|
||||
if not existing:
|
||||
await room_manager.redis.setex(
|
||||
f"player:{sid}",
|
||||
3600 * 3,
|
||||
json.dumps({"name": player_name, "room": room_code, "team": player_team})
|
||||
)
|
||||
|
||||
# Join socket room
|
||||
await sio.enter_room(sid, room_code)
|
||||
|
||||
# Send current game state to rejoining player
|
||||
await sio.emit(
|
||||
"rejoin_success",
|
||||
{
|
||||
"room": room,
|
||||
"player_name": player_name,
|
||||
"team": player_team
|
||||
},
|
||||
to=sid
|
||||
)
|
||||
|
||||
# Notify others that player reconnected
|
||||
await sio.emit(
|
||||
"player_reconnected",
|
||||
{"player_name": player_name, "team": player_team, "room": room},
|
||||
room=room_code,
|
||||
skip_sid=sid
|
||||
)
|
||||
|
||||
@sio.event
|
||||
async def change_team(sid, data):
|
||||
"""Switch player to another team."""
|
||||
@@ -89,6 +189,11 @@ def register_socket_events(sio: socketio.AsyncServer):
|
||||
|
||||
room_code = player["room"]
|
||||
new_team = data.get("team")
|
||||
current_team = player["team"]
|
||||
|
||||
# Don't do anything if already on that team
|
||||
if current_team == new_team:
|
||||
return
|
||||
|
||||
room = await room_manager.get_room(room_code)
|
||||
if not room or len(room["teams"][new_team]) >= 4:
|
||||
@@ -99,12 +204,16 @@ def register_socket_events(sio: socketio.AsyncServer):
|
||||
)
|
||||
return
|
||||
|
||||
# Remove from current team
|
||||
current_team = player["team"]
|
||||
# Remove from current team (by socket_id to be safe)
|
||||
room["teams"][current_team] = [
|
||||
p for p in room["teams"][current_team] if p["socket_id"] != sid
|
||||
]
|
||||
|
||||
# Also remove from new team if somehow already there (prevent duplicates)
|
||||
room["teams"][new_team] = [
|
||||
p for p in room["teams"][new_team] if p["socket_id"] != sid
|
||||
]
|
||||
|
||||
# Add to new team
|
||||
room["teams"][new_team].append({
|
||||
"name": player["name"],
|
||||
@@ -113,7 +222,12 @@ def register_socket_events(sio: socketio.AsyncServer):
|
||||
"socket_id": sid
|
||||
})
|
||||
|
||||
# Update room state
|
||||
await room_manager.update_room(room_code, room)
|
||||
|
||||
# Update player record with new team
|
||||
await room_manager.update_player(sid, {"team": new_team})
|
||||
|
||||
await sio.emit("team_changed", {"room": room}, room=room_code)
|
||||
|
||||
@sio.event
|
||||
|
||||
Reference in New Issue
Block a user