Skip to content

Commit

Permalink
[FE] 토큰 이동 버그 수정 및 감옥칸 기능 구현 (#62)
Browse files Browse the repository at this point in the history
* feat: 전체현황판 상태 반영해서 보드게임 기업칸 가격 표시 구현 (#54)

* chore: jotai devtools 설치 및 세팅

* feat: 이벤트 룰렛 결과창 모달 구현 및 딜레이 기능 추가

* refactor: 토큰 관련 상태 리팩터링 및 이동버그 수정 (#54)

* ui: 감옥칸에서 턴 시작시 화면중앙 ui 구현 (#54)

* refactor: 머지 후 안쓰는 토큰 상태 삭제 및 타입 정리

* chore: 이미지 파일 변경사항 수정

* feat: 감옥 보석금 내고 탈출 기능 구현

* feat: 주사위 굴려서 탈출하기 웹소켓 요청 및 응답 추가

* feat: 감옥 주사위 굴려서 탈출하기 기능 구현

* fix: store 경로 관련 타입에러 수정

* fix: 이미지 변경사항 대소문자 구분 옵션 추가

* refactor: 게임판 중앙영역 감옥 버튼들에 useHover 커스텀훅 적용

* Delete fe/src/assets/images/samsungBio.png

* Delete fe/src/assets/images/starkIndustry.png

* fix: useGameReducer에서 totalAsset 제거
  • Loading branch information
silvertae authored Nov 1, 2023
1 parent b477677 commit e007e75
Show file tree
Hide file tree
Showing 17 changed files with 1,246 additions and 306 deletions.
907 changes: 850 additions & 57 deletions fe/package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions fe/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
"preview": "vite preview"
},
"dependencies": {
"@emotion/react": "^11.11.1",
"axios": "^1.5.1",
"jotai": "^2.4.3",
"jotai-devtools": "^0.7.0",
"react": "^18.2.0",
"react-custom-roulette": "^1.4.1",
"react-dice-complete": "^2.2.0",
Expand Down
2 changes: 2 additions & 0 deletions fe/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import { router } from '@router/router';
import { GlobalStyle } from '@styles/GlobalStyle';
import { ModalRoot } from '@styles/common';
import { theme } from '@styles/designSystem';
import { DevTools } from 'jotai-devtools';
import { RouterProvider } from 'react-router-dom';
import { ThemeProvider } from 'styled-components';

export default function App() {
return (
<ThemeProvider theme={theme}>
<GlobalStyle />
<DevTools />
<RouterProvider router={router} />
<ModalRoot id="modal-root" />
</ThemeProvider>
Expand Down
73 changes: 66 additions & 7 deletions fe/src/components/GameBoard/CenterArea.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
import useGetSocketUrl from '@hooks/useGetSocketUrl';
import useHover from '@hooks/useHover';
import { usePlayerIdValue } from '@store/index';
import { useGameInfoValue } from '@store/reducer';
import { useEffect } from 'react';
import { useGameInfoValue, usePlayersValue } from '@store/reducer';
import { useCallback, useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import useWebSocket from 'react-use-websocket';
import { styled } from 'styled-components';
import Dice from './Dice';
import Roulette from './Roulette';

export default function CenterArea() {
const [isMoveFinished, setIsMoveFinished] = useState(false);
const { hoverRef: bailRef, isHover: isBailBtnHover } =
useHover<HTMLButtonElement>();
const { hoverRef: escapeRef, isHover: isEscapeBtnHover } =
useHover<HTMLButtonElement>();
const { gameId } = useParams();
const players = usePlayersValue();
const gameInfo = useGameInfoValue();
const playerId = usePlayerIdValue();
const socketUrl = useGetSocketUrl();
Expand All @@ -19,6 +26,13 @@ export default function CenterArea() {

const eventTime = gameInfo.currentPlayerId === null;
const isMyTurn = playerId === gameInfo.currentPlayerId;
const currentPlayerLocation = players.find(
(player) => player.playerId === gameInfo.currentPlayerId
)?.location;
const isPrison = currentPlayerLocation === 6;
const defaultStart = !eventTime && isMyTurn && !isPrison && !isMoveFinished;
const prisonStart = !eventTime && isMyTurn && isPrison && !isMoveFinished;
// TODO: teleport 구현 필요

useEffect(() => {
if (!eventTime) return;
Expand All @@ -40,6 +54,7 @@ export default function CenterArea() {
};

const endTurn = () => {
setIsMoveFinished(false);
const message = {
type: 'endTurn',
gameId,
Expand All @@ -48,16 +63,50 @@ export default function CenterArea() {
sendJsonMessage(message);
};

const handleFinishMove = useCallback(() => {
setIsMoveFinished(true);
}, []);

const handleBail = () => {
const message = {
type: 'expense',
gameId,
playerId,
};
sendJsonMessage(message);
};

const handleEscape = () => {
const message = {
type: 'prisonDice',
gameId,
playerId,
};
sendJsonMessage(message);
};

return (
<Center>
{!eventTime && <Dice />}
{eventTime && <Roulette />}
{!eventTime && isMyTurn && (
{!eventTime && <Dice finishMove={handleFinishMove} />}
{defaultStart && (
<>
<Button onClick={() => throwDice()}>굴리기</Button>
<Button onClick={() => endTurn()}>턴종료</Button>
<Button onClick={() => throwDice()} disabled={isMoveFinished}>
굴리기
</Button>
</>
)}
{prisonStart && (
<Wrapper>
<Button ref={bailRef} onClick={handleBail}>
{isBailBtnHover ? '-5,000,000₩' : '보석금 지불'}
</Button>
<Button ref={escapeRef} onClick={handleEscape}>
{isEscapeBtnHover ? '주사위 더블시 탈출' : '굴려서 탈출'}
</Button>
</Wrapper>
)}
{isMoveFinished && <Button onClick={() => endTurn()}>턴종료</Button>}
</Center>
);
}
Expand All @@ -74,10 +123,20 @@ const Center = styled.div`
align-items: center;
`;

const Wrapper = styled.div`
display: flex;
gap: 1rem;
`;

const Button = styled.button`
width: 6rem;
width: 8rem;
height: 4rem;
padding: 0.5rem;
border-radius: ${({ theme: { radius } }) => radius.small};
color: ${({ theme: { color } }) => color.neutralText};
background-color: ${({ theme: { color } }) => color.neutralBackground};
&:disabled {
opacity: 0.6;
}
`;
148 changes: 68 additions & 80 deletions fe/src/components/GameBoard/Dice.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,7 @@
import {
PlayerTokenAtom,
usePlayerToken1,
usePlayerToken2,
usePlayerToken3,
usePlayerToken4,
} from '@store/playerToken';
import { useGameInfoValue, usePlayersValue } from '@store/reducer';
import { useGameInfoValue, usePlayers } from '@store/reducer';
import { GameBoardType } from '@store/reducer/type';
import { delay } from '@utils/index';
import {
ForwardedRef,
MutableRefObject,
useCallback,
useEffect,
useRef,
useState,
} from 'react';
import { MutableRefObject, useEffect, useRef, useState } from 'react';
import ReactDice, { ReactDiceRef } from 'react-dice-complete';
import { styled } from 'styled-components';
import {
Expand All @@ -24,101 +11,102 @@ import {
directions,
} from './constants';

export default function Dice() {
type DiceProps = {
finishMove: () => void;
};

export default function Dice({ finishMove }: DiceProps) {
const [diceValue, setDiceValue] = useState(0);
const reactDice = useRef<ReactDiceRef>(null);
const [token1, setToken1] = usePlayerToken1();
const [token2, setToken2] = usePlayerToken2();
const [token3, setToken3] = usePlayerToken3();
const [token4, setToken4] = usePlayerToken4();

const [players, setPlayers] = usePlayers();
const gameInfo = useGameInfoValue();
const players = usePlayersValue();

const tokenList: {
[key: number]: {
atom: PlayerTokenAtom;
setAtom: (prev: PlayerTokenAtom) => PlayerTokenAtom;
};
} = {
1: { atom: token1, setAtom: setToken1 },
2: { atom: token2, setAtom: setToken2 },
3: { atom: token3, setAtom: setToken3 },
4: { atom: token4, setAtom: setToken4 },
};

useEffect(() => {
if (gameInfo.dice[0] === 0 || gameInfo.dice[1] === 0) return;
rollDice(gameInfo.dice[0], gameInfo.dice[1]);
}, [gameInfo.dice]);
finishMove();
}, [gameInfo.dice, finishMove]);

const moveToNextCell = (
x: number,
y: number,
tokenRef: ForwardedRef<HTMLDivElement>,
tokenAtom: PlayerTokenAtom
tokenCoordinates: { x: number; y: number },
tokenRef: MutableRefObject<HTMLDivElement | null> | null
) => {
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)`;
tokenCoordinates.x += x;
tokenCoordinates.y += y;
ref.current.style.transform = `translate(${tokenCoordinates.x}rem, ${tokenCoordinates.y}rem)`;
};

const moveToken = useCallback(
async (
diceCount: number,
tokenRef: ForwardedRef<HTMLDivElement>,
tokenAtom: PlayerTokenAtom,
setTokenAtom: (prev: PlayerTokenAtom) => PlayerTokenAtom
) => {
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);
const moveToken = async (
diceCount: number,
playerGameBoardData: GameBoardType
) => {
const tokenCoordinates = {
x: playerGameBoardData.coordinates.x,
y: playerGameBoardData.coordinates.y,
};
let tokenDirection = playerGameBoardData.direction;
let tokenLocation = playerGameBoardData.location;

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

tokenLocation = (tokenLocation + 1) % 24;
const isCorner = CORNER_CELLS.includes(tokenLocation);

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

setTokenAtom({
coordinates: tokenCoordinates,
direction: tokenDirection,
location: tokenLocation,
await delay(TOKEN_TRANSITION_DELAY);
}

setPlayers((prev) => {
const targetPlayerIndex = prev.findIndex(
(player) => player.playerId === gameInfo.currentPlayerId
);
const hasEscaped = tokenLocation === 6 ? false : true;

return prev.map((player, index) => {
if (index !== targetPlayerIndex) return player;
return {
...player,
gameboard: {
...player.gameboard,
location: tokenLocation,
coordinates: tokenCoordinates,
direction: tokenDirection,
hasEscaped,
},
};
});
},
[]
);
});
};

const rollDice = (dice1: number, dice2: number) => {
reactDice.current?.rollAll([dice1, dice2]);
};

const rollDone = () => {
setDiceValue(gameInfo.dice[0] + gameInfo.dice[1]);
const totalDiceValue = gameInfo.dice[0] + gameInfo.dice[1];
setDiceValue(totalDiceValue);
const targetPlayer = players.find(
(player) => player.playerId === gameInfo.currentPlayerId
);

if (!targetPlayer) return;
if (!targetPlayer.gameboard.hasEscaped) return;

const targetTokenAtom = tokenList[targetPlayer.order].atom;
const setTargetTokenAtom = tokenList[targetPlayer.order].setAtom;
moveToken(
gameInfo.dice[0] + gameInfo.dice[1],
targetPlayer.tokenRef,
targetTokenAtom,
setTargetTokenAtom
);
moveToken(totalDiceValue, targetPlayer.gameboard);
};

return (
Expand Down
Loading

0 comments on commit e007e75

Please sign in to comment.