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);
|
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
|
// Coordenadas porcentuales de los hexágonos de misión en el mapa
|
||||||
const missionCoords = [
|
const missionCoords = [
|
||||||
@@ -208,6 +228,9 @@ export default function GameBoard({ gameState, currentPlayerId, actions }: GameB
|
|||||||
if (gameState.phase === 'roll_call' as any) {
|
if (gameState.phase === 'roll_call' as any) {
|
||||||
return (
|
return (
|
||||||
<div className="relative w-full h-screen flex flex-col items-center justify-center bg-black overflow-hidden text-white font-mono">
|
<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">
|
<div className="absolute inset-0 z-0">
|
||||||
<Image src="/assets/images/ui/bg_roll_call.png" alt="Resistance HQ" fill className="object-cover" />
|
<Image src="/assets/images/ui/bg_roll_call.png" alt="Resistance HQ" fill className="object-cover" />
|
||||||
<div className="absolute inset-0 bg-black/70" />
|
<div className="absolute inset-0 bg-black/70" />
|
||||||
@@ -260,6 +283,9 @@ export default function GameBoard({ gameState, currentPlayerId, actions }: GameB
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative w-full h-screen flex flex-col items-center overflow-hidden">
|
<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">
|
<div className="absolute inset-0 z-0 opacity-40">
|
||||||
<Image src="/assets/images/ui/bg_game.png" alt="Game Background" fill className="object-cover" />
|
<Image src="/assets/images/ui/bg_game.png" alt="Game Background" fill className="object-cover" />
|
||||||
<div className="absolute inset-0 bg-black/60" />
|
<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 && (
|
{gameState.phase === 'mission_reveal' as any && (
|
||||||
<MissionReveal
|
<MissionReveal
|
||||||
votes={gameState.revealedVotes || []}
|
votes={gameState.revealedVotes || []}
|
||||||
|
onFinished={() => actions.finishMissionReveal()}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -3,42 +3,59 @@ import { useEffect } from 'react';
|
|||||||
|
|
||||||
interface MissionRevealProps {
|
interface MissionRevealProps {
|
||||||
votes: boolean[];
|
votes: boolean[];
|
||||||
|
onFinished?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function MissionReveal({ votes }: MissionRevealProps) {
|
export default function MissionReveal({ votes, onFinished }: MissionRevealProps) {
|
||||||
// Auto-avanzar después de mostrar todas las cartas
|
// Timer de seguridad: 10 segundos y avanza
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
// El servidor avanzará automáticamente
|
if (onFinished) onFinished();
|
||||||
// Este timer es solo para dar tiempo a ver las cartas
|
}, 10000);
|
||||||
}, 3000 + votes.length * 300); // 3s base + 300ms por carta
|
|
||||||
|
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}, [votes.length]);
|
}, [onFinished]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<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 }}
|
initial={{ opacity: 0 }}
|
||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1 }}
|
||||||
>
|
>
|
||||||
<h2 className="text-4xl font-bold text-white mb-8">Resultados de la Misión</h2>
|
<h2 className="text-5xl font-bold text-white mb-12 uppercase tracking-widest drop-shadow-lg">
|
||||||
<div className="flex gap-4 justify-center mb-8">
|
Resultado de Misión
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="flex gap-8 justify-center mb-12 flex-wrap max-w-[90vw]">
|
||||||
{votes.map((vote, idx) => (
|
{votes.map((vote, idx) => (
|
||||||
<motion.div
|
<motion.div
|
||||||
key={idx}
|
key={idx}
|
||||||
className={`w - 24 h - 32 rounded - lg flex items - center justify - center text - 5xl font - bold text - white ${
|
className={`w-48 h-72 rounded-xl flex items-center justify-center text-7xl font-bold text-white shadow-2xl border-4 ${vote
|
||||||
vote ? 'bg-blue-600' : 'bg-red-600'
|
? '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, rotate: -180 }}
|
}`}
|
||||||
animate={{ scale: 1, rotate: 0 }}
|
initial={{ scale: 0, rotateY: 180 }}
|
||||||
transition={{ delay: idx * 0.3 }}
|
animate={{ scale: 1, rotateY: 0 }}
|
||||||
|
transition={{
|
||||||
|
delay: idx * 0.8, // Más lento para drama
|
||||||
|
type: "spring",
|
||||||
|
stiffness: 200,
|
||||||
|
damping: 20
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{vote ? '✓' : '✗'}
|
{vote ? '✓' : '✗'}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
))}
|
))}
|
||||||
</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>
|
</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 }) => {
|
socket.on('finish_roll_call', ({ roomId }) => {
|
||||||
const game = games[roomId];
|
const game = games[roomId];
|
||||||
if (game && game.hostId === socket.id && game.state.phase === 'roll_call') {
|
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)
|
// ERROR CORREGIDO: No llamar a startGame() de nuevo porque re-baraja los roles.
|
||||||
// Solo debemos asegurarnos que el GameState se sincronice.
|
// Simplemente avanzamos a la fase de votación de líder que ya estaba configurada.
|
||||||
if (game.startGame()) {
|
game.state.phase = 'vote_leader' as any;
|
||||||
io.to(roomId).emit('game_state', game.state);
|
io.to(roomId).emit('game_state', game.state);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// 2.4 VOTAR LÍDER
|
// 2.4 VOTAR LÍDER
|
||||||
socket.on('vote_leader', ({ roomId, approve }) => {
|
socket.on('vote_leader', ({ roomId, approve }) => {
|
||||||
const game = games[roomId];
|
const game = games[roomId];
|
||||||
@@ -200,12 +200,14 @@ io.on('connection', (socket) => {
|
|||||||
// 5.1 FINALIZAR REVELACIÓN DE CARTAS
|
// 5.1 FINALIZAR REVELACIÓN DE CARTAS
|
||||||
socket.on('finish_reveal', ({ roomId }) => {
|
socket.on('finish_reveal', ({ roomId }) => {
|
||||||
const game = games[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();
|
game.finishReveal();
|
||||||
io.to(roomId).emit('game_state', game.state);
|
io.to(roomId).emit('game_state', game.state);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// 5.2 FINALIZAR PANTALLA DE RESULTADO
|
// 5.2 FINALIZAR PANTALLA DE RESULTADO
|
||||||
socket.on('finish_mission_result', ({ roomId }) => {
|
socket.on('finish_mission_result', ({ roomId }) => {
|
||||||
const game = games[roomId];
|
const game = games[roomId];
|
||||||
|
|||||||
Reference in New Issue
Block a user