2025년 2월 26일

김동환·약 11시간 전
0

오늘의 TIL (Today I Learned)

오늘은 마피아 게임의 웹소켓 게이트웨이에서 핵심적인 밤 페이즈 기능을 구현하였다. 밤 페이즈에서는 마피아의 공격, 경찰의 조사, 의사의 보호 같은 주요 역할들이 수행되며, 이를 효과적으로 관리하기 위해 여러 핸들러를 작성하였다.


1. 게임 종료 처리 (handleEndGame)

  • endGame 이벤트를 구독하여 게임 종료 요청을 받으면 gameService.endGame을 호출하여 게임을 종료한다.
  • 종료된 게임 결과를 해당 방의 모든 클라이언트에게 브로드캐스트한다.
  • 예외 발생 시 클라이언트에게 오류 메시지를 전송한다.

2. 마피아 타겟 선택 (handleMafiaTarget)

  • 마피아가 목표 대상을 선택하면, 해당 정보를 gameService.selectMafiaTarget을 통해 저장한다.
  • 마피아의 밤 행동 완료 상태를 업데이트하고, 모든 밤 행동이 완료되었는지 확인한 후 처리한다.
  • 밤 행동이 모두 완료되었을 경우 gameService.triggerNightProcessing을 호출하여 밤 결과 처리를 진행한다.

3. 경찰 조사 (handlePoliceTarget)

  • 경찰이 특정 사용자를 조사 대상으로 선택하면, gameService.savePoliceTarget을 통해 데이터를 저장한다.
  • 경찰의 밤 행동 완료 상태를 업데이트하고, 밤 행동이 모두 완료되었는지 확인한 후 필요 시 밤 결과 처리를 실행한다.

4. 의사 보호 (handleDoctorTarget)

  • 의사가 특정 사용자를 보호 대상으로 선택하면, gameService.saveDoctorTarget을 통해 데이터를 저장한다.
  • 의사의 밤 행동 완료 상태를 업데이트하고, 밤 행동이 모두 완료되었는지 확인한 후 필요 시 밤 결과 처리를 실행한다.

5. 경찰 조사 결과 전송 (handlePoliceResult)

  • 특정 방의 경찰 조사 결과를 요청하면, 해당 정보를 gameService.getPoliceResult를 통해 조회한다.
  • 경찰이 조사한 대상과 그 사용자의 역할 정보를 요청한 클라이언트에게 전송한다.
  • 조사 대상이 없거나 경찰이 존재하지 않는 경우 오류 메시지를 클라이언트에게 전달한다.

6. 밤 결과 처리 (handleNightResult)

  • 밤 결과를 처리하기 전에 해당 결과가 이미 처리되었는지 확인하여 중복 실행을 방지한다.
  • gameService.processNightResult를 호출하여 밤의 사건들을 정리하고, 해당 결과를 방의 모든 클라이언트에게 브로드캐스트한다.
  • 게임이 종료되었는지 확인하고, 종료되었다면 게임 종료 처리를 실행한다.
  • 게임이 계속 진행될 경우, 10초 후 낮 단계로 전환한다.

오늘의 인사이트

  • 중복 실행 방지: isNightResultProcessedsetNightResultProcessed를 활용하여 밤 결과가 여러 번 실행되지 않도록 방지하였다.
  • 밤 행동 완료 체크: 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: '밤 결과 처리 중 오류 발생.' });
    }
  }
profile
Node.js 7기

0개의 댓글

관련 채용 정보