Skip to content

hoheesu/indian_frog_fe

 
 

Repository files navigation

⛵INDIAN_FROG

️항해99 6조 실전 프로젝트

✍🏻 서비스 소개

자신의 카드를 모른 채 상대와 심리전을 통해 승패를 가리는 인디언 포커 게임을 웹서비스로!

대표 스택 React Spring
🚩 개발 기간 2024.03.26 ~ 2023.05.07
➡️ URL 인디안 개구리

🐬 팀원 소개

오진선 윤준수 함석원 이주호 박용운 강주성 김지우
BE / 팀리더 FE / 부리더 FE BE BE BE UI/UX

🏛️ 아키텍처 구성도

아키텍쳐

🐥 FRONT-END STACK

🔎 주요기능

✅ 로그인 / 회원가입 로그인
  • 실시간 통신(websoket)을 이용하여 아래 게임로직을 구현
  • 입장한 2명의 플레이어가 모두 READY를 한 경우 게임이 시작된다.
  • 포인트가 적은 플레이어의 10%에 해당하는 포인트를 초기 베팅금액으로 게임을 시작한다.
  • 방장이 먼저 플레이를 시작하며 방장의 턴이 끝나면 다른 플레이어의 턴이 시작된다.
  • 플레이어들은 Die, Check, Raise를 선택하여 베팅하고 Raise의 경우 금액을 입력할 수 있다.
  • 모든 베팅이 끝나면 플레이어들은 자기의 패를 확인할 수 있다.
  • 라운드가 끝나면 해당 라운드의 승패와 획득 포인트를 확인할 수 있다.
  • 플레이어들은 3라운드까지 게임을 진행한다.
  • 모든 라운드가 종료되면 승패가 정해지고 플레이어들의 총 획득 포인트를 확인할 수 있다.
  • 게임이 끝나면 플레이어들은 재게임을 할지 게임방에서 나갈지 선택할 수 있다.
✅ 게임 로비 로비
  • 게임방에 참여한 플레이어는 게임을 진행하며 채팅을 할 수 있다.
  • 채팅에는 게임 중 어떤 요청을 보냈는지 (체크, 레이즈, 다이)에 대한 정보도 표시된다.
  • 3분에 한 번씩 자동으로 게임이용을 위한 안내가 올라온다.
  • 욕설을 입력할 경우 “(개굴)”로 필터링 된다.
✅ 인디언 포커 게임 게임
  • 실시간 통신(websoket)을 이용하여 아래 게임로직을 구현
  • 입장한 2명의 플레이어가 모두 READY를 한 경우 게임이 시작된다.
  • 포인트가 적은 플레이어의 10%에 해당하는 포인트를 초기 베팅금액으로 게임을 시작한다.
  • 방장이 먼저 플레이를 시작하며 방장의 턴이 끝나면 다른 플레이어의 턴이 시작된다.
  • 플레이어들은 Die, Check, Raise를 선택하여 베팅하고 Raise의 경우 금액을 입력할 수 있다.
  • 모든 베팅이 끝나면 플레이어들은 자기의 패를 확인할 수 있다.
  • 라운드가 끝나면 해당 라운드의 승패와 획득 포인트를 확인할 수 있다.
  • 플레이어들은 3라운드까지 게임을 진행한다.
  • 모든 라운드가 종료되면 승패가 정해지고 플레이어들의 총 획득 포인트를 확인할 수 있다.
  • 게임이 끝나면 플레이어들은 재게임을 할지 게임방에서 나갈지 선택할 수 있다.
✅ 게임방 채팅 게임
  • 게임방에 참여한 플레이어는 게임을 진행하며 채팅을 할 수 있다.
  • 채팅에는 게임 중 어떤 요청을 보냈는지 (체크, 레이즈, 다이)에 대한 정보도 표시된다.
  • 3분에 한 번씩 자동으로 게임이용을 위한 안내가 올라온다.
  • 욕설을 입력할 경우 “(개굴)”로 필터링 된다.
✅ 마이 페이지
  • 프로필 및 개인 랭킹을 확인할 수 있다.
  • 프로필 편집으로 프로필 이미지를 변경할 수 있다.
  • 비밀번호를 변경할 수 있다.
  • 30 포인트 이하 시 랜덤 뽑기를 통해 포인트를 충전할 수 있다.

🔑 트러블 슈팅

✅ 동시성 문제
  • 문제 1: DB상 기존 방에 유저가 남아있는 문제
  • 문제 2: 게임방에서 뒤로가기로 나와도 계속 웹소켓이 연결되어있어 다른방에 들어가도 기존 방과 연결이 끊기지 않음. - 원인: useEffect의 의존성 배열을 비워 첫번째 마운트, 언마운트 상황에 대처하는 코드를 작성했으나 첫번째 마운트 될때에는 웹소켓에 connect가 되지 않아 leave 요청을 보낼 수 없어 에러가 발생 또한 returnValue는 공식문서에서 사용하지 않는것을 권장하고 있는 속성
useEffect(() => {
    const handleBeforeUnload = (event: BeforeUnloadEvent) => {
      if (confirmNavigation) {
      const message =
      "이 페이지를 떠나시겠습니까? 변경 사항이 저장되지 않을 수 있습니다.";
      event.returnValue = message;
      return message;
    }
  };

  window.addEventListener("beforeunload", handleBeforeUnload);

  return () => {
    window.removeEventListener("beforeunload", handleBeforeUnload);
    handleLeaveButtonClick();
  };
}, []);
  • 해결방법: 라이브러리를 사용하여 returnValue를 사용하지 않을 수 있었고, 의존성 배열에 connect를 넣어 뒤로가기 이벤트가 발생하면 서버에 나가는 요청을 보낸후 연결되어있던 웹소켓까지 연결을 끊음
useEffect(() => {
  const listenBackEvent = () => {
    stompClient.send(
      `/app/${gameId}/leave`,
      { Authorization: authToken },
      JSON.stringify({ sender: userInfoDecode.nickname })
    );
    stompClient.disconnect();
  };

  const unlistenHistoryEvent = history.listen(({ action }) => {
    if (action === "POP") {
      listenBackEvent();
      navigate("/main");
    }
  });

  return unlistenHistoryEvent;
}, [connect]);
✅ 실시간 리스트업
  • 문제 : 로비에서 게임룸을 생성할 경우와 룸에서 퇴장할경우 로비의 리스트 목록이 생성되고 삭제 되는 것이 실시간으로 리스트업 되어야 하는데 새로고침을 눌러야만 최신화가 되는 문제
  • 해결 1) : 리액트 쿼리의 queryClient.invalidateQueries 로 쿼리키를 무효화해서 쿼리의 최신데이터를 서버로 부터 다시 가져오게 하여 진행.
  • BUT 방에서 나왔을때 본인화면에서는 최신화가 되었지만 다른 브라우저에 동시접속해 있는 화면에서는 그대로 남아있는 문제발생.
  • 해결 2) : 게임룸 리스트를 불러오고 있는 useInfiniteQuery 에 1초마다 Api 호출을 해주는 refetchInterval : 1000 을 추가하여 실시간 리스트업 문제를 해결.
✅ 빌드할 경우 이미지 누락
  • 문제 : import 해서 가져온 이미지를 styled-component의 background-image의 사용할경우 로컬서버에서는 정상적으로 보이는 이미지들이 배포서버에서는 누락되는 문제.
  • 개선방안 : 현재 폴더구조를 src > assets > images 에 모든 이미지를 담고있는데 빌드시 이미지 경우는 public 폴더에 위치해야 이미지가 누락되지 않는다고 한다.

👨🏻‍🔧 유저테스트 피드백 이후 수정 사항

✅ RAISE포인트 관련 문제
  • 문제 : 유저가 상대방의 올인을 유도하여 RAISE를 할 경우 상대 유저는 포인트가 빠져나가지 않은 상황이라 다시 RAISE를 하는 경우 배팅 할 수 있는 최대 금액이 현재 포인트에 맞춰서 계산이 되기때문에 포인트가 두배로 배팅되는 경우가 발생됨.

  • 해결 : 유저가 갖고있는 포인트와 직전 RAISE금액을 비교하여 maxBetPoint를 수정하여 Input에 props로 내림.

    const raiseMaxBet = (useRef < number) | (null > null);
    const maxBetPoint = useMemo(() => {
      const usersPoint = Math.min(userPoint, otherPoint);
      return raiseMaxBet.current !== null
        ? Math.min(usersPoint, raiseMaxBet.current)
        : userPoint;
    }, [userPoint, otherPoint, raiseMaxBet]);
    
    // 전체 메세지 받은 리시브함수 내부
    if (message.previousPlayer !== userInfoDecode.nickname) {
      raiseMaxBet.current = message.otherPoint - message.nowBet;
    }
✅ 에러 핸들링
  • 문제: 에러핸들링이 미흡한 부분들이 존재하여 유저에게 좋지 못한 UX를 제공
    ex) 존재하지 않는방 접속, 없는 방 번호 입력, 상대가 나갔다 들어올때 유저 정보 등

  • 해결: react-Query → onError상황에서 에러 핸들링을 해주고, 여러 경우의 수를 생각하여 게임방page 렌더시에 유저를 판별하는 부분을 더 추가하였음.

    useEffect(() => {
      if (userInfoDecode.nickname !== leaveNickname) {
        if (userInfoDecode.nickname === roomUserInfo?.hostNickname) {
          setUserType('host');
          setUserPoint(roomUserInfo?.hostPoints);
          setOtherNickname('');
          setOtherPoint(0);
        }
      }
      if (userInfoDecode.nickname === joinNickname) {
        if (userInfoDecode.nickname === roomUserInfo?.hostNickname) {
          setUserType('host');
          setUserPoint(roomUserInfo?.hostPoints);
          setUserImg(roomUserInfo?.hostImageUrl);
          if (roomUserInfo?.participantCount === 2) {
            setOtherNickname(roomUserInfo?.participantNickname);
            setOtherPoint(roomUserInfo?.participantPoints);
            setOtherImg(roomUserInfo?.participantImageUrl);
          }
        }
        if (userInfoDecode.nickname === roomUserInfo?.participantNickname) {
          setUserType('guest');
          setUserPoint(roomUserInfo?.participantPoints);
          setUserImg(roomUserInfo?.participantImageUrl);
          setOtherNickname(roomUserInfo?.hostNickname);
          setOtherPoint(roomUserInfo?.hostPoints);
          setOtherImg(roomUserInfo?.hostImageUrl);
        }
      } else {
        if (roomUserInfo?.participantCount === 2) {
          if (userInfoDecode.nickname === roomUserInfo?.hostNickname) {
            setUserType('host');
            setUserPoint(roomUserInfo?.hostPoints);
            setUserImg(roomUserInfo?.hostImageUrl);
            setOtherNickname(roomUserInfo?.participantNickname);
            setOtherPoint(roomUserInfo?.participantPoints);
            setOtherImg(roomUserInfo?.participantImageUrl);
            if (userInfoDecode.nickname === roomUserInfo?.participantNickname) {
              setUserType('guest');
              setUserPoint(roomUserInfo?.participantPoints);
              setUserImg(roomUserInfo?.participantImageUrl);
              setOtherNickname(roomUserInfo?.hostNickname);
              setOtherPoint(roomUserInfo?.hostPoints);
              setOtherImg(roomUserInfo?.hostImageUrl);
            }
          }
        }
      }
    }, [roomUserInfo]);
✅ 해상도 마다 게임화면이 잘리거나 안보이는 문제 - 미디어쿼리를 이용해 해상도에 맞춰 반응형으로 변화되도록 개선
✅ 회원가입시 소셜로그인이 없어서 불편한 문제 - 구글, 카카오를 통한 소셜 로그인 추가 구현 (oauth2)
✅ 게임을하다가 방을 퇴장해도 방이 사라지지않는 문제 - 인증번호가 확인이 안되면 회원가입버튼을 비활성화시켜 회원가입을 할수 없도록 수정
로비 로비

About

인디안 포커 게임을 웹 사이트에서 즐길 수 있는 프로젝트 [FE]

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • TypeScript 99.1%
  • Other 0.9%