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 게임 #6

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

Conversation

Rinakk
Copy link
Contributor

@Rinakk Rinakk commented Nov 5, 2024

✨ 구현 기능 명세

게임 설명

  • 1부터 N까지 숫자를 순서대로 빠르게 클릭하는 게임

  • Level에 따라서 클릭해야 하는 숫자 범위가 다름

    Level 1: 3x3 판에서 진행. 1 ~ 18까지 클릭
    Level 2: 4x4 판에서 진행. 1 ~ 32까지 클릭
    Level 3: 5x5 판에서 진행. 1 ~ 50까지 클릭

  • 숫자는 매번 랜덤으로 배치됨

  • 처음에는 클릭해야하는 숫자중 앞에 절반이 화면에 랜덤으로 보여짐

    1부터 클릭할 때마다, 뒷쪽 나머지 숫자가 랜덤으로 채워짐

전체

구현 명세

  • Context API, 전역상태 라이브러리 사용 X (ThemeProvider 제외)

헤더

구현 명세

  • 게임/랭킹 2개의 메뉴 선택 가능
  • 게임 선택 시 헤더 우측에 레벨 선택 Select와 타이머 표시
    레벨 선택 기능은 심화과제. 하지만 Select 퍼블리싱은 기본 과제
  • 게임 선택 시 게임 판 출력
  • 랭킹 선택 시 헤더 우측엔 아무것도 나오지 않음
  • 랭킹 선택 시 랭킹 보드 출력

게임

구현 명세

  • [기본] 한 종류의 레벨만 구현
  • 숫자는 항상 랜덤으로 표시됨. (초기 표시 숫자들도, 이후 열리는 숫자들도 모두 랜덤)
  • 처음에 표시되는 숫자는 클릭해야 하는 숫자의 앞에 절반임.
    만약 level 1이라 118까지 클릭해야한다면, 처음에는 19까지의 숫자가 랜덤으로 보여짐
  • 게임판 위쪽에 다음으로 클릭해야할 숫자를 표시
  • 1을 누르는 순간 게임이 시작되며 헤더 우측의 타이머가 동작.
    타이머는 소수점 2번째 자리까지 측정.
  • 마지막 숫자 클릭시 게임 종료
  • 게임 종료 시, 타이머를 멈추고 alert 창을 띄워주며 걸린 시간을 표시
  • 게임 종료 시, 현재 시각, 게임의 레벨, 플레이 시간 3개의 정보를 localStorage에 저장
    (랭킹에서 사용)
  • 종료 창에서 확인 누르면 다시 시작할 수 있는 상태로 게임 초기화
  • 게임 중 level 변경 시 다시 시작할 수 있는 상태로 게임 초기화

심화

  • Level 선택 가능
    Level 1: 3 x 3, Level 2: 4 x 4, Level 3: 5 x 5
  • 숫자 클릭할 때 클릭되는 것 같은 효과 (예시: 깜빡거림)
  • 게임 종료 alert 대신, React의 createPortal을 사용하여 Modal 구현

랭킹

구현 명세

  • localStorage에서 데이터 불러오기
  • 플레이 시간 오름차순으로 보여야 함 (빨리 깬 기록이 위쪽)
  • 우측 상단의 초기화 버튼 누르면 대시보드 초기화 (localStorage도 초기화)

심화

  • Level 내림차순 & 시간 오름차순 정렬 (정렬 기준이 2개)
    높은 Level이 위쪽으로, 같은 레벨 중에선 플레이 시간이 짧은게 위쪽으로 정렬

❗️ 내가 새로 알게 된 점

  • ~ 부분 이렇게 구현했어요, 피드백 부탁해요!

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

  • ~ 부분이 잘 구현한건지 잘 모르겠어요!
  • ~부분 다른 방법이 있는지 궁금해요!

🥲 소요 시간


🖼️ 구현 결과물

@Rinakk Rinakk requested review from yarimu and imddoy November 6, 2024 01:33
Copy link

@imddoy imddoy left a comment

Choose a reason for hiding this comment

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

린아님 안녕하세요!! React가 이번이 처음이라고 하셨는데 너무 코드도 깔끔하고 폴더구조도 잘 나눠주셔서 깜짝 놀랐습니다🫢🫢 천재 개발자의 탄생을 제가 이렇게 직관할 수 있다니 영광입니다!!! 덕분에 많이 배우고 갑니다!!
진짜 너무 깔끔하게 작성해주셔서 코드리뷰할 부분이 딱히 없어서 어떤 부분을 해야하나 고민을 많이 한 것 같아요 ㅎㅎㅎㅋㅋ
이번 과제 하시느라 수고 많으셨구!! 앱잼 전까지 나머지 부분들 구현하시면 다시 찾아올게요 헤헤

너무 코드를 깔끔하게 작성하셔서 완성된 코드가 진짜 궁금합니다....꼭 완성하면 삐삐쳐주세요!!
이번 주차도 수고 많으셨습니다🤗🤗

Copy link

Choose a reason for hiding this comment

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

여기 파일명이 app.jsx로 되어있는데요!
일반적으로 React 컴포넌트 파일 이름은 대문자로 시작해서 파스칼 케이스에 맞게 작성합니다!!
크게 문제가 되는 것은 아니지만, App.jsx로 이름을 지어서 컴포넌트라는 것을 쉽게 알 수 있도록 하는 것을 추천드립니다 :)

Comment on lines +9 to +10
const App = () => {
const [selectedTab, setSelectedTab] = useState('game'); // 'game' 또는 'ranking'
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 +29 to +36

const startGame = () => {
setTime(0); // 타이머 초기화
setNextNumber(1);
setIsGameStarted(false);
setIsGameCompleted(false);
};

Copy link

Choose a reason for hiding this comment

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

이 함수는 선언만 되어있고 호출은 안하고 있는데 선언하신 궁금합니다!!
상태들을 초기화하고 있는데 이미 위에서 처음에 초기화해준 것이랑 같기 때문에 작성하신 의도도 궁금합니다!ㅎㅎ

const App = () => {
const [selectedTab, setSelectedTab] = useState('game'); // 'game' 또는 'ranking'
const [isGameStarted, setIsGameStarted] = useState(false);
const [isGameCompleted, setIsGameCompleted] = useState(false);
Copy link

Choose a reason for hiding this comment

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

혹시 여기 isGameCompleted 상태는 무슨 역할을 하는 상태인가요?? 이 상태값을 사용하는 로직이 없는 것 같아서요!!

Comment on lines +13 to +15
const [time, setTime] = useState(0);
const [nextNumber, setNextNumber] = useState(1);
const timerRef = useRef(null);
Copy link

Choose a reason for hiding this comment

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

useRef를 사용해서 불필요한 리렌더링을 방지하는 코드 너무 좋은 것 같습니다!! 최고최고👍🏻👍🏻

Copy link

Choose a reason for hiding this comment

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

setInterval의 리렌더링만 방지하고 있어서 타이머의 상태가 바뀔 때 발생하는 리렌더링은 방지하지 못하고 있어요...! 이 부분도 방지하면 더 좋을 것 같습니다 :)

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 +12 to +23
const initializeBoard = () => {
const initialNumbers = Array.from({ length: 9 }, (_, i) => i + 1); // 1 to 9
shuffleArray(initialNumbers);
setNumbers(initialNumbers);
};

const shuffleArray = (array) => {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
};
Copy link

Choose a reason for hiding this comment

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

shuffleArray에서 initialNumbers라는 배열의 값을 직접 변경해주면서 불변성이 지켜지지 않고 있습니다..!

React에서는 불변성을 지키는 것이 중요합니다.
물론 이 코드에서는 원본 배열을 다시 사용하지 않기 때문에 문제가 발생하지 않지만, 만약 원본 배열이 다른 곳에서도 사용되고 있다고 가정한다면 직접 값을 바꾸면 다른 곳에도 영향을 주기 때문에 예상치못한 문제가 발생할 수 있습니다.
참고하면 좋은 블로그 같이 첨부드립니다.

따라서 아래처럼 코드를 수정해서 불변성을 지키는 것은 어떨까요??ㅎㅎ

Suggested change
const initializeBoard = () => {
const initialNumbers = Array.from({ length: 9 }, (_, i) => i + 1); // 1 to 9
shuffleArray(initialNumbers);
setNumbers(initialNumbers);
};
const shuffleArray = (array) => {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
};
const initializeBoard = () => {
const initialNumbers = Array.from({ length: 9 }, (_, i) => i + 1); // 1 to 9
const shuffledNumbers = shuffleArray(initialNumbers);
setNumbers(shuffledNumbers);
};
const shuffleArray = (array) => {
const newArray = [...array]; // 원본 배열 복사
for (let i = newArray.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[newArray[i], newArray[j]] = [newArray[j], newArray[i]];
}
return newArray;
};

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
border-radius: 5px;
transition: background-color 0.2s;
}
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 +11 to +18
const loadRecords = () => {
const storedRecords = Object.keys(localStorage)
.filter((key) => key.startsWith('gameResult_'))
.map((key) => JSON.parse(localStorage.getItem(key)))
.sort((a, b) => parseFloat(a.playTime) - parseFloat(b.playTime)); // 플레이 시간 기준으로 오름차순 정렬

setRecords(storedRecords);
};
Copy link

Choose a reason for hiding this comment

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

로컬스토리지에 저장할 때 gameResult_${Date.now()}로 작성하신 부분이 인상깊었는데 이렇게 데이터를 저장하고 있었네요!! 신기하네요!!ㅎㅎ

그런데 로컬스토리지에 많은 양을 저장하고 있다면 모든 값들을 가져와서 필터링을 해야하니까 비효율적일 수도 있을 것 같습니다.
그렇지만 지금 과제에서는 이 값만 저장하고 있기 때문에 크게 문제가 되지는 않을 것 같네요!! 새롭게 배우고 갑니다 ㅎㅎ

Comment on lines +57 to +58
<td colSpan="3" style={{ textAlign: 'center' }}>
기록이 없습니다.
Copy link

@imddoy imddoy Nov 9, 2024

Choose a reason for hiding this comment

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

인라인으로 스타일을 지정하는 것은 스타일 우선순위때문에 유지보수도 힘들고 예측 불가능하기 때문에 지양해야 합니다!

그런데 React 환경에서는 더욱 지양해야하는데요!! 인라인으로 스타일을 지정하면 객체를 새롭게 만들게 됩니다. React가 리랜더링을 할 때, 인라인 스타일의 객체의 주소값을 비교하는데, 주소값이 동일하지 않기 때문에 성능이 저하된다고 합니다.

클래스를 지정해서 스타일을 주거나 아래 코드처럼 useMemo를 사용해서 불필요한 객체 생성을 막아주세요!!

const centerTextStyle = useMemo(() => ({
    textAlign: "center",
  }), []);
// ...
<td colSpan="3" style={centerTextStyle}>
  기록이 없습니다.
</td>

Copy link

Choose a reason for hiding this comment

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

오호..! 저도 새롭게 알아가네요!!

Copy link

@yarimu yarimu left a comment

Choose a reason for hiding this comment

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

조장님이 이미 피드백을 너무 잘 주셔서 뒤늦게 한 저는 코멘트 할게 별로 없네요..ㅎㅎ
5조는 레전드 인걸까요..?
폴더구조 보고 정말 놀랐습니다! Tile단위까지 컴포넌트 분리하신거 보고 감동받았어요!!
저도 컴포넌트 조금 더 분리해봐야겠습니다..!
useRef useEffect까지.. 진짜 레전드 개발자의 탄생인데요!?!!? 기대됩니다 츄류륩

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

Successfully merging this pull request may close these issues.

3 participants