Checkpoint: Fix role shuffling bug and improve mission reveal flow
This commit is contained in:
@@ -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()}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
|
||||
Reference in New Issue
Block a user