Skip to content

Commit

Permalink
feat: Implement game logic and UI (#203)
Browse files Browse the repository at this point in the history
* fix: Fix infinite update loop in room settings

* feat: Add status message for guessers

* feat: Add dynamic toast notifications for game terminationType

* feat: Add floating countdown timer UI

* feat: Update verb to correct usage
  • Loading branch information
choiseona authored Dec 4, 2024
1 parent 42fed3a commit 584b396
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 26 deletions.
7 changes: 6 additions & 1 deletion client/src/components/modal/RoundEndModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import gameWin from '@/assets/sounds/game-win.mp3';
import { Modal } from '@/components/ui/Modal';
import { useModal } from '@/hooks/useModal';
import { useGameSocketStore } from '@/stores/socket/gameSocket.store';
import { useTimerStore } from '@/stores/timer.store';
import { cn } from '@/utils/cn';
import { SOUND_IDS, SoundManager } from '@/utils/soundManager';

Expand All @@ -16,6 +17,7 @@ const RoundEndModal = () => {
const roundWinners = useGameSocketStore((state) => state.roundWinners);
const players = useGameSocketStore((state) => state.players);
const currentPlayerId = useGameSocketStore((state) => state.currentPlayerId);
const timer = useTimerStore((state) => state.timers.ENDING);

const { isModalOpened, openModal, closeModal } = useModal();
const [showAnimation, setShowAnimation] = useState(false);
Expand Down Expand Up @@ -100,7 +102,10 @@ const RoundEndModal = () => {
isModalOpened={isModalOpened}
className="max-w-[26.875rem] sm:max-w-[61.75rem]"
>
<div className="flex min-h-[12rem] items-center justify-center sm:min-h-[15.75rem]">
<div className="relative flex min-h-[12rem] items-center justify-center sm:min-h-[15.75rem]">
<span className="absolute right-2 top-2 flex h-8 w-8 items-center justify-center rounded-full border-2 border-violet-300 text-base text-violet-300">
{timer}
</span>
<p className="text-center text-2xl sm:m-2 sm:text-3xl">
{isDevilWin ? (
<> 정답을 맞춘 구경꾼이 없습니다</>
Expand Down
29 changes: 22 additions & 7 deletions client/src/components/quiz/QuizStage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useMemo } from 'react';
import { PlayerRole } from '@troublepainter/core';
import { PlayerRole, RoomStatus } from '@troublepainter/core';
import { GameCanvas } from '../canvas/GameCanvas';
import { QuizTitle } from '../ui/QuizTitle';
import sizzlingTimer from '@/assets/big-timer.gif';
Expand Down Expand Up @@ -46,20 +46,35 @@ const QuizStageContainer = () => {
return 0;
}
}, [room?.status, timers, roomSettings?.drawTime]);

const quizTitleText = useMemo(() => {
if (room.status === RoomStatus.DRAWING) {
return roundAssignedRole !== PlayerRole.GUESSER ? `${room.currentWord}` : '';
}
if (room.status === RoomStatus.GUESSING) {
return roundAssignedRole !== PlayerRole.GUESSER ? `${room.currentWord} (맞히는중...)` : '맞혀보세요-!';
}
}, [room.status, room.currentWord, roundAssignedRole]);

return (
<>
{/* 구경꾼 전용 타이머 */}
<div className={cn('relative', shouldHideSizzlingTimer && 'hidden')}>
<img src={sizzlingTimer} alt="구경꾼 전용 타이머" width={450} />
<span className="absolute left-[42%] top-[45%] text-6xl text-stroke-md lg:text-7xl">
{timers.DRAWING ?? roomSettings.drawTime - 5}
</span>
<div className={cn(shouldHideSizzlingTimer && 'hidden')}>
<p className="mb-3 text-center text-xl text-eastbay-50 text-stroke-md sm:mb-0 sm:text-2xl lg:text-3xl">
화가들이 실력을 뽐내는중...
</p>
<div className="relative">
<img src={sizzlingTimer} alt="구경꾼 전용 타이머" width={450} />
<span className="absolute left-[42%] top-[45%] text-6xl text-stroke-md lg:text-7xl">
{timers.DRAWING ?? roomSettings.drawTime - 5}
</span>
</div>
</div>

<QuizTitle
currentRound={room.currentRound}
totalRound={roomSettings.totalRounds}
title={room?.currentWord || (roundAssignedRole === PlayerRole.GUESSER ? '맞춰보세용-!' : '')}
title={quizTitleText || '제시어가 없습니다.'}
remainingTime={remainingTime || 0}
isHidden={shouldHideQuizTitle}
/>
Expand Down
31 changes: 15 additions & 16 deletions client/src/components/setting/Setting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export interface RoomSettingItem {
}

export const ROOM_SETTINGS: RoomSettingItem[] = [
{ label: '라운드 수', key: 'totalRounds', options: [3, 5], shortcutKey: 'DROPDOWN_TOTAL_ROUNDS' },
{ label: '라운드 수', key: 'totalRounds', options: [3, 5, 7, 9, 11], shortcutKey: 'DROPDOWN_TOTAL_ROUNDS' },
{ label: '최대 플레이어 수', key: 'maxPlayers', options: [4, 5], shortcutKey: 'DROPDOWN_MAX_PLAYERS' },
{ label: '제한 시간', key: 'drawTime', options: [15, 20, 25, 30], shortcutKey: 'DROPDOWN_DRAW_TIME' },
//{ label: '픽셀 수', key: 'maxPixels', options: [300, 500] },
Expand All @@ -41,21 +41,20 @@ const Setting = memo(({ className, ...props }: HTMLAttributes<HTMLDivElement>) =
setSelectedValues(roomSettings);
}, [roomSettings]);

useEffect(() => {
if (!isHost || !selectedValues || !selectedValues.drawTime) return;
// 방장일 때만 실행되는 설정 업데이트
void gameSocketHandlers.updateSettings({
settings: { ...selectedValues, drawTime: selectedValues.drawTime + 5 },
});
actions.updateRoomSettings(selectedValues);
}, [selectedValues, isHost]);

const handleSettingChange = useCallback((key: keyof RoomSettings, value: string) => {
setSelectedValues((prev) => ({
...prev,
[key]: Number(value),
}));
}, []);
const handleSettingChange = useCallback(
(key: keyof RoomSettings, value: string) => {
const newSettings = {
...selectedValues,
[key]: Number(value),
};
setSelectedValues(newSettings);
void gameSocketHandlers.updateSettings({
settings: { ...newSettings, drawTime: newSettings.drawTime + 5 },
});
actions.updateRoomSettings(newSettings);
},
[selectedValues, actions],
);

return (
<section
Expand Down
2 changes: 1 addition & 1 deletion client/src/layouts/GameLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const GameLayout = () => {
<BrowserNavigationGuard />
<NavigationModal />
<div
className={`before:bg-patternImg relative flex min-h-screen flex-col justify-start bg-gradient-to-b from-violet-950 via-violet-800 to-fuchsia-800 before:absolute before:left-0 before:top-0 before:h-full before:w-full before:bg-cover before:bg-center lg:py-5`}
className={`relative flex min-h-screen flex-col justify-start bg-gradient-to-b from-violet-950 via-violet-800 to-fuchsia-800 before:absolute before:left-0 before:top-0 before:h-full before:w-full before:bg-patternImg before:bg-cover before:bg-center lg:py-5`}
>
{/* 상단 헤더 */}
<GameHeader />
Expand Down
3 changes: 2 additions & 1 deletion client/src/pages/ResultPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ const ResultPage = () => {
terminateType === TerminationType.PLAYER_DISCONNECT
? '나간 플레이어가 있어요. 20초 후 대기실로 이동합니다!'
: '20초 후 대기실로 이동합니다!';
const variant = terminateType === TerminationType.PLAYER_DISCONNECT ? 'warning' : 'success';

toastActions.addToast({
title: '게임 종료',
description,
variant: 'success',
variant,
duration: 20000,
});
}, [terminateType, toastActions]);
Expand Down

0 comments on commit 584b396

Please sign in to comment.