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주차 기본/심화/생각 과제 ] 🌼 옴팡이를 찾아라 💖 #4

Merged
merged 27 commits into from
May 30, 2023

Conversation

seobbang
Copy link
Member

@seobbang seobbang commented May 4, 2023

✨ 구현 기능 명세

기본 과제

 게임 난이도 선택

  1. 난이도의 종류
    1. easy → 10개 :: 5쌍 맞추기
    2. normal → 14개 :: 7쌍 맞추기
    3. hard → 18개 :: 9쌍 맞추기
  2. 난이도 중간에 바꿀시, 카드 모두 뒤집어서 처음으로 돌아가기

✅ 정답 수 노출

  1. (현재 나의 스코어) / 전체 정답 으로 상단에 노출

✅ 카드 선택

  1. 2개의 카드를 선택하면 다른 카드는 선택할 수 없습니다.
  2. 해당 카드의 일치 여부를 조사
    1. 정답일 경우
      1. 정답 스코어 증가
      2. 카드 뒤집힌 채로 유지
    2. 오답일 경우
      1. 카드 다시 뒷면으로 돌리기

✅ 카드 배열 순서

  1. 카드가 배열되는 순서는 반드시 랜덤으로 지정
  2. 난이도에 따라 지정되는 쌍도 랜덤으로 지정
    1. 종류 선택시 태그를 카드섹션 위에 하나씩 부착합니다
    2. 태그별 상품 리스트 보여줍니다.
    3. 전체 → 다보여주기
    4. 종류별 → 필터기능
    5. x 클릭시
    6. 카드 다시 정렬
    7. 종류 선택 해제
    8. 태그 삭제

심화 과제

 애니메이션

  1. 카드를 선택
    1. 뒤집어지는 기깔나는 애니메이션을 적용해주세요!!
  2. 카드 쌍을 맞춘 경우
    1. 저는 현재 스코어가 빛나는전 애니메이션을 적용했습니다! 마음대루!!

✅ theme + styled-components :: 적용

  1. globalstyle
  2. theme
    1. 전역에서 사용할 수 있도록 적용해보세요!

게임 초기화 버튼

  1. 게임 도중 ‘다시하기’ 버튼을 누르면 모든 게임 설정이 처음으로 돌아갑니다.

createPortal

  1. 모든 카드 맞추기가 끝난 후 보여주는 모달을 Portal을 활용해 만들어주세요!

✅ 생각 과제


🌼 PR Point

전역 상태관리 라이브러리를 사용하지 않고 구현했습니다!

FindOmpangi.jsx

  • 랜덤으로 카드가 결정되도록 하기 위해서 셔플 함수를 만들었어요.
  // 배열 셔플 함수
  const shuffling = () => {
    const shuffle = (array) => {
      let newArray = array.sort(() => Math.random() - 0.5);
      return newArray;
    };

    shuffle(OMPANGI_DATA);
    const slicedData = OMPANGI_DATA.slice(
      0,
      level === EASY ? 5 : level === NORMAL ? 7 : 9
    );
    return shuffle([...slicedData, ...slicedData]);
  };
  • level이 바뀌거나 reset 버튼이 눌리지 않는 한 만들어진 랜덤 데이터가 바뀌지 않도록 useMemo를 이용했어요
  // 렌더링 할 랜덤 배열 만들기
  let renderData = useMemo(() => {
    return shuffling();
  }, [level, reset]);

CardSection.jsx

  • 카드 앞면과 뒷면을 openCardList에 따라 조건부 렌더링했어요.
// 카드 렌더링
  const cardList = renderData.map((item, idx) => {
    const { id: cardId, imgSrc, alt } = item;
    // openCardList에 인덱스 있는지 검사해서 조건분기 렌더링
    return openCardList.includes(idx) ? (
      <St.CardFront
        key={`${cardId}_${idx}_front`}
        className={
          openCardList.indexOf(idx) >= openCardList.length - 2 && `${isRotate}`
        }
      >
        <img src={imgSrc} alt={alt} />
      </St.CardFront>
    ) : (
      <St.CardBack
        key={`${cardId}_${idx}_back`}
        id={idx}
        className={cardId}
        onClick={isClickAbled ? handleCardClick : null}
      >
        🌼
      </St.CardBack>
    );
  });
  • 짝 맞추기 실패시,
  1. 일정 시간이 지난 후(1.2초) 회전
  2. 이후 열린 카드 배열 수정, 이외 카드 클릭 허용 등
    필요한 작업을 동기적으로 구현하기 위해 setTimeOut 메소드를 이용했어요.
    👉 이런 상황에서 동기적으로 구현할 수 있는 더 좋은 방법이 있다면 공유해주세요! 👐
 // 실패
    else {
      setTimeout(() => {
        setIsRotate("rotate");
      }, 1200);
      // 카드 오픈 리스트에서 최근에 넣었던 두 개 카드 삭제
      setTimeout(() => {
        const newList = openCardList.slice(0, -1);
        setOpenCardList(newList);
        setIsClickAbled(true);
        setIsRotate("");
      }, 1300);
    }

Header.jsx

  • 저는 정답을 맞췄을 때, 점수가 깜빡이는 애니메이션을 적용해보았어요!
    최초 렌더링에는 깜빡이지 않도록 early return을 해주었습니다!
  useEffect(() => {
    // 최초 렌더링 early return
    if (score === 0) return;

    if (score === cardCount) {
      setIsModalOpen(true);
    }

    setIsBlink(true);
    setTimeout(() => {
      setIsBlink(false);
    }, 2500);
  }, [score]);

1차 리팩토링
testCardList의 경우 선택한 두 카드가 짝이 맞는지 검사하기 위한 배열인데, 원래는 state로 만들었다가 다시 생각해보니 렌더링과 관련 없는 변수이므로 state로 관리하는게 적합하지 않다고 생각했어요! 그래서 일반 변수로 바꿔주었습니당


🥺 소요 시간, 어려웠던 점

  • 처음에 카드 앞면과 뒷면을 조건부 렌더링을 해버렸더니 뒤집히는 이벤트 구현할 때 잘 안돼서 힘들었어요 😂
    그래서 중간에 class 유무에 따라 앞 뒤를 보여주는 방식으로 다시 바꿀까 하다가, 그냥 한 번 이 상태로 구현해봤습니다! ㅎㅎ

🌈 구현 결과물

image

level 바꾸기
녹화_2023_05_04_23_01_58_514

카드게임 구현 (뒤집어지는 애니메이션, 쌍을 맞춘 경우 깜빡이는)
녹화_2023_05_04_23_02_28_595

level 바꾸면 초기화
녹화_2023_05_04_23_03_01_493

reset 버튼
녹화_2023_05_04_23_13_29_929

모달 구현
녹화_2023_05_04_23_12_46_40

@seobbang seobbang self-assigned this May 4, 2023
@seobbang seobbang changed the title [ 3주차 기본/심화/생각 과제 ] 🌼 몰티즈를 찾아라 💖 [ 3주차 기본/심화/생각 과제 ] 🌼 옴팡이를 찾아라 💖 May 5, 2023
@seobbang seobbang requested review from ljh0608 and hae2ni May 5, 2023 13:42
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="ko">
Copy link

Choose a reason for hiding this comment

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

아니 디테일 뭐냐구,, 감덩이다 정말

Copy link
Member

Choose a reason for hiding this comment

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

우리 셋은 이건 꼭 지켜야지.. 맞지맞지

<>
<ThemeProvider theme={theme}>
<GlobalStyle />
<Router />
Copy link

Choose a reason for hiding this comment

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

Router를 벌써 쓴거얌..?

<St.CardFront
key={`${cardId}_${idx}_front`}
className={
openCardList.indexOf(idx) >= openCardList.length - 2 && `${isRotate}`
Copy link

Choose a reason for hiding this comment

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

혹시 이부분 자세히 설명해줄 수 있으까욤..?

margin-top: 1rem;
`,

CardFront: styled.article`
Copy link

Choose a reason for hiding this comment

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

시멘틱 태그 다 써준 거 너무너무 죠타아! 👍

import styled from "styled-components";

const Header = ({ level, score, setIsModalOpen }) => {
const cardCount = level === "EASY" ? 5 : level === "NORMAL" ? 7 : 9;
Copy link

Choose a reason for hiding this comment

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

아니 엄청 여러가지 생각을 하게 해준 코드였ㅓ,,, (나는 하나하나 조건 나눠서 useState로 바꿨는데, 코드 줄이기 딱 좋다 배워가요ㅠㅠ!


const Header = ({ level, score, setIsModalOpen }) => {
const cardCount = level === "EASY" ? 5 : level === "NORMAL" ? 7 : 9;
const [isBlink, setIsBlink] = useState(false);
Copy link

Choose a reason for hiding this comment

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

이거는 다 맞췄을 때 반짝이는 애니메이션 효과에 관한 건가요?

Copy link

Choose a reason for hiding this comment

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

pr 다시 읽어보니까 맞구만

@@ -0,0 +1,132 @@
import styled from "styled-components";
Copy link

Choose a reason for hiding this comment

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

아니 파일 구조 왜이렇게 잘 짰어,, 난 암 생각이 없었구나,, 진짜 반성하구가,,,

const [isModalOpen, setIsModalOpen] = useState(false);

// 난이도 버튼 렌더링
const levelButtonList = [EASY, NORMAL, HARD].map((item) => (
Copy link

Choose a reason for hiding this comment

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

아 요거 map 쓸 생각을 못했네,,, 어떻게 생각해 냈어..?

};

// 렌더링 할 랜덤 배열 만들기
let renderData = useMemo(() => {
Copy link

Choose a reason for hiding this comment

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

첨에 내가 서현이랑 비슷하게 설계하다가 실패했는데, useMemo()를 안 써서 계속 원하는 대로 안 됐던 게 그래서 였구나,,, 뭔가 반성중,,,

Copy link
Member

@ljh0608 ljh0608 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 +4 to +7
"eslint:recommended",
"plugin:react/recommended",
"plugin:react/jsx-runtime",
"plugin:react-hooks/recommended",
Copy link
Member

Choose a reason for hiding this comment

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

eslint 설정까지 해줬네 대단해!
웹 심화에서 배우고 다시 많이 까먹었는데 이렇게 eslint랑 react에서 추천하는 plugin이라도 넣어주면서 다음부턴 초기설정부터 잘 하고 시작해야겠다는 생각이 드네..

Comment on lines +3 to +8
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
Copy link
Member

Choose a reason for hiding this comment

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

log파일이 어떤거고 왜 ignore에 넣는건지 궁금한데 설명해줄 수 있어?

Copy link
Member Author

Choose a reason for hiding this comment

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

기록이 자동으로 남는 파일로 알고 있어!
예를 들어 yarn-error.log같은 경우에는 yarn 하면서 발생한 에러를 자동으로 기록해주더라!

Copy link
Member Author

Choose a reason for hiding this comment

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

아무래도 그 기록이 코드파일에는 필요하지 않으니까 깃에 올릴 필요가 없는게 아닐까 싶네?!

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="ko">
Copy link
Member

Choose a reason for hiding this comment

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

우리 셋은 이건 꼭 지켜야지.. 맞지맞지

Comment on lines +16 to +17
"styled-components": "^5.3.10",
"styled-reset": "^4.4.6"
Copy link
Member

Choose a reason for hiding this comment

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

styled-reset 적용했구나! 항상 코드리뷰 하면서 배워갑니다~

setIsRotate("rotate");
setTimeout(() => {
// 카드 오픈 리스트에서 최근에 넣었던 두 개 카드 삭제
const newList = openCardList.slice(0, -1);
Copy link
Member

Choose a reason for hiding this comment

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

오호 이건
openCardList.slice(0, -1 + openCardList.length)
처럼 동작하는 거구나 문법도 배워가!!

근데 배열을 slice로 잘라서 newList에 대입하고 그걸 다시 setOpenList에 주는 이유가 궁금해!

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 +48 to +50

testCardList.push(classList[2]); // 테스트 배열에 cardId 추가
setTimeout(() => {
Copy link
Member

Choose a reason for hiding this comment

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

classList[2]가 무엇을 의미하는거야??

setIsModalOpen(true);
}

setIsBlink(true);
Copy link
Member

Choose a reason for hiding this comment

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

score가 바뀐다는건 곧 올라간다는 의미니까 setIsBlick를 이렇게 score가 의존성 배열에 들어있는 effect에 넣어줘도 되는거구나? 배워가요~

Comment on lines +23 to +35
<St.HeaderContainer>
<h1>🔮 옴팡이를 맞춰주세요 🔮</h1>
<St.Score className={isBlink && "blink"}>
{score} / {cardCount}
</St.Score>
</St.HeaderContainer>
);
};

export default Header;

const St = {
HeaderContainer: styled.header`
Copy link
Member

Choose a reason for hiding this comment

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

St.Component 의 방법으로 styled component를 사용하는 이유가 궁금해요 금장디장님

Copy link

Choose a reason for hiding this comment

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

팟장님도 이렇게 하시던데! 이게 더 가독성있고 편한것 같기두,,,? 취향차이인가용?

Copy link
Member Author

Choose a reason for hiding this comment

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

저 같은 경우에는 한눈에 스타일 컴포넌트인지, 일반 컴포넌트인지 쉽게 구분하기 위해서 사용합니당!!!
요렇게 하면 컴포넌트명만 보고도 바로 알 수 있거든요 ㅎㅎㅎ

Comment on lines +4 to +6
const handleOnClick = () => {
setIsModalOpen(false);
};
Copy link
Member

Choose a reason for hiding this comment

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

온클릭에 reset매개변수도 넣어주어서 reset시켜주면 게임 이용자가 더 간편하지 않을까 생각을 해봤어!

Comment on lines +11 to +13
html {
font-size: 62.5%;
}
Copy link
Member

Choose a reason for hiding this comment

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

나도 담부터 이렇게 설정해서 1rem 10px로 편하게 적용해야겠다ㅎㅎ;

Copy link

@hae2ni hae2ni left a comment

Choose a reason for hiding this comment

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

❤️ 역시 우리 잔디쟝 짱짱 너무 수고해써요! ❤️

const [isClickAbled, setIsClickAbled] = useState(true); // 카드 클릭 가능 여부
const [isRotate, setIsRotate] = useState("");

// level이 바뀌면, reset 버튼 눌리면 모두 초기화
Copy link

Choose a reason for hiding this comment

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

하나하나 주석 달아줘서 너무 고마운,,,

setIsRotate("rotate");
setTimeout(() => {
// 카드 오픈 리스트에서 최근에 넣었던 두 개 카드 삭제
const newList = openCardList.slice(0, -1);
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 +23 to +35
<St.HeaderContainer>
<h1>🔮 옴팡이를 맞춰주세요 🔮</h1>
<St.Score className={isBlink && "blink"}>
{score} / {cardCount}
</St.Score>
</St.HeaderContainer>
);
};

export default Header;

const St = {
HeaderContainer: styled.header`
Copy link

Choose a reason for hiding this comment

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

팟장님도 이렇게 하시던데! 이게 더 가독성있고 편한것 같기두,,,? 취향차이인가용?

const Router = () => {
return (
<BrowserRouter>
<Routes>
Copy link

Choose a reason for hiding this comment

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

라우터 지금부터 쓰는 똑또기가 여기이따!!

@seobbang seobbang merged commit 8d7da6d into main May 30, 2023
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