프로젝트 Final

19일 일지

rabbit97 2024. 11. 19. 21:18

# 오늘의 구현 로직 기록

 

카드 버리기 관련 로직

 

이 부분은 구현 중 회의 내용으로 노티피케이션 파일을 어떻게 관리할 것인가에 대한 내용 중 구조가 좀 바뀔 것 같아서 일단 커밋하고 구현 보류

import { getGameSessionBySocket } from '../../sessions/game.session.js';
import { createResponse } from '../../utils/packet/response/createResponse.js';
import { PACKET_TYPE } from '../../constants/header.js';

const packetType = PACKET_TYPE;

// 카드 버리기 요청 핸들러
const handleDestroyCardRequest = async (socket, payload) => {
  try {
    const { destroyCards } = payload;

    const gameSession = getGameSessionBySocket(socket);
    if (!gameSession) {
      throw new Error('해당 유저의 게임 세션이 존재하지 않습니다.');
    }

    const currentUser = gameSession.users.find((user) => user.socket === socket);
    if (!currentUser) {
      throw new Error('현재 유저가 존재하지 않습니다.');
    }

    // 게임 룰 - 클라이언트에서 턴이 끝날때 요청하는 핸들러, 카드는 현재 체력보다 많을 수 없음
    // (어떻게???) 일단 임시로 저장하고 다른 로직 먼저 보기
    // 턴이 끝나면 현재 체력많큼 카드 수 맞추기? (만약에 현재 카드 수가 체력보다 많다면으로 진행)
    // 남은 카드들의 갯수를 구하고
    // (리듀스 사용 - 핸드에 들고있는 카드 배열을 반복을 도는데 카운트만큼 누적값에 더해서 총 값을 구한다면?)
    const handCards = currentUser.character.handCards;
    let remainingCards = handCards.reduce((total, card) => total + card.count, 0);

    for (let i = 0; i < destroyCards.length; i++) {
      const destroyCard = destroyCards[i];
      const cardIndex = handCards.findIndex((card) => card.type === destroyCard.type);

      // 버릴 카드를 현재 유저의 손에서 제거
      // 클라이언트에서 보낸 디스트로이 카드의 정보를 가지고 유저의 현재 인덱스에서 제거해야함
      // (어떻게?) 유저 핸드를 배열 안에 인덱스 단위로 반복문으로 조회 후 카드 위치를 선택 후 제거?
      if (cardIndex !== -1) {
        const handCard = handCards[cardIndex];
        if (handCard.count >= destroyCard.count) {
          // 남겨야 하는 카드 수를 초과하지 않도록 체크
          if (remainingCards - destroyCard.count >= minCardsToKeep) {
            handCard.count -= destroyCard.count;
            remainingCards -= destroyCard.count;

            if (handCard.count === 0) {
              handCards.splice(cardIndex, 1);
            }
          } else {
            const allowableDiscardCount = remainingCards - minCardsToKeep;
            if (allowableDiscardCount > 0) {
              handCard.count -= allowableDiscardCount;
              remainingCards -= allowableDiscardCount;

              if (handCard.count === 0) {
                handCards.splice(cardIndex, 1);
              }
              throw new Error(
                `체력만큼의 카드(최소 ${minCardsToKeep}장)를 유지해야 합니다. 남은 ${allowableDiscardCount}장의 카드만 버릴 수 있습니다.`,
              );
            } else {
              throw new Error(
                `체력만큼의 카드(최소 ${minCardsToKeep}장)를 유지해야 합니다. 카드를 버릴 수 없습니다.`,
              );
            }
          }
        } else {
          throw new Error(`유저의 손에 해당 수량의 ${destroyCard.type} 카드가 존재하지 않습니다.`);
        }
      } else {
        throw new Error(`카드 타입 ${destroyCard.type}이 현재 유저의 손에 없습니다.`);
      }
    }

    // 요청을 보낸 소켓에 성공 여부 및 최신 손 카드 리스트 보내기
    const destroyCardResponse = createResponse(packetType.DESTROY_CARD_RESPONSE, 0, {
      handCards,
    });
    socket.write(destroyCardResponse);
  } catch (error) {
    console.error('카드 버리기 중 에러 발생:', error.message);
  }
};

export default handleDestroyCardRequest;

 

디버프 관련 핸들러 로직

import { getGameSessionBySocket } from '../../sessions/game.session.js';
import { createResponse } from '../../utils/packet/response/createResponse.js';
import { PACKET_TYPE } from '../../constants/header.js';

const packetType = PACKET_TYPE;

// 디버프 전달 요청 핸들러
const handlePassDebuffRequest = async (socket, payload) => {
  try {
    const { targetUserId, debuffCardType } = payload;

    const gameSession = getGameSessionBySocket(socket);
    if (!gameSession) {
      throw new Error('해당 유저의 게임 세션이 존재하지 않습니다.');
    }

    const currentUser = gameSession.users.find((user) => user.socket === socket);
    if (!currentUser) {
      throw new Error('현재 유저가 존재하지 않습니다.');
    }

    const targetUser = gameSession.users.find((user) => user.id === targetUserId);
    if (!targetUser) {
      throw new Error('타겟 유저가 존재하지 않습니다.');
    }

    // 디버프 카드를 전달 로직 추가 (현재 유저의 핸드에서 제거 후 타겟 유저에게 추가)
    const debuffCardIndex = currentUser.character.handCards.findIndex(
      (card) => card.type === debuffCardType,
    );
    if (debuffCardIndex === -1) {
      throw new Error(`유저의 핸드에 해당 디버프 카드가 존재하지 않습니다.`);
    }

    const [debuffCard] = currentUser.character.handCards.splice(debuffCardIndex, 1);
    targetUser.character.debuffs.push(debuffCard.type);

    // 요청을 보낸 소켓에 성공 여부 보내기
    const passDebuffResponse = createResponse(packetType.PASS_DEBUFF_RESPONSE, 0, {
      success: true,
      failCode: 0,
    });
    socket.write(passDebuffResponse);
  } catch (error) {
    console.error('디버프 전달 중 에러 발생:', error.message);

    // 요청을 보낸 소켓에 실패 여부 보내기
    const errorResponse = createResponse(packetType.PASS_DEBUFF_RESPONSE, 0, {
      success: false,
      failCode: 1,
      message: error.message || 'Debuff pass failed',
    });
    socket.write(errorResponse);
  }
};

export default handlePassDebuffRequest;

 

유저 상태 업데이트 관련 핸들러 로직

import { getGameSessionBySocket } from '../../sessions/game.session.js';
import { createResponse } from '../../utils/packet/response/createResponse.js';
import { PACKET_TYPE } from '../../constants/header.js';

const packetType = PACKET_TYPE;

// 유저 업데이트 알림 전송 함수
const sendUserUpdateNotification = (gameSession, payload) => {
  const userUpdatePayload = {
    // 업데이트 된 유저의 정보를 담음
    users: payload.users.map((user) => ({
      id: user.id,
      nickname: user.nickname,
      character: {
        characterType: user.character.characterType,
        roleType: user.character.roleType,
        hp: user.character.hp,
        weapon: user.character.weapon,
        stateInfo: user.character.stateInfo,
        equips: user.character.equips,
        debuffs: user.character.debuffs,
        handCards: user.character.handCards,
        bbangCount: user.character.bbangCount,
        handCardsCount: user.character.handCardsCount,
      },
    })),
  };

  gameSession.users.forEach((user) => {
    const userUpdateNotification = createResponse(
      packetType.USER_UPDATE_NOTIFICATION,
      0,
      userUpdatePayload,
    );
    user.socket.write(userUpdateNotification);
  });
};

// 유저 업데이트 요청 핸들러
const handleUserUpdate = async (socket, payload) => {
  try {
    const { users } = payload;

    const gameSession = getGameSessionBySocket(socket);
    if (!gameSession) {
      throw new Error('해당 유저의 게임 세션이 존재하지 않습니다.');
    }

    // 모든 유저에게 유저 업데이트 알림 전송
    sendUserUpdateNotification(gameSession, { users });
  } catch (error) {
    console.error('유저 업데이트 중 에러 발생:', error.message);
  }
};

export default handleUserUpdate;

 

그리고 어제 로직 수정 사항

 

위치 업데이트 핸들러 파일에서 수정이 좀 있다

 

이유는 아래에서

import { getGameSessionBySocket } from '../../sessions/game.session.js';
import { createResponse } from '../../utils/packet/response/createResponse.js';
import { PACKET_TYPE } from '../../constants/header.js';

const packetType = PACKET_TYPE;

// 유저 위치 업데이트 함수
const updateCharacterPosition = (gameSession, userId, x, y) => {
  const targetUser = gameSession.users.find((user) => user.id === userId);
  if (!targetUser) return false;

  targetUser.position = { x, y };
  return true;
};

// 상대 유저들에게 위치 업데이트 전송 함수
const sendPositionUpdateToOpponents = (gameSession, updatedUserId, payload) => {
  gameSession.users.forEach((user) => {
    if (user.id !== updatedUserId) {
      const notification = Buffer.alloc(10);
      // 패킷 타입 설정
      notification.writeUInt16BE(packetType.POSITION_UPDATE_NOTIFICATION, 0);

      // 좌표 값
      notification.writeUInt32BE(payload.x, 2);
      notification.writeUInt32BE(payload.y, 6);

      user.socket.write(notification);
    }
  });
};

// 주기적으로 위치 정보를 전송하는 함수
const startPeriodicPositionUpdates = (gameSession) => {
  setInterval(() => {
    gameSession.users.forEach((currentUser) => {
      const { id, position } = currentUser;
      sendPositionUpdateToOpponents(gameSession, id, { x: position.x, y: position.y });
    });
  }, 1000);
};

// 위치 업데이트 요청 핸들러
const handlePositionUpdate = async (socket, payload) => {
  try {
    const { x, y } = payload;
    const gameSession = getGameSessionBySocket(socket);
    if (!gameSession) {
      throw { message: '해당 유저의 게임 세션이 존재하지 않습니다.', failCode: 2 };
    }

    const currentUser = gameSession.users.find((user) => user.socket === socket);
    if (!currentUser) {
      throw { message: '유저가 존재하지 않습니다.', failCode: 3 };
    }

    // 위치 업데이트 호출
    const success = updateCharacterPosition(gameSession, currentUser.id, x, y);

    if (success) {
      const positionResponse = createResponse(packetType.POSITION_UPDATE_RESPONSE, 0, {
        success: true,
        failCode: 0,
      });
      socket.write(positionResponse);

      // 위치를 업데이트 시 바로 상대에게 전송
      sendPositionUpdateToOpponents(gameSession, currentUser.id, { x, y });
    } else {
      throw { message: '캐릭터 위치 업데이트에 실패하였습니다.', failCode: 1 };
    }

    // 주기적으로 위치 정보를 전송하도록 타이머 시작 (한 번만 시작)
    if (!gameSession.isPeriodicUpdateStarted) {
      gameSession.isPeriodicUpdateStarted = true;
      startPeriodicPositionUpdates(gameSession);
    }
  } catch (error) {
    console.error('위치 업데이트 중 에러 발생:', error.message);

    const errorResponse = createResponse(packetType.POSITION_UPDATE_RESPONSE, 0, {
      success: false,
      failCode: error.failCode || 1,
    });
    socket.write(errorResponse);
  }
};

export default handlePositionUpdate;

 

일단 회의 중 노티피케이션 로직들은 어떻게 처리를 할거냐에 대한 회의가 있었는데

 

각자 사용해본 방식 

 

 

일단 내가 사용해본건 핸들러 로직에서 같이 처리하는 방식

이 방법은 전 프로젝트에서 노티피케이션은 따로 관리하는게 좋다는 피드백이 있었어서 일단 보류

 

 

다른 사람들 방법으로는

 

클래스 안에 노티피케이션 로직들을 관리하는 방식

이 방법은 팀원들끼리 클래스 로직이 너무 길어진다는 의견을 받았다고 한다.

 

 

마지막으로 우리가 선택한 방법은

노티피케이션 로직을 유틸 폴더에서 따로 관리하는 방식

 

이 방식이 깔끔하고 좋지만 지금 뼈대 구조가 이 방식과 너무 달라서 내일까지 수정 작업을 해보고 결정한다고 한다.

 

 

클래스도 계속 구조가 바뀌고 있어서 일단 내가 구현했던 핸들러 관련 로직은 핸들러 파일안에 모두 몰아넣었다

 

대충 구조가 잡히면 그때 다시 나눌 예정