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

[REFACTOR] 게임 시작 전 카운트 다운 애니메이션 기능 구현 #271

Merged
merged 22 commits into from
Sep 25, 2024

Conversation

rbgksqkr
Copy link
Contributor

@rbgksqkr rbgksqkr commented Sep 16, 2024

Issue Number

#270

As-Is

  • 방장 외에 사용자가 게임이 갑자기 시작되서 당황스럽다는 피드백

To-Be

  • 게임 시작 전 3, 2, 1 카운트다운 애니메이션 기능 구현
  • 카운트다운 테스트 코드 작성

Check List

  • 테스트가 전부 통과되었나요?
  • 모든 commit이 push 되었나요?
  • merge할 branch를 확인했나요?
  • Assignee를 지정했나요?
  • Label을 지정했나요?

Test Screenshot

_.mov

(Optional) Additional Description

  • 261번 이슈와 코드가 겹쳐 머지 후 코드 리뷰 진행

카운트다운 구현

  • ❌ before : 게임 시작 → 게임 시작 API 호출 → 폴링 true → 라우팅

    • 유저 : 게임 시작 버튼 클릭 → useGameStart의 startGame 호출 → useGetRoomInfo의 isGameStart 필드가 true로 변경 → navigate
  • ✅ after : 게임 시작 → 게임 시작 API 호출 → 폴링 true → 카운트다운 UI → 라우팅

    • 게임 시작 버튼 클릭 → useGameStart의 startGame 호출 → useGetRoomInfo의 isGameStart 필드가 true로 변경 → 카운트다운 렌더링 → 3초 후 navigate
  • 방장의 동작에 의해 다른 사용자가 영향을 받으려면 응답받는 서버의 데이터가 변경되어야 한다.

  • 따라서, 폴링으로 받는 isGameStart가 true가 될 때 카운트다운을 시작해야 모두가 같은 화면을 볼 수 있다.

  • 그래서 이를 위해 서버 개발자와 소통하여 문제 상황을 공유하였고, 이전에 피드백을 받았던 내용과 함께 총 4초의 여유초를 제한시간보다 더 두기로 결정하였다.

모든 사람이 카운트다운을 보려면 폴링으로 받는 데이터로 카운트다운을 시작해야할 것으로 예상됩니다. 그럼 게임은 게임 시작 API를 호출 한 후 실제로는 3초 뒤에 시작되는데, 라운드 시작 기준으로 timeLimit을 측정한다면 첫번째 게임의 투표시간에는 3초의 여유초가 더 필요할 것 같아요.

🌸 Storybook 배포 주소

https://woowacourse-teams.github.io/2024-ddangkong/storybook/

🌸 Storybook 배포 주소

https://woowacourse-teams.github.io/2024-ddangkong/storybook/

@rbgksqkr rbgksqkr added ♻️ refactor 리팩토링 ✅ test 테스트 관련 🫧 FE front end labels Sep 16, 2024
@rbgksqkr rbgksqkr added this to the FE Sprint5 milestone Sep 16, 2024
@rbgksqkr rbgksqkr self-assigned this Sep 16, 2024
@rbgksqkr rbgksqkr linked an issue Sep 16, 2024 that may be closed by this pull request
2 tasks
Copy link
Contributor

@novice0840 novice0840 left a comment

Choose a reason for hiding this comment

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

리뷰 남깁니다~ 카운트 애니메이션이 이쁘게 잘 되었어요 !!
소스 코드가 많아 보였는데 대부분 모션 관련된 코드라 예상했던 것 보다는 리뷰가 빨리 끝났네요

Comment on lines 7 to 11
const countMapper: Record<number, number> = {
3: 1,
2: 2,
1: 3,
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🙏 제안 🙏

mapper이면 key과 value를 이어준다는 의미일텐데 무엇과 무엇을 매칭 시키는지 감이 안 잡혀서요. 아래쪽 JSX를 확인해보니 땅콩 개수 리스트인 것 같은데

주석을 작성하거나 아예 object에서 array로 바꾸거나 하는 건 어떤가요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

count에 따른 이미지 개수를 의미하는데 array로 바꾸는 건 어떤 걸 의미하는 걸까요?? count 가 3, 2, 1로 줄어들 때, length는 1, 2, 3으로 증가 해야합니다. 어떤 식으로 바꿀 수 있을까요?
일단은 변수명을 수정해서 imageCountMapper 로 바꿔보았습니다!

Copy link
Contributor

Choose a reason for hiding this comment

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

제가 생각했던 건
const Mapper = [
{
count: 3,
image: 1
},
{
count: 2,
image: 2
},
{
count: 1,
image: 3
}]

이런 느낌도 괜찮을 것 같다고 생각했었습니다.

최종적인 판단은 마루에게 맡길게요!

Comment on lines 37 to +39
if (memberInfo.isMaster) {
startGameMutation.mutate();
startCountdown();
Copy link
Contributor

Choose a reason for hiding this comment

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

💭 질문 💭

혹시 이렇게 되면 Countdown이 끝나기 전에 게임이 시작되지는 않나요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

서버에 게임 시작 요청을 보내야 모든 사용자가 게임이 시작됨을 감지할 수 있습니다.
그래서 게임 시작 API를 호출해야 했고, 그 후 카운트다운을 시작하여 3초 뒤에 navigate 되도록 수정하였습니다.
기존에 isGameStart가 true일 때 navigate 되는 로직을 없애고 3초 뒤에 navigate 된다고 보시면 될 것 같습니당

Comment on lines +10 to +23
describe('StartButtonContainer 테스트', () => {
it('게임 시작 버튼을 클릭하면 카운트 다운을 시작한다.', async () => {
const initializeIsMaster = (snap: MutableSnapshot) => {
snap.set(memberInfoState, { memberId: 1, nickname: 'Test User', isMaster: true });
};

const user = userEvent.setup();
customRender(<StartButtonContainer />, { initializeState: initializeIsMaster });
const COUNTDOWN_LABEL_TEXT = '게임 시작 3초 전';

const button = await screen.findByRole('button', { name: '시작' });
await user.click(button);

expect(screen.getByLabelText(COUNTDOWN_LABEL_TEXT)).toBeInTheDocument();
Copy link
Contributor

Choose a reason for hiding this comment

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

🌸 칭찬 🌸

꼼꼼한 테스트 좋네요

Comment on lines +6 to +20
const useCountdown = () => {
const navigate = useNavigate();
const { roomId } = useParams();
const [isCountdownStart, setIsCountdownStart] = useState(false);

const startCountdown = () => {
setIsCountdownStart(true);
};

const goToGame = () => {
navigate(ROUTES.game(Number(roomId)));
};

return { isCountdownStart, startCountdown, goToGame };
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🌸 칭찬 🌸

깔끔한 hook 분리 굿굿

Comment on lines +10 to +16
const StartButton = ({ show, startCountdown }: StartButtonProps) => {
const { memberInfo, handleGameStart } = useGameStart({ showModal: show, startCountdown });

return (
<Button
text={master?.memberId === memberInfo.memberId ? '시작' : '방장이 시작해 주세요'}
disabled={master?.memberId !== memberInfo.memberId}
text={memberInfo.isMaster ? '시작' : '방장이 시작해 주세요'}
disabled={!memberInfo.isMaster}
Copy link
Contributor

Choose a reason for hiding this comment

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

🌸 칭찬 🌸

API response가 바뀌었는데 예전 데이터를 기준으로 방장을 확인하고 있었는데
이제 깔끔하게 변경되었네요 ㅎㅎ

Copy link
Contributor Author

@rbgksqkr rbgksqkr left a comment

Choose a reason for hiding this comment

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

포메 코멘트 답변 달았습니다~!

Comment on lines 37 to +39
if (memberInfo.isMaster) {
startGameMutation.mutate();
startCountdown();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

서버에 게임 시작 요청을 보내야 모든 사용자가 게임이 시작됨을 감지할 수 있습니다.
그래서 게임 시작 API를 호출해야 했고, 그 후 카운트다운을 시작하여 3초 뒤에 navigate 되도록 수정하였습니다.
기존에 isGameStart가 true일 때 navigate 되는 로직을 없애고 3초 뒤에 navigate 된다고 보시면 될 것 같습니당

Comment on lines 7 to 11
const countMapper: Record<number, number> = {
3: 1,
2: 2,
1: 3,
};
Copy link
Contributor Author

Choose a reason for hiding this comment

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

count에 따른 이미지 개수를 의미하는데 array로 바꾸는 건 어떤 걸 의미하는 걸까요?? count 가 3, 2, 1로 줄어들 때, length는 1, 2, 3으로 증가 해야합니다. 어떤 식으로 바꿀 수 있을까요?
일단은 변수명을 수정해서 imageCountMapper 로 바꿔보았습니다!

Copy link
Contributor

@useon useon left a comment

Choose a reason for hiding this comment

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

마루 ~ ! ! 꿈을 펼치라 해서 마음껏 펼쳤는데 멋지게 실현해 주셨군요 ^_^! 따봉 오천개 ~ ! ! 몇 가지 코멘트 남겼는데 편하게 확인해 주셔요 ~ !!! ✨

const meta = {
title: 'Countdown',
component: Countdown,
tags: ['!autodocs'],
Copy link
Contributor

Choose a reason for hiding this comment

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

💭 질문 💭

!autodocs로 설정한 이유가 있으신가요 😊 ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Countdown 컴포넌트 자체는 position:fixed 를 가져서 화면에 잘려 보이더라구요! 카운트다운이 안보여서 의미가 없겠다 싶어 제외했습니다. 높이를 지정해도 안보이더라구요 그래서 docs를 제거했습니다. 어떻게 생각하시나요??

image

Copy link
Contributor

Choose a reason for hiding this comment

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

아하 그런 문제가 있어서 !autodocs로 설정을 했던 거군요! 좋아요 마루 !!!! 저도 종종 잘리는 경우가 있어서 고민을 했었는데 찾아보고 다른 해결 방안을 알게 되면 그때 추가로 공유해 볼게요 !!!! 🤭


const Countdown = ({ goToGame }: CountdownProps) => {
const [count, setCount] = useState(START_COUNTDOWN);
const timeout = useRef<NodeJS.Timeout>();
Copy link
Contributor

Choose a reason for hiding this comment

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

💭 질문 💭

해당 코드에서 useRef의 타입이 number가 아니라 NodeJS.timeout인 이유가 궁금해요!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

setTimeout의 반환값 타입이 NodeJS.Timeout 입니다!
number 타입으로 설정하면 오류가 발생합니다! clearTimeout의 인자로는 다양한 타입이 들어갈 수 있지만 setInterval의 반환값이 NodeJS.Timeout 이기 때문에 이걸로 설정하였습니다.

image image

Copy link
Contributor

Choose a reason for hiding this comment

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

친절한 설명 감사드려요 🌞🌞🌞 !!!1

)}
<div css={imageContainer}>
{Array.from({ length: imageCountMapper[count] }, (_, i) => i + 1).map((idx) => (
<img key={idx} src={SpinDdangkong} css={peanut(idx)} alt={`${idx}번째 카운트다운 땅콩`} />
Copy link
Contributor

Choose a reason for hiding this comment

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

💭 질문 💭

imageCountMapper[count]가 undefined일 경우를 대비해서 || 0 작업을 하지 않아도 괜찮나요 ??

)}
<div css={imageContainer}>
{Array.from({ length: imageCountMapper[count] }, (_, i) => i + 1).map((idx) => (
<img key={idx} src={SpinDdangkong} css={peanut(idx)} alt={`${idx}번째 카운트다운 땅콩`} />
Copy link
Contributor

Choose a reason for hiding this comment

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

🙏 제안 🙏

Array.from 만으로도 동작을 수행할 수 있을 것 같은데 어떻게 생각하시나용 ?

{Array.from({ length: imageCountMapper[count] || 0 }, (_, idx) => (
  <img key={idx + 1} src={SpinDdangkong} css={peanut(idx + 1)} alt={`${idx + 1}번째 카운트다운 땅콩`} />
))}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

undefined여도 렌더링을 안하기 때문에 명시적인 처리를 안해줘도 된다고 생각했습니다! 하지만 명시적인 것도 좋네용
추가적으로 빈배열 자체를 안만들도록 분기처리를 추가해보았습니다!

        {imageCountMapper[count] &&
          Array.from({ length: imageCountMapper[count] }, (_, idx) => (
            <img
              key={idx + 1}
              src={SpinDdangkong}
              css={peanut(idx + 1)}
              alt={`${idx + 1}번째 카운트다운 땅콩`}
            />
          ))}

Copy link
Contributor

Choose a reason for hiding this comment

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

오오 좋은데요 ?!!! 🤭🤭🤭

<div css={countdownLayout}>
<div css={dimmed} />
{count > 0 && (
<span css={countdown} aria-label={`게임 시작 ${count}초 전`}>
Copy link
Contributor

Choose a reason for hiding this comment

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

🌸 칭찬 🌸

aria 추가 너무 좋아요 ~~~~~!!!! 스크린 리더기로 읽었을 때 혹시 어떻게 나왔는지 궁금해요 ! 사실 추후에 집중 개선 작업이 들어갈 것이기 때문에 단지 질문입니다요 ....... 🙌

Copy link
Contributor Author

Choose a reason for hiding this comment

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

3 2 1 카운트다운을 타이밍 맞춰서 읽기가 어려웠는데 생각해보니 바뀔때마다 읽어야하니까 aria-live="polite"를 추가했습니다!

Copy link
Contributor

Choose a reason for hiding this comment

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

접근성 개선 폼 미쵸따이.

jest.advanceTimersByTime(COUNTDOWN_LENGTH);
});

expect(goToGameMock).toHaveBeenCalled();
Copy link
Contributor

Choose a reason for hiding this comment

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

🌸 칭찬 🌸

게임 화면으로 잘 넘어가는 것까지 테스트하는 꼼꼼함 .. !!!!

width: ${2.4 * idx}rem;
height: ${3.6 * idx}rem;

animation: ${peanutAnimation} 1s ease-in-out infinite;
Copy link
Contributor

Choose a reason for hiding this comment

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

🌸 칭찬 🌸

image

우리의 추억 .. PR에도 남겨봅시다 .. 제 꿈을 실현하게 해주어 고마워요 마루 😊🎨🖌️✨

Copy link
Contributor

@useon useon left a comment

Choose a reason for hiding this comment

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

추가 반영 사항 및 코멘트 확인했습니다 🤭 고생했어요 마루 ~ !!! 🌞

@rbgksqkr rbgksqkr merged commit 09ad10a into develop Sep 25, 2024
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🫧 FE front end ♻️ refactor 리팩토링 ✅ test 테스트 관련
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

[REFACTOR] 게임 시작 전 카운트 다운 애니메이션 기능 구현
3 participants