feat: Implementar sesiones persistentes y botones de salida
- Añadido sistema de sesiones con localStorage - Nuevo hook useSessionStorage para manejar sesiones - Botón de salir de la partida (ExitGameButton) en todas las pantallas del juego - Botón de logout (LogoutButton) solo en el lobby - Evento leave_game en servidor para cerrar partida cuando alguien sale - Evento reconnect_session para reconectar jugadores después de recargar - Actualizado GameBoard para incluir botón de salida - Actualizado page.tsx para manejar sesiones y logout
This commit is contained in:
62
.agent/workflows/sesiones-y-botones.md
Normal file
62
.agent/workflows/sesiones-y-botones.md
Normal file
@@ -0,0 +1,62 @@
|
||||
---
|
||||
description: Implementación de sesiones persistentes y botones de salida
|
||||
---
|
||||
|
||||
# Implementación de Sesiones y Botones de Salida
|
||||
|
||||
## Objetivo
|
||||
Implementar tres mejoras principales:
|
||||
1. Sistema de sesiones persistentes
|
||||
2. Botón de salir de la partida (en todas las pantallas de juego)
|
||||
3. Botón de salir del juego (solo en lobby)
|
||||
|
||||
## Tareas
|
||||
|
||||
### 1. Sistema de Sesiones Persistentes
|
||||
|
||||
**Cliente:**
|
||||
- Crear hook `useSessionStorage` para manejar sesiones
|
||||
- Guardar en localStorage:
|
||||
- `playerName` y `fullPlayerName`
|
||||
- `currentView` (login/lobby/game)
|
||||
- `roomId` si está en una partida
|
||||
- Al cargar la app, verificar si hay sesión activa
|
||||
- Si hay sesión, reconectar al servidor y recuperar estado
|
||||
|
||||
**Servidor:**
|
||||
- Implementar evento `reconnect_session` para validar y recuperar estado
|
||||
- Mantener mapping de `socketId` a `playerId` persistente
|
||||
- Al reconectar, actualizar el socketId del jugador en la partida
|
||||
|
||||
### 2. Botón de Salir de la Partida
|
||||
|
||||
**Cliente:**
|
||||
- Crear componente `ExitGameButton` con icono de flecha
|
||||
- Posicionar arriba a la izquierda
|
||||
- Mostrar en todas las fases del juego (no en lobby)
|
||||
- Al hacer clic, emitir evento `leave_game`
|
||||
|
||||
**Servidor:**
|
||||
- Implementar evento `leave_game`
|
||||
- Notificar a todos los jugadores que alguien salió
|
||||
- Eliminar la partida de la BD
|
||||
- Devolver a todos al lobby
|
||||
|
||||
### 3. Botón de Salir del Juego
|
||||
|
||||
**Cliente:**
|
||||
- Crear componente `LogoutButton` con icono de apagar
|
||||
- Posicionar arriba a la izquierda solo en lobby
|
||||
- Al hacer clic:
|
||||
- Limpiar localStorage
|
||||
- Volver a vista de login
|
||||
- Desconectar socket si está en partida
|
||||
|
||||
## Orden de Implementación
|
||||
|
||||
1. Crear hooks y utilidades para sesiones
|
||||
2. Implementar botón de salir del juego (logout)
|
||||
3. Implementar botón de salir de la partida
|
||||
4. Implementar lógica de reconexión en servidor
|
||||
5. Integrar sistema de sesiones en el cliente
|
||||
6. Pruebas
|
||||
112
SESIONES-IMPLEMENTACION.md
Normal file
112
SESIONES-IMPLEMENTACION.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# Resumen de Implementación: Sesiones y Botones de Salida
|
||||
|
||||
## Cambios Realizados
|
||||
|
||||
### 1. Sistema de Sesiones Persistentes ✅
|
||||
|
||||
#### Cliente
|
||||
- **Nuevo hook**: `client/src/hooks/useSessionStorage.ts`
|
||||
- Maneja el almacenamiento y recuperación de sesiones en localStorage
|
||||
- Guarda: playerName, fullPlayerName, currentView, roomId
|
||||
|
||||
- **Actualización de `page.tsx`**:
|
||||
- Integrado hook `useSessionStorage`
|
||||
- Al iniciar sesión, se guarda la sesión
|
||||
- Al cargar la app, se restaura la sesión si existe
|
||||
- Al cambiar de vista (lobby/game), se actualiza la sesión
|
||||
- Función `handleLogout` para limpiar sesión
|
||||
|
||||
#### Servidor
|
||||
- **Nuevo evento**: `reconnect_session`
|
||||
- Permite a un jugador reconectarse a una partida existente
|
||||
- Actualiza el socketId del jugador en la partida
|
||||
- Envía el estado actualizado al jugador reconectado
|
||||
|
||||
### 2. Botón de Salir de la Partida ✅
|
||||
|
||||
#### Cliente
|
||||
- **Nuevo componente**: `client/src/components/ExitGameButton.tsx`
|
||||
- Botón con icono de flecha
|
||||
- Posicionado arriba a la izquierda (fixed top-4 left-4)
|
||||
- Modal de confirmación antes de salir
|
||||
- Se muestra en todas las fases del juego excepto en pantallas de victoria
|
||||
|
||||
- **Actualización de `GameBoard.tsx`**:
|
||||
- Agregado prop `fullPlayerName`
|
||||
- Integrado `ExitGameButton`
|
||||
- Llama a `actions.leaveGame()` al confirmar
|
||||
|
||||
- **Actualización de `useSocket.ts`**:
|
||||
- Nueva acción: `leaveGame()`
|
||||
- Nuevo listener: `player_left_game`
|
||||
|
||||
#### Servidor
|
||||
- **Nuevo evento**: `leave_game`
|
||||
- Notifica a todos los jugadores que alguien abandonó
|
||||
- Elimina la partida de la base de datos
|
||||
- Limpia timers asociados
|
||||
- Actualiza la lista de salas
|
||||
- Desconecta a todos los jugadores de la sala
|
||||
|
||||
### 3. Botón de Salir del Juego (Logout) ✅
|
||||
|
||||
#### Cliente
|
||||
- **Nuevo componente**: `client/src/components/LogoutButton.tsx`
|
||||
- Botón con icono de apagar
|
||||
- Posicionado arriba a la izquierda (fixed top-4 left-4)
|
||||
- Solo visible en el lobby
|
||||
|
||||
- **Actualización de `page.tsx`**:
|
||||
- Función `handleLogout()`:
|
||||
- Limpia la sesión de localStorage
|
||||
- Vuelve a la vista de login
|
||||
- Si está en una partida, llama a `leaveGame()`
|
||||
|
||||
## Flujo de Uso
|
||||
|
||||
### Sesiones Persistentes
|
||||
1. Usuario se loguea → sesión guardada en localStorage
|
||||
2. Usuario cierra navegador
|
||||
3. Usuario vuelve a abrir → sesión restaurada automáticamente
|
||||
4. Si estaba en una partida, intenta reconectar
|
||||
|
||||
### Salir de la Partida
|
||||
1. Usuario hace clic en botón de flecha (arriba izquierda)
|
||||
2. Aparece modal de confirmación
|
||||
3. Al confirmar:
|
||||
- Servidor notifica a todos: "Jugador X ha abandonado"
|
||||
- Partida eliminada de la BD
|
||||
- Todos vuelven al lobby
|
||||
|
||||
### Salir del Juego (Logout)
|
||||
1. Usuario hace clic en botón de apagar (arriba izquierda, solo en lobby)
|
||||
2. Sesión eliminada de localStorage
|
||||
3. Vuelve a pantalla de login
|
||||
|
||||
## Archivos Modificados
|
||||
|
||||
### Nuevos Archivos
|
||||
- `client/src/hooks/useSessionStorage.ts`
|
||||
- `client/src/components/LogoutButton.tsx`
|
||||
- `client/src/components/ExitGameButton.tsx`
|
||||
- `.agent/workflows/sesiones-y-botones.md`
|
||||
|
||||
### Archivos Modificados
|
||||
- `client/src/app/page.tsx`
|
||||
- `client/src/components/GameBoard.tsx`
|
||||
- `client/src/hooks/useSocket.ts`
|
||||
- `server/src/index.ts`
|
||||
|
||||
## Próximos Pasos
|
||||
|
||||
1. **Probar la aplicación**:
|
||||
- Verificar que las sesiones persisten correctamente
|
||||
- Probar el botón de salir de la partida
|
||||
- Probar el botón de logout
|
||||
- Verificar reconexión después de recargar
|
||||
|
||||
2. **Posibles mejoras**:
|
||||
- Agregar notificación toast cuando alguien abandona
|
||||
- Mejorar el manejo de errores en reconexión
|
||||
- Agregar timeout de sesión (expiración automática)
|
||||
- Guardar más información en la sesión (configuración, preferencias)
|
||||
@@ -2,9 +2,11 @@
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useSocket } from '../hooks/useSocket';
|
||||
import { useSessionStorage } from '../hooks/useSessionStorage';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import Image from 'next/image';
|
||||
import GameBoard from '../components/GameBoard';
|
||||
import LogoutButton from '../components/LogoutButton';
|
||||
import { GameRoom } from '../../../shared/types';
|
||||
|
||||
// Constantes de apellidos
|
||||
@@ -21,6 +23,7 @@ type ViewState = 'login' | 'lobby' | 'game';
|
||||
|
||||
export default function Home() {
|
||||
const { isConnected, gameState, roomsList, actions, socket } = useSocket();
|
||||
const { session, saveSession, updateSession, clearSession } = useSessionStorage();
|
||||
|
||||
// Estados locales de UI
|
||||
const [view, setView] = useState<ViewState>('login');
|
||||
@@ -35,13 +38,32 @@ export default function Home() {
|
||||
const [passwordPromptRoomId, setPasswordPromptRoomId] = useState<string | null>(null);
|
||||
const [joinPassword, setJoinPassword] = useState('');
|
||||
|
||||
// Restaurar sesión al cargar
|
||||
useEffect(() => {
|
||||
if (session && isConnected) {
|
||||
setPlayerName(session.playerName);
|
||||
setFullPlayerName(session.fullPlayerName);
|
||||
setView(session.currentView);
|
||||
|
||||
// Si había una partida activa, intentar reconectar
|
||||
if (session.roomId && session.currentView === 'game') {
|
||||
actions.reconnectSession({ playerName: session.fullPlayerName, roomId: session.roomId });
|
||||
} else if (session.currentView === 'lobby') {
|
||||
actions.refreshRooms();
|
||||
}
|
||||
}
|
||||
}, [session, isConnected]);
|
||||
|
||||
// Efecto para cambiar a vista de juego cuando el servidor nos une
|
||||
useEffect(() => {
|
||||
if (gameState?.roomId) {
|
||||
setView('game');
|
||||
// Guardar en sesión
|
||||
updateSession({ currentView: 'game', roomId: gameState.roomId });
|
||||
} else if (view === 'game' && !gameState) {
|
||||
// Si estábamos en juego y volvemos a null, volver al lobby
|
||||
setView('lobby');
|
||||
updateSession({ currentView: 'lobby', roomId: undefined });
|
||||
}
|
||||
}, [gameState]);
|
||||
|
||||
@@ -50,13 +72,32 @@ export default function Home() {
|
||||
if (playerName) {
|
||||
// Generar apellido aleatorio
|
||||
const randomSurname = SURNAMES[Math.floor(Math.random() * SURNAMES.length)];
|
||||
setFullPlayerName(`${playerName} ${randomSurname}`);
|
||||
const fullName = `${playerName} ${randomSurname}`;
|
||||
setFullPlayerName(fullName);
|
||||
|
||||
// Guardar sesión
|
||||
saveSession({
|
||||
playerName,
|
||||
fullPlayerName: fullName,
|
||||
currentView: 'lobby'
|
||||
});
|
||||
|
||||
setView('lobby');
|
||||
actions.refreshRooms();
|
||||
}
|
||||
};
|
||||
|
||||
const handleLogout = () => {
|
||||
clearSession();
|
||||
setView('login');
|
||||
setPlayerName('');
|
||||
setFullPlayerName('');
|
||||
// Si está en una partida, salir
|
||||
if (gameState?.roomId) {
|
||||
actions.leaveGame();
|
||||
}
|
||||
};
|
||||
|
||||
const handleCreateGame = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
actions.createGame(fullPlayerName, createConfig.maxPlayers, createConfig.password);
|
||||
@@ -149,8 +190,9 @@ export default function Home() {
|
||||
return (
|
||||
<GameBoard
|
||||
gameState={gameState}
|
||||
currentPlayerId={socket.id}
|
||||
currentPlayerId={socket.id || ''}
|
||||
actions={actions}
|
||||
fullPlayerName={fullPlayerName}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -185,6 +227,9 @@ export default function Home() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Botón de Logout - solo en lobby */}
|
||||
{view === 'lobby' && <LogoutButton onClick={handleLogout} />}
|
||||
|
||||
<div className="z-10 w-full flex-1 flex flex-col items-center justify-center p-4">
|
||||
<AnimatePresence mode="wait">
|
||||
|
||||
|
||||
80
client/src/components/ExitGameButton.tsx
Normal file
80
client/src/components/ExitGameButton.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { useState } from 'react';
|
||||
|
||||
interface ExitGameButtonProps {
|
||||
onExit: () => void;
|
||||
playerName: string;
|
||||
}
|
||||
|
||||
export default function ExitGameButton({ onExit, playerName }: ExitGameButtonProps) {
|
||||
const [showConfirm, setShowConfirm] = useState(false);
|
||||
|
||||
const handleConfirmExit = () => {
|
||||
setShowConfirm(false);
|
||||
onExit();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<motion.button
|
||||
onClick={() => setShowConfirm(true)}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="fixed top-4 left-4 z-50 bg-yellow-900/80 hover:bg-yellow-800 text-white p-3 rounded-lg border border-yellow-700/50 backdrop-blur-sm shadow-lg transition-all group"
|
||||
title="Abandonar partida"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-5 w-5"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M9.707 16.707a1 1 0 01-1.414 0l-6-6a1 1 0 010-1.414l6-6a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l4.293 4.293a1 1 0 010 1.414z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<span className="text-xs font-bold uppercase tracking-wider hidden md:inline">Salir</span>
|
||||
</div>
|
||||
</motion.button>
|
||||
|
||||
{/* Modal de confirmación */}
|
||||
{showConfirm && (
|
||||
<div className="fixed inset-0 z-[100] flex items-center justify-center bg-black/80 backdrop-blur-sm">
|
||||
<motion.div
|
||||
initial={{ scale: 0.9, opacity: 0 }}
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
className="bg-zinc-900 p-6 rounded-lg border border-red-700/50 w-full max-w-md mx-4 shadow-2xl"
|
||||
>
|
||||
<h3 className="text-xl font-bold text-red-400 mb-4 uppercase flex items-center gap-2">
|
||||
⚠️ Abandonar Partida
|
||||
</h3>
|
||||
<p className="text-gray-300 mb-2">
|
||||
¿Estás seguro de que quieres abandonar la partida?
|
||||
</p>
|
||||
<p className="text-sm text-gray-400 mb-6">
|
||||
La partida se cerrará para todos los jugadores y se perderá todo el progreso.
|
||||
</p>
|
||||
|
||||
<div className="flex gap-3">
|
||||
<button
|
||||
onClick={() => setShowConfirm(false)}
|
||||
className="flex-1 py-3 bg-gray-700 hover:bg-gray-600 text-white rounded font-bold uppercase text-sm transition-colors"
|
||||
>
|
||||
Cancelar
|
||||
</button>
|
||||
<button
|
||||
onClick={handleConfirmExit}
|
||||
className="flex-1 py-3 bg-red-900 hover:bg-red-800 text-white rounded font-bold uppercase text-sm transition-colors"
|
||||
>
|
||||
Salir
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -5,14 +5,16 @@ import { GameState, GamePhase, Player, GAME_CONFIG, Faction } from '../../../sha
|
||||
import MissionReveal from './MissionReveal';
|
||||
import MissionResult from './MissionResult';
|
||||
import VictoryScreen from './VictoryScreen';
|
||||
import ExitGameButton from './ExitGameButton';
|
||||
|
||||
interface GameBoardProps {
|
||||
gameState: GameState;
|
||||
currentPlayerId: string;
|
||||
actions: any;
|
||||
fullPlayerName: string;
|
||||
}
|
||||
|
||||
export default function GameBoard({ gameState, currentPlayerId, actions }: GameBoardProps) {
|
||||
export default function GameBoard({ gameState, currentPlayerId, actions, fullPlayerName }: GameBoardProps) {
|
||||
const [selectedTeam, setSelectedTeam] = useState<string[]>([]);
|
||||
|
||||
// Hooks para FASE REVEAL ROLE
|
||||
@@ -298,6 +300,14 @@ export default function GameBoard({ gameState, currentPlayerId, actions }: GameB
|
||||
|
||||
return (
|
||||
<div className="relative w-full h-screen flex flex-col overflow-hidden">
|
||||
{/* Botón de Salir de la Partida - No mostrar en pantallas de victoria */}
|
||||
{gameState.phase !== GamePhase.ALLIED_WIN && gameState.phase !== GamePhase.NAZIS_WIN && (
|
||||
<ExitGameButton
|
||||
onExit={() => actions.leaveGame()}
|
||||
playerName={fullPlayerName}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Fondo */}
|
||||
<div className="absolute inset-0 z-0 opacity-40">
|
||||
<Image
|
||||
|
||||
33
client/src/components/LogoutButton.tsx
Normal file
33
client/src/components/LogoutButton.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
interface LogoutButtonProps {
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export default function LogoutButton({ onClick }: LogoutButtonProps) {
|
||||
return (
|
||||
<motion.button
|
||||
onClick={onClick}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="fixed top-4 left-4 z-50 bg-red-900/80 hover:bg-red-800 text-white p-3 rounded-lg border border-red-700/50 backdrop-blur-sm shadow-lg transition-all group"
|
||||
title="Salir del juego"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-5 w-5"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8 7a1 1 0 00-1 1v4a1 1 0 001 1h4a1 1 0 001-1V8a1 1 0 00-1-1H8z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<span className="text-xs font-bold uppercase tracking-wider hidden md:inline">Salir</span>
|
||||
</div>
|
||||
</motion.button>
|
||||
);
|
||||
}
|
||||
53
client/src/hooks/useSessionStorage.ts
Normal file
53
client/src/hooks/useSessionStorage.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
interface SessionData {
|
||||
playerName: string;
|
||||
fullPlayerName: string;
|
||||
currentView: 'login' | 'lobby' | 'game';
|
||||
roomId?: string;
|
||||
}
|
||||
|
||||
export function useSessionStorage() {
|
||||
const [session, setSession] = useState<SessionData | null>(null);
|
||||
|
||||
// Cargar sesión al iniciar
|
||||
useEffect(() => {
|
||||
const savedSession = localStorage.getItem('resistencia_session');
|
||||
if (savedSession) {
|
||||
try {
|
||||
const parsed = JSON.parse(savedSession);
|
||||
setSession(parsed);
|
||||
} catch (e) {
|
||||
console.error('Error parsing session:', e);
|
||||
localStorage.removeItem('resistencia_session');
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Guardar sesión
|
||||
const saveSession = (data: SessionData) => {
|
||||
localStorage.setItem('resistencia_session', JSON.stringify(data));
|
||||
setSession(data);
|
||||
};
|
||||
|
||||
// Actualizar sesión parcialmente
|
||||
const updateSession = (partial: Partial<SessionData>) => {
|
||||
if (session) {
|
||||
const updated = { ...session, ...partial };
|
||||
saveSession(updated);
|
||||
}
|
||||
};
|
||||
|
||||
// Limpiar sesión
|
||||
const clearSession = () => {
|
||||
localStorage.removeItem('resistencia_session');
|
||||
setSession(null);
|
||||
};
|
||||
|
||||
return {
|
||||
session,
|
||||
saveSession,
|
||||
updateSession,
|
||||
clearSession
|
||||
};
|
||||
}
|
||||
@@ -50,6 +50,12 @@ export const useSocket = () => {
|
||||
setGameState(null); // Resetear estado para volver al lobby
|
||||
});
|
||||
|
||||
// Manejar cuando un jugador abandona la partida
|
||||
socketInstance.on('player_left_game', ({ playerName }: { playerName: string }) => {
|
||||
console.log(`${playerName} ha abandonado la partida`);
|
||||
// El servidor ya habrá cerrado la partida, solo mostramos mensaje
|
||||
});
|
||||
|
||||
setSocket(socketInstance);
|
||||
|
||||
return () => {
|
||||
@@ -90,6 +96,14 @@ export const useSocket = () => {
|
||||
socket?.emit('assassin_kill', { roomId: gameState?.roomId, targetId });
|
||||
};
|
||||
|
||||
const leaveGame = () => {
|
||||
socket?.emit('leave_game', { roomId: gameState?.roomId });
|
||||
};
|
||||
|
||||
const reconnectSession = (sessionData: { playerName: string; roomId?: string }) => {
|
||||
socket?.emit('reconnect_session', sessionData);
|
||||
};
|
||||
|
||||
return {
|
||||
socket,
|
||||
isConnected,
|
||||
@@ -105,6 +119,8 @@ export const useSocket = () => {
|
||||
voteMission,
|
||||
voteLeader: (approve: boolean) => socket?.emit('vote_leader', { roomId: gameState?.roomId, approve }),
|
||||
assassinKill,
|
||||
leaveGame,
|
||||
reconnectSession,
|
||||
finishIntro: () => socket?.emit('finish_intro', { roomId: gameState?.roomId }),
|
||||
finishReveal: () => socket?.emit('finish_reveal', { roomId: gameState?.roomId }),
|
||||
finishRollCall: () => socket?.emit('finish_roll_call', { roomId: gameState?.roomId }),
|
||||
|
||||
@@ -301,7 +301,89 @@ io.on('connection', (socket) => {
|
||||
}
|
||||
});
|
||||
|
||||
// 9. DESCONEXIÓN
|
||||
// 8. ABANDONAR PARTIDA (cualquier jugador)
|
||||
socket.on('leave_game', ({ roomId }) => {
|
||||
const game = games[roomId];
|
||||
if (game) {
|
||||
// Encontrar el jugador que se va
|
||||
const leavingPlayer = game.state.players.find(p => p.id === socket.id);
|
||||
const playerName = leavingPlayer?.name || 'Un jugador';
|
||||
|
||||
// Notificar a todos los jugadores
|
||||
io.to(roomId).emit('player_left_game', { playerName });
|
||||
io.to(roomId).emit('game_finalized');
|
||||
|
||||
// Eliminar la partida de la base de datos
|
||||
delete games[roomId];
|
||||
|
||||
// Limpiar timer si existe
|
||||
if (voteTimers[roomId]) {
|
||||
clearTimeout(voteTimers[roomId]);
|
||||
delete voteTimers[roomId];
|
||||
}
|
||||
|
||||
// Actualizar lista de salas
|
||||
io.emit('rooms_list', getRoomsList());
|
||||
|
||||
// Desconectar a todos de la sala
|
||||
io.in(roomId).socketsLeave(roomId);
|
||||
|
||||
console.log(`[LEAVE_GAME] ${playerName} abandonó la partida ${roomId}. Partida eliminada.`);
|
||||
}
|
||||
});
|
||||
|
||||
// 9. RECONECTAR SESIÓN
|
||||
socket.on('reconnect_session', ({ playerName, roomId }) => {
|
||||
console.log(`[RECONNECT_SESSION] Intento de reconexión: ${playerName} a sala ${roomId}`);
|
||||
|
||||
if (roomId) {
|
||||
const game = games[roomId];
|
||||
if (game) {
|
||||
// Buscar si el jugador existe en la partida
|
||||
const existingPlayer = game.state.players.find(p => p.name === playerName);
|
||||
|
||||
if (existingPlayer) {
|
||||
// Actualizar el socket ID del jugador
|
||||
existingPlayer.id = socket.id;
|
||||
|
||||
// Unir al socket a la sala
|
||||
socket.join(roomId);
|
||||
|
||||
// Enviar estado actualizado
|
||||
socket.emit('game_joined', { roomId, state: game.state });
|
||||
io.to(roomId).emit('game_state', game.state);
|
||||
|
||||
console.log(`[RECONNECT_SESSION] ${playerName} reconectado exitosamente a ${roomId}`);
|
||||
} else {
|
||||
console.log(`[RECONNECT_SESSION] Jugador ${playerName} no encontrado en partida ${roomId}`);
|
||||
socket.emit('error', 'No se pudo reconectar a la partida');
|
||||
}
|
||||
} else {
|
||||
console.log(`[RECONNECT_SESSION] Partida ${roomId} no existe`);
|
||||
socket.emit('error', 'La partida ya no existe');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 10. FINALIZAR Y EXPULSAR JUGADORES (solo host)
|
||||
socket.on('finalize_game', ({ roomId }) => {
|
||||
const game = games[roomId];
|
||||
if (game && game.hostId === socket.id) {
|
||||
// Notificar a todos los jugadores que la partida ha sido finalizada
|
||||
io.to(roomId).emit('game_finalized');
|
||||
|
||||
// Eliminar la partida inmediatamente del registro
|
||||
delete games[roomId];
|
||||
|
||||
// Actualizar lista de salas para todos los clientes
|
||||
io.emit('rooms_list', getRoomsList());
|
||||
|
||||
// Desconectar a todos los jugadores de la sala
|
||||
io.in(roomId).socketsLeave(roomId);
|
||||
}
|
||||
});
|
||||
|
||||
// 11. DESCONEXIÓN
|
||||
socket.on('disconnect', () => {
|
||||
// Buscar en qué partida estaba y sacarlo (opcional, por ahora solo notificamos)
|
||||
console.log('Desconectado:', socket.id);
|
||||
|
||||
Reference in New Issue
Block a user