feat: Actualizar roles y facciones a Francia Ocupada
- Cambiar nombre del juego de 'La Resistencia' a 'Francia Ocupada' - Actualizar roles: Marlene, Capitán Philippe, Partisano, Comandante Schmidt, Francotirador, Agente Doble, Infiltrado, Colaboracionista - Actualizar facciones: Aliados vs Alemanes - Implementar timer de votación de líder con auto-avance - Eliminar componentes de debug
This commit is contained in:
@@ -28,6 +28,9 @@ const io = new Server(server, {
|
||||
// En el futuro esto podría estar en Redis o Postgres
|
||||
const games: Record<string, Game> = {};
|
||||
|
||||
// Almacén de timers para auto-resolución de votaciones
|
||||
const voteTimers: Record<string, NodeJS.Timeout> = {};
|
||||
|
||||
// --- LOBBY MANAGEMENT ---
|
||||
|
||||
const MISSION_NAMES = [
|
||||
@@ -38,6 +41,24 @@ const MISSION_NAMES = [
|
||||
"Operación Eiche", "Operación León Marino", "Operación Urano"
|
||||
];
|
||||
|
||||
// Helper para iniciar timer de votación de líder
|
||||
function startLeaderVoteTimer(roomId: string) {
|
||||
if (voteTimers[roomId]) clearTimeout(voteTimers[roomId]);
|
||||
voteTimers[roomId] = setTimeout(() => {
|
||||
const g = games[roomId];
|
||||
if (g && g.state.phase === 'vote_leader') {
|
||||
// Pasar al siguiente líder sin resolver (rechazar implícitamente)
|
||||
g.nextLeader();
|
||||
io.to(roomId).emit('game_state', g.state);
|
||||
|
||||
// Si sigue en vote_leader (nuevo líder), reiniciar timer
|
||||
if (g.state.phase === 'vote_leader') {
|
||||
startLeaderVoteTimer(roomId);
|
||||
}
|
||||
}
|
||||
}, 11000); // 11 segundos
|
||||
}
|
||||
|
||||
const generateRoomName = () => {
|
||||
const idx = Math.floor(Math.random() * MISSION_NAMES.length);
|
||||
const suffix = Math.floor(100 + Math.random() * 900); // 3 digit code
|
||||
@@ -156,6 +177,10 @@ io.on('connection', (socket) => {
|
||||
// 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;
|
||||
|
||||
// Iniciar timer de 11 segundos para forzar cambio de líder
|
||||
startLeaderVoteTimer(roomId);
|
||||
|
||||
io.to(roomId).emit('game_state', game.state);
|
||||
}
|
||||
});
|
||||
@@ -165,8 +190,23 @@ io.on('connection', (socket) => {
|
||||
socket.on('vote_leader', ({ roomId, approve }) => {
|
||||
const game = games[roomId];
|
||||
if (game) {
|
||||
const previousPhase = game.state.phase;
|
||||
game.voteLeader(socket.id, approve);
|
||||
io.to(roomId).emit('game_state', game.state);
|
||||
|
||||
// Si cambió de fase (líder aprobado o rechazado)
|
||||
if (game.state.phase !== previousPhase) {
|
||||
// Limpiar timer actual
|
||||
if (voteTimers[roomId]) {
|
||||
clearTimeout(voteTimers[roomId]);
|
||||
delete voteTimers[roomId];
|
||||
}
|
||||
|
||||
// Si pasó a vote_leader de nuevo (líder rechazado), iniciar nuevo timer
|
||||
if (game.state.phase === 'vote_leader') {
|
||||
startLeaderVoteTimer(roomId);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -47,9 +47,21 @@ export class Game {
|
||||
}
|
||||
|
||||
addPlayer(id: string, name: string): Player {
|
||||
// Asignar avatar aleatorio persistente (rebel001.jpg - rebel010.jpg)
|
||||
const avatarIdx = Math.floor(Math.random() * 10) + 1;
|
||||
const avatarStr = `rebel${avatarIdx.toString().padStart(3, '0')}.jpg`;
|
||||
// Asignar avatar aleatorio sin repetir (rebel001.jpg - rebel010.jpg)
|
||||
// Obtener avatares ya usados
|
||||
const usedAvatars = this.state.players.map(p => p.avatar);
|
||||
|
||||
// Crear lista de avatares disponibles
|
||||
const allAvatars = Array.from({ length: 10 }, (_, i) =>
|
||||
`rebel${(i + 1).toString().padStart(3, '0')}.jpg`
|
||||
);
|
||||
|
||||
const availableAvatars = allAvatars.filter(av => !usedAvatars.includes(av));
|
||||
|
||||
// Si no hay avatares disponibles (más de 10 jugadores), usar cualquiera
|
||||
const avatarStr = availableAvatars.length > 0
|
||||
? availableAvatars[Math.floor(Math.random() * availableAvatars.length)]
|
||||
: allAvatars[Math.floor(Math.random() * allAvatars.length)];
|
||||
|
||||
const player: Player = {
|
||||
id,
|
||||
@@ -92,13 +104,13 @@ export class Game {
|
||||
// ... assignRoles se mantiene igual ...
|
||||
private assignRoles(goodCount: number, evilCount: number) {
|
||||
// Roles obligatorios
|
||||
const roles: Role[] = [Role.MERLIN, Role.ASSASSIN];
|
||||
const roles: Role[] = [Role.MARLENE, Role.FRANCOTIRADOR]; // Updated roles
|
||||
|
||||
// Rellenar resto de malos
|
||||
for (let i = 0; i < evilCount - 1; i++) roles.push(Role.MINION);
|
||||
for (let i = 0; i < evilCount - 1; i++) roles.push(Role.COLABORACIONISTA); // Updated role
|
||||
|
||||
// Rellenar resto de buenos
|
||||
for (let i = 0; i < goodCount - 1; i++) roles.push(Role.LOYAL_SERVANT);
|
||||
for (let i = 0; i < goodCount - 1; i++) roles.push(Role.PARTISANO); // Updated role
|
||||
|
||||
// Barajar roles
|
||||
const shuffledRoles = roles.sort(() => Math.random() - 0.5);
|
||||
@@ -107,10 +119,10 @@ export class Game {
|
||||
this.state.players.forEach((player, index) => {
|
||||
player.role = shuffledRoles[index];
|
||||
// Asignar facción basada en el rol
|
||||
if ([Role.MERLIN, Role.PERCIVAL, Role.LOYAL_SERVANT].includes(player.role)) {
|
||||
player.faction = Faction.RESISTANCE;
|
||||
if ([Role.MARLENE, Role.PARTISANO].includes(player.role)) { // Updated roles
|
||||
player.faction = Faction.ALIADOS;
|
||||
} else {
|
||||
player.faction = Faction.SPIES;
|
||||
player.faction = Faction.ALEMANES;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -125,12 +137,21 @@ export class Game {
|
||||
}
|
||||
}
|
||||
|
||||
// Método para forzar la resolución de votos (llamado por timeout)
|
||||
forceResolveLeaderVote() {
|
||||
// Registrar votos null para los que no votaron
|
||||
this.state.players.forEach(player => {
|
||||
if (!(player.id in this.state.leaderVotes)) {
|
||||
this.state.leaderVotes[player.id] = null;
|
||||
}
|
||||
});
|
||||
this.resolveLeaderVote();
|
||||
}
|
||||
|
||||
private resolveLeaderVote() {
|
||||
const votes = Object.values(this.state.leaderVotes);
|
||||
const approves = votes.filter(v => v === true).length;
|
||||
const rejects = votes.filter(v => v === false).length;
|
||||
// Los nulos (timeout) no suman a ninguno, o cuentan como reject implícito?
|
||||
// "Si llega a 0... su voto no cuenta". Simplemente no suma.
|
||||
const rejects = votes.filter(v => v === false || v === null).length; // null cuenta como rechazo
|
||||
|
||||
this.log(`Votación de Líder: ${approves} A favor - ${rejects} En contra.`);
|
||||
|
||||
@@ -189,7 +210,7 @@ export class Game {
|
||||
this.state.proposedTeam = [];
|
||||
|
||||
if (this.state.failedVotesCount >= 5) {
|
||||
this.endGame(Faction.SPIES, 'Se han rechazado 5 equipos consecutivos.');
|
||||
this.endGame(Faction.ALEMANES, 'Se han rechazado 5 equipos consecutivos.'); // Updated Faction
|
||||
} else {
|
||||
this.nextLeader(); // Pasa a VOTE_LEADER
|
||||
this.log('El equipo fue rechazado. El liderazgo pasa al siguiente jugador.');
|
||||
@@ -244,10 +265,7 @@ export class Game {
|
||||
|
||||
this.log(`Misión ${round} completada. Revelando votos...`);
|
||||
|
||||
// Auto-avanzar a MISSION_RESULT después de 5 segundos
|
||||
setTimeout(() => {
|
||||
this.finishReveal();
|
||||
}, 5000);
|
||||
// El cliente controlará el avance a MISSION_RESULT con su timer
|
||||
}
|
||||
|
||||
|
||||
@@ -268,7 +286,7 @@ export class Game {
|
||||
const failures = this.state.questResults.filter(r => r === false).length;
|
||||
|
||||
if (failures >= 3) {
|
||||
this.endGame(Faction.SPIES, 'Tres misiones han fracasado.');
|
||||
this.endGame(Faction.ALEMANES, 'Tres misiones han fracasado.'); // Updated Faction
|
||||
} else if (successes >= 3) {
|
||||
this.state.phase = GamePhase.ASSASSIN_PHASE;
|
||||
this.log('¡La Resistencia ha triunfado! Pero el Asesino tiene una última oportunidad...');
|
||||
@@ -284,14 +302,14 @@ export class Game {
|
||||
|
||||
assassinKill(targetId: string) {
|
||||
const target = this.state.players.find(p => p.id === targetId);
|
||||
if (target && target.role === Role.MERLIN) {
|
||||
this.endGame(Faction.SPIES, '¡El Asesino ha eliminado a Marlenne (Merlín)!');
|
||||
if (target && target.role === Role.MARLENE) { // Updated Role
|
||||
this.endGame(Faction.ALEMANES, '¡El Asesino ha eliminado a Marlene!'); // Updated Faction and message
|
||||
} else {
|
||||
this.endGame(Faction.RESISTANCE, 'El Asesino ha fallado. ¡La Resistencia gana!');
|
||||
this.endGame(Faction.ALIADOS, 'El Asesino ha fallado. ¡La Resistencia gana!'); // Updated Faction
|
||||
}
|
||||
}
|
||||
|
||||
private nextLeader() {
|
||||
nextLeader() {
|
||||
const currentIdx = this.state.players.findIndex(p => p.id === this.state.currentLeaderId);
|
||||
const nextIdx = (currentIdx + 1) % this.state.players.length;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user