feat: Sistema completo de fin de juego y pantallas de victoria

Nuevas funcionalidades:
- Pantallas de victoria diferenciadas (NAZIS_WIN / ALLIED_WIN)
  * Diseño visual diferenciado (rojo para Nazis, azul para Aliados)
  * Timer de 30 segundos con auto-finalización
  * Estadísticas de misiones (exitosas vs fracasadas)
  * Opciones para el host: NUEVA PARTIDA o TERMINAR
  * Mensaje de espera para jugadores no-host

- Sistema de reinicio de partida
  * Método restartGame() que resetea todas las variables
  * Reasigna roles y líder aleatorios
  * Vuelve a fase REVEAL_ROLE manteniendo jugadores

- Sistema de finalización y expulsión
  * Método finalizeGame() que expulsa a todos después de 5s
  * Auto-expulsión si el host no decide en 30s
  * Limpieza de partida del servidor

Mejoras en MISSION_RESULT:
- Eliminado oscurecimiento de fondo (bg-transparent)
- Tiempo de visualización aumentado de 5 a 7 segundos
- Ahora se puede ver claramente el tablero con las fichas

Lógica de transiciones:
- 3 misiones fracasadas → NAZIS_WIN
- 3 misiones exitosas → ASSASSIN_PHASE
  * Asesino acierta (mata a Marlene) → NAZIS_WIN
  * Asesino falla → ALLIED_WIN

Archivos modificados:
- shared/types.ts: Nuevas fases NAZIS_WIN y ALLIED_WIN
- server/src/models/Game.ts: Métodos restartGame() y finalizeGame()
- server/src/index.ts: Eventos restart_game y finalize_game
- client/src/hooks/useSocket.ts: Acciones restartGame() y finalizeGame()
- client/src/components/GameBoard.tsx: Renderizado de VictoryScreen
- client/src/components/MissionResult.tsx: Sin oscurecimiento, 7s
- client/src/components/VictoryScreen.tsx: NUEVO componente
This commit is contained in:
Resistencia Dev
2025-12-08 13:41:44 +01:00
parent 774e1b982d
commit b836c53002
7 changed files with 234 additions and 11 deletions

View File

@@ -274,7 +274,32 @@ io.on('connection', (socket) => {
}
});
// 7. DESCONEXIÓN
// 7. REINICIAR PARTIDA
socket.on('restart_game', ({ roomId }) => {
const game = games[roomId];
if (game && game.hostId === socket.id) {
game.restartGame();
io.to(roomId).emit('game_state', game.state);
}
});
// 8. FINALIZAR Y EXPULSAR JUGADORES
socket.on('finalize_game', ({ roomId }) => {
const game = games[roomId];
if (game && game.hostId === socket.id) {
game.finalizeGame();
io.to(roomId).emit('game_state', game.state);
// Esperar 5 segundos y luego eliminar la partida
setTimeout(() => {
delete games[roomId];
// Desconectar a todos los jugadores de la sala
io.in(roomId).socketsLeave(roomId);
}, 5000);
}
});
// 9. DESCONEXIÓN
socket.on('disconnect', () => {
// Buscar en qué partida estaba y sacarlo (opcional, por ahora solo notificamos)
console.log('Desconectado:', socket.id);

View File

@@ -312,10 +312,14 @@ export class Game {
const failures = this.state.questResults.filter(r => r === false).length;
if (failures >= 3) {
this.endGame(Faction.ALEMANES, 'Tres misiones han fracasado.'); // Updated Faction
// Los Nazis ganan directamente
this.state.winner = Faction.ALEMANES;
this.state.phase = GamePhase.NAZIS_WIN;
this.log('¡Los Nazis han ganado! Tres misiones han fracasado.');
} else if (successes >= 3) {
// Los Aliados han completado 3 misiones, pero el Asesino tiene una oportunidad
this.state.phase = GamePhase.ASSASSIN_PHASE;
this.log('¡La Resistencia ha triunfado! Pero el Asesino tiene una última oportunidad...');
this.log('¡La Resistencia ha triunfado! Pero el Francotirador tiene una última oportunidad...');
} else {
// Siguiente ronda
this.state.currentRound++;
@@ -328,10 +332,16 @@ export class Game {
assassinKill(targetId: string) {
const target = this.state.players.find(p => p.id === targetId);
if (target && target.role === Role.MARLENE) { // Updated Role
this.endGame(Faction.ALEMANES, '¡El Asesino ha eliminado a Marlene!'); // Updated Faction and message
if (target && target.role === Role.MARLENE) {
// El Francotirador acierta: Nazis ganan
this.state.winner = Faction.ALEMANES;
this.state.phase = GamePhase.NAZIS_WIN;
this.log('¡El Francotirador ha eliminado a Marlene! Los Nazis ganan.');
} else {
this.endGame(Faction.ALIADOS, 'El Asesino ha fallado. ¡La Resistencia gana!'); // Updated Faction
// El Francotirador falla: Aliados ganan
this.state.winner = Faction.ALIADOS;
this.state.phase = GamePhase.ALLIED_WIN;
this.log('El Francotirador ha fallado. ¡La Resistencia gana!');
}
}
@@ -354,6 +364,43 @@ export class Game {
this.log(`FIN DEL JUEGO. Victoria para ${winner}. Razón: ${reason}`);
}
// Método para reiniciar la partida (volver a REVEAL_ROLE)
restartGame() {
this.log('=== REINICIANDO PARTIDA ===');
// Resetear variables de juego
this.state.currentRound = 1;
this.state.failedVotesCount = 0;
this.state.questResults = [null, null, null, null, null];
this.state.leaderVotes = {};
this.state.proposedTeam = [];
this.state.teamVotes = {};
this.state.missionVotes = [];
this.state.revealedVotes = [];
this.state.missionHistory = [];
this.state.winner = undefined;
// Reasignar roles
const count = this.state.players.length;
const config = GAME_CONFIG[count as keyof typeof GAME_CONFIG];
this.assignRoles(config.good, config.evil);
// Asignar nuevo líder aleatorio
const leaderIndex = Math.floor(Math.random() * count);
this.state.players.forEach((p, i) => p.isLeader = i === leaderIndex);
this.state.currentLeaderId = this.state.players[leaderIndex].id;
// Volver a REVEAL_ROLE
this.state.phase = GamePhase.REVEAL_ROLE;
this.log('Nueva partida iniciada. Revelando roles...');
}
// Método para finalizar definitivamente y preparar expulsión
finalizeGame() {
this.state.phase = GamePhase.GAME_OVER;
this.log('Partida finalizada. Los jugadores serán expulsados.');
}
private log(message: string) {
this.state.history.push(message);
// Mantener solo los últimos 50 mensajes