오늘은 마피아 게임의 웹소켓 게이트웨이에서 핵심적인 밤 페이즈 기능을 구현하였다. 밤 페이즈에서는 마피아의 공격, 경찰의 조사, 의사의 보호 같은 주요 역할들이 수행되며, 이를 효과적으로 관리하기 위해 여러 핸들러를 작성하였다.
handleEndGame
)endGame
이벤트를 구독하여 게임 종료 요청을 받으면 gameService.endGame
을 호출하여 게임을 종료한다.handleMafiaTarget
)gameService.selectMafiaTarget
을 통해 저장한다.gameService.triggerNightProcessing
을 호출하여 밤 결과 처리를 진행한다.handlePoliceTarget
)gameService.savePoliceTarget
을 통해 데이터를 저장한다.handleDoctorTarget
)gameService.saveDoctorTarget
을 통해 데이터를 저장한다.handlePoliceResult
)gameService.getPoliceResult
를 통해 조회한다.handleNightResult
)gameService.processNightResult
를 호출하여 밤의 사건들을 정리하고, 해당 결과를 방의 모든 클라이언트에게 브로드캐스트한다.isNightResultProcessed
및 setNightResultProcessed
를 활용하여 밤 결과가 여러 번 실행되지 않도록 방지하였다.checkAllNightActionsCompleted
를 사용하여 마피아, 경찰, 의사의 행동이 모두 완료되었는지 확인한 후 다음 단계를 진행하도록 설계하였다.setTimeout
을 활용하여 일정 시간 후 낮 단계로 전환하는 로직을 추가하였다.RoomEvents
를 좀 더 체계적으로 관리하여 예기치 않은 이벤트 명 오타나 변경에 대비할 필요가 있다.오늘 작업을 통해 밤 페이즈의 흐름이 한층 정리되었으며, 전체적인 게임 진행의 안정성을 높이는 데 집중했다.
@SubscribeMessage('endGame')
async handleEndGame(
@MessageBody() data: { roomId: string },
@ConnectedSocket() client: Socket,
) {
try {
const result = await this.gameService.endGame(data.roomId);
this.server.to(data.roomId).emit(RoomEvents.GAME_END, result);
} catch (error) {
client.emit('error', { message: '게임 종료 처리 중 오류 발생.' });
}
}
// ✅ 마피아 타겟 선택
@SubscribeMessage('ACTION:MAFIA_TARGET')
async handleMafiaTarget(
@MessageBody()
data: { roomId: string; userId: number; targetUserId: number },
@ConnectedSocket() client: Socket,
) {
try {
await this.gameService.selectMafiaTarget(
data.roomId,
data.userId,
data.targetUserId,
);
await this.gameService.setNightActionComplete(data.roomId, 'mafia');
console.log(`🔥 [마피아] 대상 선택 완료: ${data.targetUserId}`);
this.server.to(data.roomId).emit(RoomEvents.ACTION_MAFIA_TARGET, {
message: '마피아 대상 선택 완료',
});
// ✅ 밤 행동 완료 체크 후 처리
const allCompleted = await this.gameService.checkAllNightActionsCompleted(
data.roomId,
);
if (allCompleted) {
await this.gameService.triggerNightProcessing(this.server, data.roomId);
}
} catch (error) {
console.error('🚨 마피아 공격 오류:', error);
client.emit('error', { message: '마피아 공격 처리 중 오류 발생.' });
}
}
// ✅ 경찰 조사
@SubscribeMessage('ACTION:POLICE_TARGET')
async handlePoliceTarget(
@MessageBody()
data: { roomId: string; userId: number; targetUserId: number },
@ConnectedSocket() client: Socket,
) {
try {
await this.gameService.savePoliceTarget(data.roomId, data.targetUserId);
await this.gameService.setNightActionComplete(data.roomId, 'police');
console.log(`🔍 [경찰] 조사 대상 선택 완료: ${data.targetUserId}`);
this.server.to(data.roomId).emit(RoomEvents.ACTION_POLICE_TARGET, {
message: '경찰 조사 완료',
});
// ✅ 밤 행동 완료 체크 후 처리
const allCompleted = await this.gameService.checkAllNightActionsCompleted(
data.roomId,
);
if (allCompleted) {
await this.gameService.triggerNightProcessing(this.server, data.roomId);
}
} catch (error) {
console.error('🚨 경찰 조사 오류:', error);
client.emit('error', { message: '경찰 조사 처리 중 오류 발생.' });
}
}
// ✅ 의사 보호
@SubscribeMessage('ACTION:DOCTOR_TARGET')
async handleDoctorTarget(
@MessageBody()
data: { roomId: string; userId: number; targetUserId: number },
@ConnectedSocket() client: Socket,
) {
try {
await this.gameService.saveDoctorTarget(data.roomId, data.targetUserId);
await this.gameService.setNightActionComplete(data.roomId, 'doctor');
console.log(`💊 [의사] 보호 대상 선택 완료: ${data.targetUserId}`);
this.server.to(data.roomId).emit(RoomEvents.ACTION_DOCTOR_TARGET, {
message: '의사 보호 완료',
});
// ✅ 밤 행동 완료 체크 후 처리
const allCompleted = await this.gameService.checkAllNightActionsCompleted(
data.roomId,
);
if (allCompleted) {
await this.gameService.triggerNightProcessing(this.server, data.roomId);
}
} catch (error) {
console.error('🚨 의사 보호 오류:', error);
client.emit('error', { message: '의사 보호 처리 중 오류 발생.' });
}
}
// 경찰 조사 결과 전송
@SubscribeMessage('REQUEST:POLICE_RESULT')
async handlePoliceResult(
@MessageBody() data: { roomId: string },
@ConnectedSocket() client: Socket,
) {
try {
const gameId = await this.gameService.getCurrentGameId(data.roomId);
if (!gameId) {
throw new BadRequestException(
'현재 진행 중인 게임이 존재하지 않습니다.',
);
}
const result = await this.gameService.getPoliceResult(data.roomId);
if (!result.policeId) {
client.emit('error', { message: '경찰이 존재하지 않습니다.' });
return;
}
if (!result.targetUserId) {
client.emit('error', { message: '조사 대상이 선택되지 않았습니다.' });
return;
}
client.emit(RoomEvents.POLICE_RESULT, {
roomId: data.roomId,
targetUserId: result.targetUserId,
role: result.role,
});
} catch (error) {
client.emit('error', { message: '경찰 조사 결과 전송 중 오류 발생.' });
}
}
// ✅ 밤 결과 처리 후 발표 (이건 유지해야 함)
@SubscribeMessage('PROCESS:NIGHT_RESULT')
async handleNightResult(
@MessageBody() data: { roomId: string },
@ConnectedSocket() client: Socket,
) {
try {
console.log(`🌙 Room ${data.roomId} - NIGHT RESULT PROCESSING`);
// 🔥 이미 밤 결과가 처리된 경우 실행 방지
if (await this.gameService.isNightResultProcessed(data.roomId)) {
console.warn(`⚠️ Room ${data.roomId}: 밤 결과가 이미 처리되었습니다.`);
return;
}
// ✅ 밤 결과 처리 실행
const result = await this.gameService.processNightResult(data.roomId);
console.log(`🛑 밤 결과:`, result);
// ✅ 중복 실행 방지 플래그 저장
await this.gameService.setNightResultProcessed(data.roomId);
// ✅ 밤 결과 브로드캐스트 (1번만 실행)
this.server.to(data.roomId).emit(RoomEvents.ROOM_NIGHT_RESULT, {
roomId: data.roomId,
result,
message: `🌙 밤 결과: ${result.details}`,
});
console.log('밤 결과 브로드캐스트 완료');
// ✅ 게임 종료 체크
const endCheck = await this.gameService.checkEndGame(data.roomId);
if (endCheck.isGameOver) {
console.log(`🏁 게임 종료 감지 - ${endCheck.winningTeam} 팀 승리!`);
const endResult = await this.gameService.endGame(data.roomId);
this.server.to(data.roomId).emit(RoomEvents.GAME_END, endResult);
return; // 게임이 끝났으므로 더 이상 낮 단계로 이동하지 않음
}
// ✅ 낮 단계 전환 (10초 후)
setTimeout(async () => {
const gameId = await this.gameService.getCurrentGameId(data.roomId); // 🔥 gameId 조회 추가
if (!gameId) {
console.error('🚨 낮 단계 전환 실패: gameId가 null임.');
return;
}
await this.gameService.startDayPhase(data.roomId, gameId); // ✅ gameId 전달
this.server.to(data.roomId).emit('message', {
sender: 'system',
message: `🌞 낮이 밝았습니다!`,
});
console.log(`✅ 낮 단계로 이동`);
}, 10000);
} catch (error) {
console.error(`🚨 NIGHT RESULT ERROR:`, error);
client.emit('error', { message: '밤 결과 처리 중 오류 발생.' });
}
}