Checkpoint: Fix role shuffling bug and improve mission reveal flow

This commit is contained in:
Resistencia Dev
2025-12-06 00:32:09 +01:00
parent ead54e0102
commit 8f95413782
3 changed files with 70 additions and 24 deletions

View File

@@ -63,6 +63,26 @@ export default function GameBoard({ gameState, currentPlayerId, actions }: GameB
actions.voteMission(vote);
};
// Componente de Debug para mostrar facción
const DebugInfo = () => (
<div className="fixed top-2 left-2 bg-black/80 text-white p-3 rounded-lg text-xs z-50 border border-yellow-500 font-mono shadow-xl pointer-events-none">
<div className="font-bold text-yellow-400 mb-1 flex items-center gap-2">
<span>🐛 DEBUG INFO</span>
</div>
<div className="space-y-1">
<div>Fase: <span className="text-cyan-400 font-bold">{gameState.phase}</span></div>
<div>ID: <span className="text-green-400">{currentPlayerId}</span></div>
<div>Facción: <span className={`font-bold ${currentPlayer?.faction === 'spies' ? 'text-red-500' : 'text-blue-400'}`}>
{currentPlayer?.faction || 'UNDEFINED'}
</span></div>
<div>Rol: <span className="text-purple-400">{currentPlayer?.role || 'UNDEFINED'}</span></div>
{gameState.currentLeaderId && (
<div>Líder: <span className="text-yellow-400">{gameState.currentLeaderId === currentPlayerId ? 'TÚ' : gameState.currentLeaderId}</span></div>
)}
</div>
</div>
);
// Coordenadas porcentuales de los hexágonos de misión en el mapa
const missionCoords = [
@@ -208,6 +228,9 @@ export default function GameBoard({ gameState, currentPlayerId, actions }: GameB
if (gameState.phase === 'roll_call' as any) {
return (
<div className="relative w-full h-screen flex flex-col items-center justify-center bg-black overflow-hidden text-white font-mono">
{/* Debug Info */}
<DebugInfo />
<div className="absolute inset-0 z-0">
<Image src="/assets/images/ui/bg_roll_call.png" alt="Resistance HQ" fill className="object-cover" />
<div className="absolute inset-0 bg-black/70" />
@@ -260,6 +283,9 @@ export default function GameBoard({ gameState, currentPlayerId, actions }: GameB
return (
<div className="relative w-full h-screen flex flex-col items-center overflow-hidden">
{/* Debug Info */}
<DebugInfo />
<div className="absolute inset-0 z-0 opacity-40">
<Image src="/assets/images/ui/bg_game.png" alt="Game Background" fill className="object-cover" />
<div className="absolute inset-0 bg-black/60" />
@@ -599,6 +625,7 @@ export default function GameBoard({ gameState, currentPlayerId, actions }: GameB
{gameState.phase === 'mission_reveal' as any && (
<MissionReveal
votes={gameState.revealedVotes || []}
onFinished={() => actions.finishMissionReveal()}
/>
)}

View File

@@ -3,42 +3,59 @@ import { useEffect } from 'react';
interface MissionRevealProps {
votes: boolean[];
onFinished?: () => void;
}
export default function MissionReveal({ votes }: MissionRevealProps) {
// Auto-avanzar después de mostrar todas las cartas
export default function MissionReveal({ votes, onFinished }: MissionRevealProps) {
// Timer de seguridad: 10 segundos y avanza
useEffect(() => {
const timer = setTimeout(() => {
// El servidor avanzará automáticamente
// Este timer es solo para dar tiempo a ver las cartas
}, 3000 + votes.length * 300); // 3s base + 300ms por carta
if (onFinished) onFinished();
}, 10000);
return () => clearTimeout(timer);
}, [votes.length]);
}, [onFinished]);
return (
<motion.div
className="fixed inset-0 flex flex-col items-center justify-center bg-black/90 z-50"
className="fixed inset-0 flex flex-col items-center justify-center bg-black/95 z-50 pointer-events-auto"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
>
<h2 className="text-4xl font-bold text-white mb-8">Resultados de la Misión</h2>
<div className="flex gap-4 justify-center mb-8">
<h2 className="text-5xl font-bold text-white mb-12 uppercase tracking-widest drop-shadow-lg">
Resultado de Misión
</h2>
<div className="flex gap-8 justify-center mb-12 flex-wrap max-w-[90vw]">
{votes.map((vote, idx) => (
<motion.div
key={idx}
className={`w - 24 h - 32 rounded - lg flex items - center justify - center text - 5xl font - bold text - white ${
vote ? 'bg-blue-600' : 'bg-red-600'
} `}
initial={{ scale: 0, rotate: -180 }}
animate={{ scale: 1, rotate: 0 }}
transition={{ delay: idx * 0.3 }}
className={`w-48 h-72 rounded-xl flex items-center justify-center text-7xl font-bold text-white shadow-2xl border-4 ${vote
? 'bg-gradient-to-br from-blue-600 to-blue-900 border-blue-400 shadow-blue-500/50'
: 'bg-gradient-to-br from-red-600 to-red-900 border-red-400 shadow-red-500/50'
}`}
initial={{ scale: 0, rotateY: 180 }}
animate={{ scale: 1, rotateY: 0 }}
transition={{
delay: idx * 0.8, // Más lento para drama
type: "spring",
stiffness: 200,
damping: 20
}}
>
{vote ? '✓' : '✗'}
</motion.div>
))}
</div>
<p className="text-white text-xl">Procesando resultado...</p>
<motion.div
className="text-white text-xl font-mono mt-8"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: votes.length * 0.8 + 1 }}
>
<span className="animate-pulse">Analizando resultado estratégico...</span>
</motion.div>
</motion.div>
);
}

View File

@@ -149,18 +149,18 @@ io.on('connection', (socket) => {
}
});
// 2.3 FINALIZAR ROLL CALL -> PRIMER TURNO DE JUEGO (TEAM_BUILDING)
// 2.3 FINALIZAR ROLL CALL -> PRIMER TURNO DE JUEGO
socket.on('finish_roll_call', ({ roomId }) => {
const game = games[roomId];
if (game && game.hostId === socket.id && game.state.phase === 'roll_call') {
// Ir a VOTE_LEADER (ya que startGame lo inicializa a VOTE_LEADER en el modelo, y nextLeader tambien)
// Solo debemos asegurarnos que el GameState se sincronice.
if (game.startGame()) {
io.to(roomId).emit('game_state', game.state);
}
// ERROR CORREGIDO: No llamar a startGame() de nuevo porque re-baraja los roles.
// Simplemente avanzamos a la fase de votación de líder que ya estaba configurada.
game.state.phase = 'vote_leader' as any;
io.to(roomId).emit('game_state', game.state);
}
});
// 2.4 VOTAR LÍDER
socket.on('vote_leader', ({ roomId, approve }) => {
const game = games[roomId];
@@ -200,12 +200,14 @@ io.on('connection', (socket) => {
// 5.1 FINALIZAR REVELACIÓN DE CARTAS
socket.on('finish_reveal', ({ roomId }) => {
const game = games[roomId];
if (game && game.hostId === socket.id && game.state.phase === 'mission_reveal') {
// Permitir a cualquiera avanzar para evitar bloqueos
if (game && game.state.phase === 'mission_reveal') {
game.finishReveal();
io.to(roomId).emit('game_state', game.state);
}
});
// 5.2 FINALIZAR PANTALLA DE RESULTADO
socket.on('finish_mission_result', ({ roomId }) => {
const game = games[roomId];