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

[3주차 기본/심화/공유 과제] 1 to 50 게임 #3

Open
wants to merge 38 commits into
base: main
Choose a base branch
from
Open

Conversation

ocahs9
Copy link
Contributor

@ocahs9 ocahs9 commented Nov 1, 2024

✨ 구현 기능 명세

💡 기본 과제

  • Context API, 전역상태 라이브러리 사용 X (ThemeProvider 제외)
  1. 헤더
  • 게임/랭킹 2개의 메뉴 선택 가능
  • 게임 선택 시 헤더 우측에 레벨 선택 Select와 타이머 표시
  • 게임 선택 시 게임 판 출력
  • 랭킹 선택 시 헤더 우측엔 아무것도 나오지 않음
  • 랭킹 선택 시 랭킹 보드 출력
  1. 게임
  • (기본) 한 종류의 레벨만 구현
  • 숫자는 항상 랜덤으로 표시됨. (초기 표시 숫자들도, 이후 열리는 숫자들도 모두 랜덤)
  • 처음에 표시되는 숫자는 클릭해야 하는 숫자의 앞에 절반임. 만약 level 1이라 118까지 클릭해야한다면, 처음에는 19까지의 숫자가 랜덤으로 보여짐
  • 게임판 위쪽에 다음으로 클릭해야할 숫자를 표시
  • 1을 누르는 순간 게임이 시작되며 헤더 우측의 타이머가 동작. 타이머는 소수점 2번째 자리까지 측정.
  • 마지막 숫자 클릭시 게임 종료
  • 게임 종료 시, 타이머를 멈추고 alert 창을 띄워주며 걸린 시간을 표시
  • 게임 종료 시, 현재 시각, 게임의 레벨, 플레이 시간 3개의 정보를 localStorage에 저장 (랭킹에서 사용)
  • 종료 창에서 확인 누르면 다시 시작할 수 있는 상태로 게임 초기화
  • 게임 중 level 변경 시 다시 시작할 수 있는 상태로 게임 초기화
  1. 랭킹
  • localStorage에서 데이터 불러오기
  • 플레이 시간 오름차순으로 보여야 함 (빨리 깬 기록이 위쪽)
  • 우측 상단의 초기화 버튼 누르면 대시보드 초기화 (localStorage도 초기화)

🔥 심화 과제

  1. 게임
  • Level 선택 가능
    Level 1: 3 x 3, Level 2: 4 x 4, Level 3: 5 x 5
  • 숫자 클릭할 때 클릭되는 것 같은 효과 (예시: 깜빡거림)
  • 게임 종료 alert 대신, React의 createPortal을 사용하여 Modal 구현
    createPortal
  1. 랭킹
  • Level 내림차순 & 시간 오름차순 정렬(정렬 기준이 2개). 높은 Level이 위쪽으로, 같은 레벨 중에선 플레이 시간이 짧은게 위쪽으로 정렬

공유과제

제목: state 변경에 따른 리렌더링, useEffect, setTimeout 이해하기

링크 첨부 :
https://wave-web.tistory.com/103


❗️ 내가 새로 알게 된 점

  • 제가 2주차 과제 브랜치에서 넋 놓고 작업하다보니 커밋이 2주차랑 섞였는데, 10월 31일 커밋인 [feat: Header 퍼블리싱] 부터 보시면 됩니다
  • createPortal은 그동안 안 써봤는데 이번 과제 덕분에 잘 써봤습니다.
  • 리렌더링 - side effect 관리 + 비동기에 대해 한번 더 이해하게 되었습니다

❓ 구현 과정에서의 어려웠던/고민했던 부분

  • 초반에 제대로 리셋안되는 현상을 지금은 해결했지만, 다른 분들도 겪으셨을 것 같은데 어떻게 해결하셨나 궁금해요!
  • 그리고 저는 grid의 item이 없어지는 걸 배경 색상 변경과 숫자 변경으로 구현했는데, 다른 분들은 어떻게 했나 궁금해요!
  • 마지막으로, 저는 배열 2개를 생성해둔 뒤 갈아끼우는 방식을 채택했는데, 다른 분들 방법도 궁금합니다 ~

🥲 소요 시간

  • 8h

🖼️ 구현 결과물

week3_1.mp4
week_2.mp4
week_3.mp4

ocahs9 added 30 commits October 15, 2024 20:14
@ocahs9 ocahs9 self-assigned this Nov 1, 2024
@ocahs9 ocahs9 changed the title [3주차 기본/심화/공유 과제] 1 to 50 게 [3주차 기본/심화/공유 과제] 1 to 50 개 Nov 1, 2024
@ocahs9 ocahs9 changed the title [3주차 기본/심화/공유 과제] 1 to 50 개 [3주차 기본/심화/공유 과제] 1 to 50 게임 Nov 1, 2024
Copy link

@look-back-luca look-back-luca left a comment

Choose a reason for hiding this comment

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

준혁님 안녕하세요 ~~ 금단디조 정완이에요

역시 OB답게 폴더 구조도 너무 깔끔하고 코드들도 다 가독성 있게 느껴졌어요
희선님과 준혁님의 코드를 보면서 많이 배웠어요
저도 얼른 그렇게 할 수 있는 날이 오기를 바라면서 더 열심히 해보겠습니다 !!! 🤩

게임 기능 구현할 때 레벨마다 value에 들어오는 값을 활용하여 배열의 크기를 조정하는 것 저도 참고하여 리팩토링해보겠습니다! 그리고 이미 알고 계실 것 같지만 !! css 스타일 주실 때 px 단위를 거의 사용하셨는데 rem 값을 사용하시면 좋을 것 같아요

과제하시느라 고생 많으셨습니다 앞으로도 남은 활동 화이팅해요

setIsModalOpen(false);
setTimeout(()=>initGame(),0); //비동기 활용 -> 초기화 안되는 거 방지
}


const initGame = () => {
const initialCells = Array.from({length: cellCount}, (_,i)=> i+1).sort(() => Math.random() - 0.5);

Choose a reason for hiding this comment

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

sort() 메서드가 배열을 무작위 정렬할 때 배열이 클수록 성능이 떨어질 수 있고, 완전히 공평하게 무작위 정렬이 되지 않다고 하여 피셔-예이츠 셔플 알고리즘을 사용하면 시간복잡도를 줄일 수도 있고 더 공평하게 섞을 수 있다고 하여 관련 글 남겨드립니다

https://taesung1993.tistory.com/54

Comment on lines +34 to +35
padding: 20px;
border-radius: 8px;

Choose a reason for hiding this comment

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

반응형 웹 만들 때는 상대 단위 rem을 사용하는 것이 더 좋다고 저도 저번 과제할 때 배워서
px는 rem으로 수정하면 좋을 것 같습니다

Comment on lines 7 to 9
const lineCount = (level + 2);
const cellCount = lineCount ** 2;
const endNumber = cellCount * 2;

Choose a reason for hiding this comment

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

역시 OB는 다르군요 🫢

//기본적으로 모달 컨텐트를 제외한 부분 누르면 닫히도록 구현
return createPortal(
<Overlay onClick={onClose}>
<ModalContent onClick={(e)=> e.stopPropagation()}>

Choose a reason for hiding this comment

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

stopPropagation 메서드도 주시고 세심킹이네요

Choose a reason for hiding this comment

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

stopPropagation... 처음봤어요... 알아갑니다!


&.flash{
animation: flash 0.5s ease-in-out;
}

Choose a reason for hiding this comment

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

이런 식으로 깜빡거림을 구현할 수 있는 것 덕분에 알아갑니다 !!!!

Copy link

@heesunee heesunee Nov 11, 2024

Choose a reason for hiding this comment

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

PR 보고 남겨요! 저는 카드를 섞고 화면에 렌더링 하는 과정을 다음과 같이 분리했었습니다

  • 섞는 로직, 카드 렌더링 컴포넌트 분리
  • 카드 배열은 첫번째와 두번째로 보여질 카드 배열을 각각 분리했습니다. 첫 배열의 인덱스를 클릭하면 해당 인덱스의 숫자가 두번째 배열의 값으로 교체됩니다
  • 두번째 배열의 카드는 클릭시 빈 배열로 설정하여 값을 비우고, 이 경우에는 inVisible 처리가 되도록 구현했습니다!
background-color: ${({ theme, isVisible, isSecondSet }) =>
  isVisible
    ? isSecondSet
      ? theme.colors.mediumblue
      : theme.colors.blue
    : 'transparent'};

이런식으로 작성해서 e.target같은 직접 조작 없이 동적으로 설정했었어요. 참고해보셔도 좋을 것 같습니다 :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

이건 정말 좋은 방법인 것 같아요! 추후 적용해볼게요 !!
배워갑니다 😊

Copy link

@heesunee heesunee left a comment

Choose a reason for hiding this comment

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

OB의 품격이 느껴지는 코드였습니다.. 준혁님 코드를 보고나니까 저는 굳이 필요하지 않은 상태를 추가해서 관리한 것 같기도 하고 ㅎㅎㅎ 🥹 레벨을 상수로 설정하는 것 대신 number로 구현하고 값을 활용하는 방식 등 많이 알아가는 것 같습니다 !! 또 중간중간 주석에 고민하셨던 흔적이 남아있어서 ㅋㅋㅋㅋ 어떤 부분에서 고민하셨는지 공감되기도 하고 이해도 더 잘 갔던 것 같아요

3주차 과제 너무너무 고생많으셨고, 남은 기간도 화이팅입니다 ~~ 👍👍👍

Comment on lines +1 to +7
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
Copy link

Choose a reason for hiding this comment

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

페이지 제목 변경해주기!

Comment on lines +9 to +11
const [mode, setMode] = useState("game");
const [level, setLevel] = useState(1);
const [time, setTime] = useState(0); //ms 단위 (1000ms -> 1s)
Copy link

Choose a reason for hiding this comment

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

저는 const [isRankingMode, setIsRankingMode] = useState(false);로 설정해 랭킹모드가 아니면 게임모드를 보여주게 만들었는데 mode, setMode에 game을 넣어주는게 가독성이 훨씬 좋네요 .. !!

clearInterval(intervalRef.current);
//시작 timestamp, level, time -> 로컬 스토리지 저장 필요

const prevRecords = localStorage.getItem('records') ? JSON.parse(localStorage.getItem('records')) : null;

Choose a reason for hiding this comment

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

삼항 연산자로 쓰신 이유가 있을까요?? || 로 or처리를 할수도 있을 것 같은데, 작성하신 이유가 궁금합니다 !

Copy link
Contributor Author

Choose a reason for hiding this comment

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

딱히 이유는 없었어요! 그냥 if문 사용하긴 싫었고, 한 줄에 표현하고 싶어서 바로 삼항 연산자로 사용했습니다.
근데 희선님 코멘트대로 생각해보니

const prevRecords = JSON.parse(localStorage.getItem('records')) || null;

라고 하는 게 단락 평가를 잘 이용해서 더 깔끔하긴 한 것 같아요! 좋은 의견 감사합니다 !

Comment on lines +82 to +90
// 배경색 초기화
document.querySelectorAll('.flash').forEach(element => {
element.classList.remove('flash');
element.style.backgroundColor = 'pink';
});

setGameCount((prev)=>prev+1);
}

Choose a reason for hiding this comment

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

지금 배경색 초기화하는 부분에서 DOM을 직접 조작하고 있는데, 리액트의 특성을 생각해보면 컴포넌트 상태를 통해 스타일을 제어하는 것도 좋은 방법일 것 같아요 :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

확실히 좋은 방법인 거 같아요! DOM을 직접 조작하는 건 추후 리팩토링에 힘들긴 하니까..!

transition: background-color 0.2s ease, transform 0.1s ease; // 부드러운 전환
${({ isClicked }) =>
isClicked &&
background-color: rgba(255, 255, 255, 0.2); // 클릭 시 배경색 변경 transform: scale(1.02); // 클릭 시 약간 확대}

이런 방식으로 코드를 작성하셨던데, 그냥 색상 천천히 변하게 만들고, visible이 아닐 때는 아예 없애버리는 방식 너무 좋은 것 같아요~! 배워갑니다 !!

Comment on lines +26 to +30
if(targetNumber >= 2){
intervalRef.current = setInterval(()=>{
setTime((prev)=> prev + 10);
},10)
}

Choose a reason for hiding this comment

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

저는 게임이 실행중인 상태를 넣어서 타이머가 실행되도록 설정했는데 클릭한 카드가 2이상으로 갔을 때 타이머를 처리하셨네요! 간단한 방법인 것 같습니다 ㅎㅎ

//기본적으로 모달 컨텐트를 제외한 부분 누르면 닫히도록 구현
return createPortal(
<Overlay onClick={onClose}>
<ModalContent onClick={(e)=> e.stopPropagation()}>

Choose a reason for hiding this comment

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

stopPropagation... 처음봤어요... 알아갑니다!

Comment on lines +20 to +26
const GameSection = styled.section`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;

gap: 3vh;

Choose a reason for hiding this comment

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

gap에 3vh도 화면 크기에 따른 반응형 때문에 사용하시는 걸까요 ?!? 궁금합니다!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

넵 맞습니다 ~~ 제가 vw, vh를 많이 써놔서 gap도 그에 맞게 vh 단위로 작성했어요!

width: 500px;
min-height: 50px;
max-height: 500px;
overflow-y: scroll;

Choose a reason for hiding this comment

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

스크롤까지.. 세심하시네요 👍

Comment on lines +73 to +89
const RankingTitle = styled.h1`

`;

const ResetButton = styled.button`
width: 90px;
height: 30px;
border: 1px solid black;
white-space: nowrap;

position: absolute;
top: 5px;
right: 10px;

background-color: #58a3e4;
color: white;
`

Choose a reason for hiding this comment

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

RankingTitle을 h1으로 설정하기 위해 사용하신 것 같은데 사실상 안에 들어가는 스타일이 없어서 스타일 컴포넌트로 작성하시는 기준이 궁금해요! 랭킹 제목과 초기화 버튼을 담는 div는 또 인라인 스타일로 처리되어있더라구요.
저는, RankingTitle과 ResetButton을 포함하는 div도 컴포넌트로 만들고 타이틀이나 버튼은 그냥 <h1></h1>, 으로 작성한뒤 div컴포넌트안에서 $ h1{}으로 주로 처리를 하거나, 아님 다 컴포넌트로 만들어서 작성하는 편이어서, 의견이 궁금합니다 !

Copy link
Contributor Author

Choose a reason for hiding this comment

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

인라인 스타일은 실제 프로젝트나 협업에서는 거의 사용안하는 편이긴 합니다!
그러나 종종 사용할 떄가 있는데, 특별한 네이밍을 할 정도로 의미가 없거나, 네이밍이 어려울 정도로 복잡한 경우! 입니다.

이번 과제에서는 아무리 css 속성이 없다고 하더라도, 추후 변경될 수도 있는 부분이나, 의미가 충분한 경우 위의 예시처럼 StyledComponent 로 만들어주었습니다. 반대로, 큰 의미가 없고, 앞으로 수정될 것 같지도 않으며, 반복되는 형식의 css도 아닌 경우 그냥 빠르게 코딩하기 위해서 인라인 스타일을 사용했어요!

그래서 정리하자면 정말 별 의미없고, 반복되지 않는 간단한 css를 적용하려는 경우에는 종종 인라인스타일을 쓰지만
왠만해서는 styled-component로 사용하자~ 라는 생각을 하고 있습니다 😊
( + 요즘 tailwind를 사용해보고 있는데, 거기에 조금 익숙해져서 그런 것도 있습니다 ㅎㅎ..)

Comment on lines +37 to +52
<RankingHeader>
<tr>
<th style={{textAlign:"center", border: "1px solid black"}}>타임스탬프</th>
<th style={{textAlign:"center", border: "1px solid black"}}>레벨</th>
<th style={{textAlign:"center", border: "1px solid black"}}>플레이 시간</th>
</tr>
</RankingHeader>
<RankingBody>
{records.map((record,index) => (
<tr key={`record-${index}`}>
<td style={{textAlign:"center", border: "1px solid black"}}>{record.timestamp}</td>
<td style={{textAlign:"center", border: "1px solid black"}}>{`Level${record.level}`}</td>
<td style={{textAlign:"center", border: "1px solid black"}}>{formattedTime(record.time)}</td>
</tr>
))}
</RankingBody>

Choose a reason for hiding this comment

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

엇 th와 td 반복되는 스타일을 밑에 선언되어 있는 RankingHeader와 RankingBody에 적용할 수 있지 않나요?? & th {}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

맞습니다! 이건 그냥 제가 게으른 게 맞아요 ㅎ..
좋은 지적 감사합니다 !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants