Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FE] 주사위 기능과 웹소켓 연결하기 #39

Merged
merged 12 commits into from
Oct 26, 2023
Merged

[FE] 주사위 기능과 웹소켓 연결하기 #39

merged 12 commits into from
Oct 26, 2023

Conversation

silvertae
Copy link
Collaborator

@silvertae silvertae commented Oct 26, 2023

📌 이슈번호

🔑 Key changes

2023-10-26.4.17.19.mov
  • 주사위 UI 구현 (react-dice-complete 패키지 사용)
  • 임시 웹소켓 서버 구현: 서버 배포전에 웹소켓 기능을 테스트하기 위해서 server 폴더에 따로 간단한 웹소켓 서버를 구현했습니다.

클라이언트 웹소켓 기능 구현

  • react-use-websocket 패키지를 설치해서 useWebSocket 훅을 사용해서 웹소켓을 연결하고 서버와 통신합니다.
  • GamePage 마운트 시 정해둔 WS_URL로 웹소켓이 연결됩니다. 이후 react-use-websocket이 제공하는 sendJsonMessage를 이용해서 서버로 메세지를 보내고, lastMessage로 서버로부터의 메세지를 받아 dispatch()를 실행합니다.

게임시작 기능 구현

우선 입장 후에 게임시작 버튼 클릭시 서버로 type: start 메세지를 보내서 게임이 시작되도록 구현했습니다.

주사위 굴리기 기능 구현:

본인 차례일 때만 화면에 굴리기와 턴종료 버튼이 렌더링 되도록 했고, 굴리기 버튼 클릭시 type: dice 메세지를 보내서 주사위 값을 받아오고 이를 이용해 화면에 주사위 결과를 표시합니다. 이후 결과값만큼 본인 토큰을 이동하도록 구현했습니다.

턴종료 기능 구현:

턴종료 버튼 클릭시 type: endTurn 메세지를 보내서 다음 차례가 될 플레이어를 받아옵니다.

👋 To reviewers

  • 플레이어마다 본인 토큰의 ref를 기존 initialPlayer에 추가하였습니다. moveToken을 할 때 ref를 찾아서 넣어줘야 되는데 더 좋은 방법이 생각이 안났네요.
  • 간단히 주사위 기능을 구현하려고 했었던 게 웹소켓을 연결해서 기능구현을 진행하다 보니 작업한게 너무 늘어났네요. 😅

@silvertae silvertae added refactor 코드 리팩토링 fe 프론트엔드 UI UI 및 디자인 작업 labels Oct 26, 2023
@silvertae silvertae added this to the [FE] 3주차 마일스톤 milestone Oct 26, 2023
@silvertae silvertae requested a review from aaaz425 October 26, 2023 07:18
@silvertae silvertae self-assigned this Oct 26, 2023

export default function CenterArea() {
const { gameId } = useParams();
const [gameInfo] = useGameInfo();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(중요하지 않음)
value만 사용할 거면 useGameInfoValue() 사용해도 될 것 같습니다

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

엇 그러네요 어차피 setAtom() 하는 분이 리듀서에서 처리되니까 여기서는 value만 불러와도 될 것 같습니다

Comment on lines 13 to 15
const { sendJsonMessage } = useWebSocket(BASE_WS_URL, {
share: true,
});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

share옵션 사용하면 여러 페이지에서 공유할 수 있나보네요 👍

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

같은 socketURL을 사용할 때 share 옵션을 켜주면 여러 컴포넌트에서 같은 sendMessage을 불러서 사용할 수 있더라구요!

Comment on lines 32 to 33
const [gameInfo] = useGameInfo();
const players = usePlayersValue();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

둘다 writeOnly로 사용하는데,
gameInfo는 일반 atom, playersvalueAtom을 사용하신 기준이 궁금합니다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이제보니 value만 불러와도 될 것 같습니다 😅

Comment on lines +54 to +102
const moveToNextCell = (
x: number,
y: number,
tokenRef: ForwardedRef<HTMLDivElement>,
tokenAtom: PlayerTokenAtom
) => {
if (!tokenRef) return;
const ref = tokenRef as MutableRefObject<HTMLDivElement>;
tokenAtom.coordinates.x += x;
tokenAtom.coordinates.y += y;
ref.current.style.transform = `translate(${tokenAtom.coordinates.x}rem, ${tokenAtom.coordinates.y}rem)`;
};

const moveToken = useCallback(
async (
diceCount: number,
tokenRef: ForwardedRef<HTMLDivElement>,
tokenAtom: PlayerTokenAtom,
setTokenAtom: (
updateFunction: (prev: PlayerTokenAtom) => PlayerTokenAtom
) => void
) => {
const tokenCoordinates = tokenAtom.coordinates;
let tokenDirection = tokenAtom.direction;
let tokenLocation = tokenAtom.location;

for (let i = diceCount; i > 0; i--) {
const directionData = directions[tokenDirection];
moveToNextCell(directionData.x, directionData.y, tokenRef, tokenAtom);

tokenLocation = (tokenLocation + 1) % 24;
const isCorner = CORNER_CELLS.includes(tokenLocation); // 0, 6, 12, 18 칸에서 방향 전환

if (isCorner) {
tokenDirection = changeDirection(tokenDirection);
}

await delay(TOKEN_TRANSITION_DELAY);
}

setTokenAtom((prev) => ({
...prev,
coordinates: tokenCoordinates,
direction: tokenDirection,
location: tokenLocation,
}));
},
[]
);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

나~중에 여유 있으면
훅으로 분리해도 좋을 것 같습니다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe later...

Comment on lines +36 to +60
{initialBoard.map((line, index) => (
<Line key={index} $lineNum={index + 1}>
{line.map((cell) => (
<Cell
key={cell.name}
theme={cell.theme}
logo={cell.logo}
name={cell.name}
price={cell.price}
/>
))}
</Line>
))}
{gameInfo.isPlaying && <CenterArea />}
{players.map((player) => {
if (player.playerId === '') return;
const tokenRef = setToken(player.order);
return (
<PlayerToken
key={player.playerId}
ref={tokenRef}
order={player.order}
/>
);
})}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👀👍👍

Comment on lines +16 to +24
return prev.map((player, index) => {
if (index === targetIndex) {
return {
...player,
tokenRef: ref,
};
}
return player;
});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

새로운 배열을 반환하는 방법 말고
기존 배열에서 바뀐 부분만 수정하는 방법은 없을지
한번 찾아보겠습니다 !

@aaaz425 aaaz425 merged commit 21e311f into fe Oct 26, 2023
1 check passed
aaaz425 added a commit that referenced this pull request Oct 27, 2023
* chore: vite 초기 프로젝트 세팅 (#1)

- React & TypeScript + SWC 기본 템플릿

* chore: styled-components 설치 및 reset 설정

* ui: 폰트 적용 및 테스트 페이지 구현

* chore: react-router 설치 및 세팅

* chore: eslint-plugin-import 설치 및 설정 (#1)

* ui: 회원가입 페이지 UI 구현 (#5)

* rename: pages 폴더로 기존 컴포넌트들 이동 및 이름 변경 (#5)

* ui: 로그인/회원가입 UI 약간 수정

- 아이디/비밀번호 인풋에 label 추가

* ui: 로그인/회원가입 UI 약간 수정 (#5)

- 아이디/비밀번호 인풋에 label 추가

* feat: 디자인 시스템 구축

* refactor: 공통 스타일 분리

* refactor: 라우터 분리

* chore: 프리티어 설정

* chore: prettier 설정 변경

* fix: 머지 후 수정사항 반영

* style: import 플러그인 prettier 충돌 해결

* fix: DefaultTheme 타입 eslint 오류해결

* chore: axios, jotai 패키지 설치

* chore: axios 세팅

- fetcher 함수 구현
- end point 상수 선언

* chore: gitignore 환경 변수 파일 추가

* chore: store 파일 추가

* chore: 라우팅 설정 변경

- 인증 페이지 분리
- Layout 추가

* feat: 회원가입 요청 기능 구현

- api 폴더에 signup 함수 추가

* refactor: common.ts 파일 삭제 및 기존 페이지 styled component 복구

* refactor: SignUpPage 리팩터링

- 전역적으로 저장할 필요가 없는 상태인 playerId, password는 useAtom -> useState로 수정
- 회원가입시 Form으로 감싸서 submit 하는 방식으로 수정

* fix: endpoint 에서 signin, signup 반대로 되어있던것 수정

* feat: 로그인 요청 기능 구현

- api에 signin 함수 추가
- 로그인 및 회원가입이 인풋이 비어있을 때 disabled 기능과 스타일 추가

* feat: 아이디, 비밀번호 인풋 정규표현식 추가

* feat: api에 logout 함수 추가

* chore: MSW 설치 및 초기 세팅

- 로그인/회원가입 기능 구현 테스트 시에 mocking이 없으니 너무 어려워서 설치하게 되었습니다.

* feat: 로그인 요청 mocking 추가

* feat: jotai atomWithStorage로 토큰 저장 및 리셋 기능 구현

* feat: axios interceptor로 헤더에 accessToken 설정

* feat: ProtectedRoute 적용

- 로그인 된 상태에서 로그인/회원가입 페이지 접근시 홈으로 리다이렉트
- 로그인 안 된 상태에서 홈화면 접근시 로그인 페이지로 리다이렉트

* refactor: localStorage 토큰 저장 기능 리팩터링

- atomWithStorage 사용시 ProtectedRoute 적용했을 때 몇가지 문제가 있어서 다른 방식으로 구현

* feat: 로그인시 응답으로 받는 playerId도 store에 추가

* chore: svgr, path 관련 패키지 설치

* feat: modal 루트 추가

* feat: 모달 공통 컴포넌트 생성

* feat: 디자인 시스템 변경

* add: 아이콘 asset 추가

* refactor: 스타일 위치 변경

* feat: 방 입장 모달 생성

* feat: 헤더 및 페이지 레이아웃 구현

* feat: Icon 공통 컴포넌트 구현

* refactor: import 순서 자동 변경

* feat: useHover 커스텀 훅 구현

* feat: useOutsideClick 커스텀 훅 구현

- ref 요소의 외부를 클릭하면 닫히는 로직

* feat: 홈 페이지 UI 작업

* feat: 방 생성 api 구현

* feat: 방 생성 mock 서버 api 구현

* refactor: 사용자 인증 페이지 Layout 적용

* feat: 게임 페이지 라우터 경로 추가

* feat: 홈 페이지 방 입장 기능 구현

* refactor: useHover 커스텀 훅 리팩토링

- useCallback을 이용한 최적화
- 이벤트 중첩 방지

* feat: 게임 페이지 파일 생성

* refactor: 필요없는 fragment 삭제

* feat: 게임 방 유효 체크 api 함수 구현

* feat: 게임 페이지 ui 구현

* ui: 보드게임판 간단히 틀 잡아보기

- css만 이용해서 하드코딩 해보면서 틀을 잡는다.

* feat: GameBoard, Cell 컴포넌트 분리 및 UI 작업

- GamePage에서 보드게임판을 별도 컴포넌트로 분리
- GameBoard에서는 4개의 라인이 있고 각각의 라인에 6개의 Cell 컴포넌트가 들어가있는 형태로 구현
- 각 Cell 컴포넌트들마다 들어갈 정보 표시

* add: 몇몇 기업 로고 이미지와 특수칸 이미지 파일 추가

* add: asset 추가

* refactor: 핸들러 함수 네이밍 통일

* refactor: 프로젝트 전체 리팩토링

* feat: mock 서버용 임시 api 구현

- 웹 소켓 연결 전까지 사용

* refactor: 페이지별 헤더 구분

* feat: 게임 페이지 및 플레이어 카드 UI 작업

* feat: 플레이어 상태 생성 및 reducer 구현

* refactor: 핸들러 함수 네이밍 통일

* add: 모든 기업 이미지 추가

* [FE] 플레이어 토큰 이동기능 구현 (#27)

* feat: 주사위 눈 수만큼 말을 이동시키는 moveToken() 함수 구현 (#25)

* feat: utils 폴더에 delay 함수 분리

* style: styled componenet css속성 컨벤션에 맞게 순서 수정

* add: designSystem 색상에 각 플레이어 대표색상 추가

* feat: PlayerToken 컴포넌트 구현

* refactor: 플레이어 토큰 이동로직 리팩터링 및 상수 분리

* feat: 각 플레이어별 토큰 상태 atom으로 분리 (#25)

* refactor: 일부 함수 및 파일위치 재조정

* refactor: 리뷰어 피드백 반영해서 리팩터링

- PlayerToken 스타일링 부분 리팩터링
- moveToken() 함수 좀 더 분리

* refactor: 피드백 반영 2차 수정

- moveToNextCell 함수 분리
- changeDirection 함수 위치 이동

* [FE] 게임 상태 관리 (#29)

* chore: jotai optics 패키지 설치

* remove: 불필요한 asset 삭제

* refactor: 플레이어 카드 컴포넌트 분리

- 게임 페이지 전체 재랜더링 방지

* feat: 게임 나가기 기능 임시 구현

* refactor: api 문서 변경에 의한 리팩토링

* feat: 게임 전체 상태 리듀서 구현

* refactor: 피어 리뷰 피드백 반영

- 상태 네이밍 변경

* [FE] 전체 주식 현황판 (#33)

* add: 전체 현황판 아이콘 에셋 추가

* move: test 모달 파일 위치 변경

* refactor: 모달 공통 컴포넌트 위치 계산 로직 추가

* refactor: 테스트 모달 컴포넌트 위치 변경

* feat: 아이콘 svgr 추가

* feat: ul 태그 글로벌 스타일 변경

* feat: 전체 현황판 모달 구현

* refactor: 불필요한 위치 계산 과정 삭제

- 위치 계산 관련 useState, useEffect, useRef 삭제
- transform으로 대체

* [FE] 프론트엔드 자동배포 테스트 (#38)

* chore: deploy.yml 파일 작성 (#37)

* fix: github actions 작업 디렉토리 수정 (#37)

* fix: working directory 설정 변경 (#37)

* fix: source 디렉토리 이름 dist로 수정 (#37)

* fix: source 디렉토리 이름 fe/dist로 수정 (#37)

* chore: Invalidate CloudFront cache 스크립트 추가 (#37)

* [FE] 주사위 기능과 웹소켓 연결하기 (#39)

* refactor: GameBoard 렌더링 방식 리팩터링

- api 명세서에 맞게 바뀐 이름으로 기업 logo, name 변경
- Board 컴포넌트 렌더링을 한번에 하기 위해 initialBoard를 contants에 선언

* feat: 테스트용 웹소켓 서버 구현

* feat: 웹소켓 응답으로 주사위 결과 출력 및 이동기능 구현 (#31)

- useEffect 내부 dependecy에 dispatch 추가시 무한렌더링 되는 오류 해결 필요

* chore: react-dice-complete 패키지 설치 (#31)

* feat: 주사위 굴린 후 결과값에 따라 이동하는 기능 구현 (#31)

* chore: BASE_WS_URL 환경변수 세팅 추가

* feat: 각 플레이어 턴마다 본인 토큰 이동 기능 구현 (#31)

- PlayerType에 tokenRef 데이터 추가
- 본인 order를 이용해 자기 토큰 찾아서 이동시키는 로직

* feat: 게임시작 버튼과 기능 구현

* refactor: 주사위 컴포넌트 및 이동로직 GameBoard에서 분리

* refactor: 리뷰어 피드백 반영해서 코드 수정

- useGameInfo를 useGameInfoValue로 변경

* feat: WS_URL 명세서에 맞게 변경 및 주사위 숫자 표시 추가

* [FE] 게임페이지 리팩토링 (#40)

* add: 필요 에셋 추가

- 왼쪽 화살표, 오른쪽 화살표

* add: icons에 화살표 아이콘 추가

* feat: 플레이어 입장 안했을 시 랜더링할 빈 카드 구현

* feat: 친구 초대 모달 구현

* feat: 개인 주식 세부정보 표시 툴팁 구현

- PlayerStockTooptip 컴포넌트 생성
- useTooltipPosition으로 툴팁 위치 조정

* refactor: 플레이어 주식 정보 리스트 리팩토링

- 길이 초과시 스크롤 생성
- useClickScrollButton 커스텀훅 생성
- 버튼으로 스크롤 제어

* refactor: 스타일 수정 및 오타 수정

* refactor: 플레이어 개인 소유 주식 없을 시 주식 리스트 html 미생성

* feat: yml파일 환경변수 추가

* chore: 플레이어 카드 개미 이미지 변경

* refactor: multi ref 콜백 함수 분리

* refactor: useClickScrollButton 커스텀 훅 else문 삭제

- 조기리턴으로 변경

* refactor: api 문서에 맞게 리듀서 및 목 핸들러 수정

---------

Co-authored-by: aaaz425 <[email protected]>
Co-authored-by: TOKO <[email protected]>
@silvertae silvertae deleted the feat/#31-Dice branch November 1, 2023 05:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
fe 프론트엔드 refactor 코드 리팩토링 UI UI 및 디자인 작업
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants