Skip to content

Commit

Permalink
[FE] 최종발표전 버그 수정 및 룰렛 타이머 구현 (#74)
Browse files Browse the repository at this point in the history
* refactor: Dice 컴포넌트 내부 moveToken 함수 커스텀훅으로 분리

- 순간이동 기능 구현시 재사용을 위해서 커스텀훅으로 분리

* refactor: 플레이어의 이동종료 여부 상태 리팩터링

- 기존 CenterArea 컴포넌트 내부 state인 isMoveFinished를 GameInfo에서 전역적으로 관리하도록 변경
- 기존 방식에는 주사위 더블일때 버그가 있어서 수정함
- 안쓰는 nextPlayerStatus 삭제

* fix: 리듀서에서 isMoveFinished, hasEscaped 관련 수정

- 기존 방식으로는 보석금 지불 탈출 시 버그가 있어서 수정

* feat: CenterArea 컴포넌트에서 handleTeleport 함수 구현

- 텔레포트칸에서 유저 위치를 이동시킬 때 사용할 함수 구현
- 인자로 넘길 targetCell을 선택할수 있게 하는 기능 추가예정

* feat: 순간이동 구현 중간 커밋

- 모두에게 같은 화면을 보여주지 못하는 문제 해결 필요

* feat: 순간이동 기능 구현

- 서버에서 teleport 타입 메세지 받는것으로 수정해서 구현
- 텔레포트시 선택된 칸 색깔 강조

* fix: 순간이동 칸으로 다시 순간이동 못하게 수정

* ui: 화면 크기 변경시 깨지는 레이아웃과 버튼 위치 수정 (#72)

* ui: 보드게임판 ui 약간 수정

* fix: 주식 매도 모달 & 버튼 위치 및 버그 수정

* feat: 매도시간 타이머가 끝나면 룰렛이 돌아가는 기능 구현

- 테스트용 버튼은 남겨둠 (선턴 플레이어만 클릭 가능)
- 버튼으로 작동시 룰렛이 이상하게 움직이는 버그 존재
  • Loading branch information
silvertae authored Nov 2, 2023
1 parent 94752a8 commit d7b81e0
Show file tree
Hide file tree
Showing 16 changed files with 177 additions and 89 deletions.
6 changes: 4 additions & 2 deletions fe/src/components/GameBoard/Cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ export default function Cell({
>
{cell.theme && (
<Header>
<Logo src={cellImageMap[cell.logo]} />
<Name>{cell.name}</Name>
</Header>
)}
<Content>
{cell.theme && <Logo src={cellImageMap[cell.logo]} />}
{!cell.theme && <CellImg src={cellImageMap[cell.logo]} />}
{price && <span>{addCommasToNumber(price)}</span>}
</Content>
Expand All @@ -71,8 +71,10 @@ const Container = styled.div<{
`;

const Header = styled.div`
min-height: 2rem;
display: flex;
justify-content: center;
align-items: center;
background-color: ${({ theme: { color } }) => color.accentText};
`;

Expand All @@ -94,6 +96,6 @@ const Content = styled.div`
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
justify-content: space-evenly;
align-items: center;
`;
47 changes: 45 additions & 2 deletions fe/src/components/GameBoard/GameBoard.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import useGetSocketUrl from '@hooks/useGetSocketUrl';
import {
playerAtomsAtom,
useGameInfoValue,
Expand All @@ -6,6 +7,8 @@ import {
} from '@store/reducer';
import { useAtom } from 'jotai';
import { useState } from 'react';
import { useParams } from 'react-router-dom';
import useWebSocket from 'react-use-websocket';
import { css, styled } from 'styled-components';
import Cell from './Cell';
import CenterArea from './CenterArea';
Expand All @@ -18,12 +21,28 @@ export default function GameBoard() {
const stockList = useStocksValue();
const players = usePlayersValue();
const [playerAtoms] = useAtom(playerAtomsAtom);
const socketUrl = useGetSocketUrl();
const { gameId } = useParams();
const { sendJsonMessage } = useWebSocket(socketUrl, {
share: true,
});

const isEveryoneReady = players.every(
(player) => player.playerId === '' || player.isReady
);
const currentPlayer = players.find(
(player) => player.playerId === gameInfo.currentPlayerId
);
const currentPlayerStatus = currentPlayer?.gameboard.status ?? 'event';

const handleStart = () => {
const message = {
type: 'start',
gameId,
};
sendJsonMessage(message);
};

const selectTargetLocation = (location: number) => {
setTargetLocation(location);
};
Expand All @@ -33,7 +52,7 @@ export default function GameBoard() {
};

return (
<>
<Container>
<Board>
{initialBoard.map((line, index) => (
<Line key={index} $lineNum={index + 1}>
Expand All @@ -54,6 +73,9 @@ export default function GameBoard() {
})}
</Line>
))}
{!gameInfo.isPlaying && isEveryoneReady && (
<Button onClick={handleStart}>게임 시작</Button>
)}
{gameInfo.isPlaying && (
<CenterArea
currentStatus={currentPlayerStatus}
Expand All @@ -65,11 +87,20 @@ export default function GameBoard() {
return <PlayerToken key={`${playerAtom}`} playerAtom={playerAtom} />;
})}
</Board>
</>
</Container>
);
}

const Container = styled.div`
position: relative;
display: flex;
justify-content: center;
align-items: center;
`;

const Board = styled.div`
width: 42rem;
height: 42rem;
min-width: 42rem;
min-height: 42rem;
position: relative;
Expand All @@ -82,6 +113,18 @@ const Line = styled.div<{ $lineNum: number }>`
${({ $lineNum }) => drawLine($lineNum)}
`;

const Button = styled.button`
width: 6rem;
height: 4rem;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border-radius: ${({ theme: { radius } }) => radius.small};
color: ${({ theme: { color } }) => color.neutralText};
background-color: ${({ theme: { color } }) => color.neutralBackground};
`;

const drawLine = (lineNum: number) => {
switch (lineNum) {
case 1:
Expand Down
75 changes: 59 additions & 16 deletions fe/src/components/GameBoard/Roulette.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,65 @@
import EventModal from '@components/Modal/EventModal/EventModal';
import useGetSocketUrl from '@hooks/useGetSocketUrl';
import { usePlayerIdValue } from '@store/index';
import { useGameInfo, useResetEventRound } from '@store/reducer';
import { delay } from '@utils/index';
import { useEffect, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import { Wheel } from 'react-custom-roulette';
import { useParams } from 'react-router-dom';
import useWebSocket from 'react-use-websocket';
import { styled } from 'styled-components';

export default function Roulette() {
const [mustSpin, setMustSpin] = useState(false);
const [isEventModalOpen, setIsEventModalOpen] = useState(false);
const [prizeNumber, setPrizeNumber] = useState(0);
const [stockSellTime, setStockSellTime] = useState(30);
const [isEventModalOpen, setIsEventModalOpen] = useState(false);

const { gameId } = useParams();
const [gameInfo] = useGameInfo();
const playerId = usePlayerIdValue();
const socketUrl = useGetSocketUrl();
const resetGameInfo = useResetEventRound();

const { sendJsonMessage } = useWebSocket(socketUrl, {
share: true,
});

const startSpin = useCallback(() => {
const eventListData = gameInfo.eventList.map((event) => event.title);
if (eventListData.length === 0) return;
if (gameInfo.firstPlayerId !== playerId) return;
const message = {
type: 'eventResult',
gameId,
events: eventListData,
};
sendJsonMessage(message);
}, [
gameId,
playerId,
gameInfo.eventList,
gameInfo.firstPlayerId,
sendJsonMessage,
]);

useEffect(() => {
let isMounted = true;
const stockSellTimer = async () => {
await delay(1000);
if (!isMounted) return;
if (stockSellTime > 0) {
setStockSellTime((prev) => prev - 1);
} else {
startSpin();
}
};
stockSellTimer();

return () => {
isMounted = false;
};
}, [stockSellTime, startSpin]);

useEffect(() => {
if (gameInfo.eventResult === '') return;
const prizeNumber = gameInfo.eventList.findIndex(
Expand All @@ -35,16 +74,6 @@ export default function Roulette() {
return { option: event.title };
});

const handleSpinClick = () => {
const eventListData = gameInfo.eventList.map((event) => event.title);
const message = {
type: 'eventResult',
gameId,
events: eventListData,
};
sendJsonMessage(message);
};

const handleSpinDone = async () => {
setIsEventModalOpen(true);
await delay(5000);
Expand All @@ -61,20 +90,34 @@ export default function Roulette() {
data={wheelData}
fontSize={16}
spinDuration={0.5}
textColors={['#fff', '#000']}
radiusLineWidth={2}
outerBorderWidth={2}
pointerProps={{ style: { width: '70px', height: '70px' } }}
textColors={['#FCF5ED', '#000']}
backgroundColors={['#3e3e3e', '#f4acb7']}
onStopSpinning={handleSpinDone}
/>
<Button onClick={handleSpinClick}>Spin!</Button>
<Wrapper>
<Timer>남은 매도시간: {stockSellTime}</Timer>
<Button onClick={startSpin}>룰렛 테스트 버튼</Button>
</Wrapper>
{isEventModalOpen && <EventModal />}
</>
);
}

const Wrapper = styled.div`
width: 100%;
display: flex;
justify-content: space-around;
`;

const Timer = styled.div`
font-size: ${({ theme }) => theme.fontSize.sMedium};
`;

const Button = styled.button`
width: 150px;
height: 100px;
margin-right: 10px;
margin-bottom: 10px;
align-self: flex-end;
Expand Down
2 changes: 1 addition & 1 deletion fe/src/components/GameBoard/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const initialBoard = [
{
theme: 'pharmaceutical',
logo: 'samsungbio',
name: '삼성바이오로직스',
name: '삼성바이오',
location: 13,
},
{ theme: 'it', logo: 'google', name: '구글', location: 14 },
Expand Down
13 changes: 2 additions & 11 deletions fe/src/components/Header/GameHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import StatusBoardModal from '@components/Modal/StatusBoardModal/StatusBoardModal';
import StockBuyModal from '@components/Modal/StockBuyModal/StockBuyModal';
import StockSellModal from '@components/Modal/StockSellModal/StockSellModal';
import { Icon } from '@components/icon/Icon';
import useSound from '@hooks/useSound';
import { ROUTE_PATH } from '@router/constants';
Expand All @@ -12,7 +11,7 @@ export default function GameHeader() {
const navigate = useNavigate();
const [isStatusBoardModalOpen, setIsStatusBoardModalOpen] = useState(false);
const [isStockBuyModalOpen, setIsStockBuyModalOpen] = useState(false);
const [isStockSellModalOpen, setIsStockSellModalOpen] = useState(false);

const {
isSoundPlaying,
togglePlayingSound,
Expand All @@ -33,16 +32,11 @@ export default function GameHeader() {
setIsStockBuyModalOpen((prev) => !prev);
};

const toggleStockSellModal = () => {
setIsStockSellModalOpen((prev) => !prev);
};

return (
<>
<Header>
<Logo>Gaemi Marble</Logo>
<Temp>
<IconContainer onClick={toggleStockSellModal}>매도하기</IconContainer>
<IconContainer onClick={toggleStockBuyModal}>칸도착</IconContainer>
<IconContainer>
<Icon
Expand Down Expand Up @@ -76,9 +70,6 @@ export default function GameHeader() {
{isStockBuyModalOpen && (
<StockBuyModal handleClose={toggleStockBuyModal} />
)}
{isStockSellModalOpen && (
<StockSellModal handleClose={toggleStockSellModal} />
)}
{GameBgm}
</>
);
Expand All @@ -90,9 +81,9 @@ const Header = styled.div`
}
width: 100%;
display: flex;
position: fixed;
top: 0.5rem;
padding: 0 2rem;
margin: 1rem 0;
justify-content: space-between;
`;

Expand Down
6 changes: 4 additions & 2 deletions fe/src/components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ export default function Layout() {
}

const Page = styled.div`
width: 100vw;
height: 100vh;
width: max-content;
height: max-content;
min-width: 100vw;
min-height: 100vh;
display: flex;
flex-direction: column;
color: ${({ theme: { color } }) => color.accentText};
Expand Down
35 changes: 22 additions & 13 deletions fe/src/components/Modal/StockSellModal/StockSellModalContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type StockSellModalContentProps = {
export default function StockSellModalContent({
handleClose,
}: StockSellModalContentProps) {
const { game, players, stocks } = useGameValue();
const { players, stocks } = useGameValue();
const playerId = usePlayerIdValue();
const { gameId } = useParams();
const socketUrl = useGetSocketUrl();
Expand Down Expand Up @@ -86,27 +86,30 @@ export default function StockSellModalContent({
</tr>
</thead>
<tbody>
{!!playerStocks?.length &&
{playerStocks?.length ? (
playerStocks.map((stock) => (
<StockSellTableData
stock={stock}
salesList={salesList}
handleSalesQuantity={handleSalesQuantity}
/>
))}
))
) : (
<EmptyTable>
<td colSpan={7}>보유한 주식이 없습니다.</td>
</EmptyTable>
)}
</tbody>
</StockInfoTable>
<span>총 매도 가격: {addCommasToNumber(totalPrice)}</span>
{playerId === game.currentPlayerId && (
<ButtonWrapper>
<Button className="close" onClick={handleClose}>
닫기
</Button>
<Button className="sell" onClick={handleSellStock}>
매도
</Button>
</ButtonWrapper>
)}
<ButtonWrapper>
<Button className="close" onClick={handleClose}>
닫기
</Button>
<Button className="sell" onClick={handleSellStock}>
매도
</Button>
</ButtonWrapper>
</ModalContent>
);
}
Expand Down Expand Up @@ -143,6 +146,12 @@ const StockInfoTable = styled.table`
}
`;

const EmptyTable = styled.tr`
td {
text-align: center;
}
`;

const ButtonWrapper = styled.div`
display: flex;
align-items: center;
Expand Down
Loading

0 comments on commit d7b81e0

Please sign in to comment.