24/1/8 메모리카드 게임

2024. 1. 9. 08:32카테고리 없음

 

<MemoryCardPage.jsx>

import React from "react";
import MemoryCard from "../components/memocard/MemoryCard";

export default function MemoryCardPage() {
  return (
    <div>
      <MemoryCard />
    </div>
  );
}

 

<MemoryCard.jsx>

import React, { useEffect, useState } from "react";  //메모리카드는 한쌍의 두장으로 총 16개의 카드가 나온다.
import phi from "../../assets/images/phi.jpg";
import Mu from "../../assets/images/mu.jpg";
import ggo from "../../assets/images/ggo.jpg";
import pha from "../../assets/images/pha.jpg";
import isang from "../../assets/images/isang.jpg";
import ghi from "../../assets/images/ghi.jpg";
import ing from "../../assets/images/ing.jpg";
import eve from "../../assets/images/eve.jpg";

import styled from "styled-components";
import SingleCard from "./SingleCard";

export default function MemoryCard() {
  const cardImages = [
    { src: ggo, matched: false },   //카드 이미지를 객체의 속성으로 갖는 배열
    { src: phi, matched: false },
    { src: Mu, matched: false },
    { src: pha, matched: false },
    { src: isang, matched: false },
    { src: ghi, matched: false },
    { src: ing, matched: false },
    { src: eve, matched: false },
  ];

  const [cards, setCards] = useState([]);         
  const [turns, setTurns] = useState(0);
  const [choiceOne, setChoiceOne] = useState(null);     //카드의 변경을 기록에 따라 리랜더링 시킬
  const [choiceTwo, setChoiceTwo] = useState(null);      //유즈 스테이트 들
  const [disabled, setDisabled] = useState(false);
  //카드섞기
  const shuffleCards = () => {
    const shuffledCards = [...cardImages, ...cardImages]   //카드는 같은 카드가 두장이므로 두번사용
      .sort(() => Math.random() - 0.5)    //랜덤을 이용해서 카드를 무작위 배열로 만든다.
      .map((card) => ({ ...card, id: Math.random() }));   //각각의 카드의 랜덤한 아이디 지정
    setChoiceOne(null);
    setChoiceTwo(null);          //choice 상태를 일단 널로 둔다.
    setCards(shuffledCards);     //현재 카드의 배열의 상태 저장
    setTurns(0);             //뒤집힌 횟수 초기화
  };
  //선택 조절
  const handleChoice = (card) => {
    choiceOne ? setChoiceTwo(card) : setChoiceOne(card);  // 현재 첫선택이 되있으면 두번쨰 선택을
  };                                                                                //하고 안되어 있으면 첫 선택을 한다. card를 매개변수로 
  //카드 두개 비교                                                            //넘겨줌
  useEffect(() => {
    if (choiceOne && choiceTwo) {        //첫선택과 두번째 선택이 이루워 졌으면 상태를 disable로 만드는 로직
      setDisabled(true);
      if (choiceOne.src === choiceTwo.src) { //첫선택카드와 두번째 선택카드의 이미짖가 같다면
        setCards((prevCards) => {
          return prevCards.map((card) => {
            if (card.src === choiceOne.src) {   //카드들의 배열에서 이미지를 정확히 일치시킨 카드 두개들을
              return { ...card, matched: true };     //찾아 내서 걔네들은 matched를 트루로 바꿔놓는다.
            } else {
              return card;         //나머지 카드들은 고대로
            }
          });
        });
        resetTurn();      //요건 초기화 함수 인데 밑에 있다. // 첫번째 선택과 두번쨰 선택이 같지 않으면 초기화
      } else {
        setTimeout(() => resetTurn(), 1000);  //첫선택과 두번째 선택이 이루워지고 1초가 지나면 다시 초기화
      }
    }
  }, [choiceOne, choiceTwo]);               //마운트시 첫선택시 두번째 선택시 유즈이펙트 발동
  const resetTurn = () => {
    setChoiceOne(null);        //첫카드와 두번째 카드를 다시 빈값으로 만든다.
    setChoiceTwo(null);
    setTurns((prev) => prev + 1);    //횟수 1증가
    setDisabled(false);         //disable상태를 false로 만든다.
  };

  useEffect(() => {
    shuffleCards();
  }, []);   //마운트시 바로 카드를 섞도록 한다.
  return (
    <Content>
      <Text>
        <p>뒤집은 횟수 : {turns}</p>
        <button onClick={shuffleCards}>RESET</button>
      </Text>

      <Grid>
        {cards.map((card) => {   //카드들의 배열의 카드들을 map 매서드로 뿌린다.
          return (
            <SingleCard                      //컴포넌트이다. 아래에 있다.  //각 하나의 카드를 컴포넌트로 만들었다.
              key={card.id}                  //cards 배열의 카드들을 전달 받았으니까 각 singlecard들을 다른 카드를 갖는다.
              card={card}
              flipped={card === choiceOne || card === choiceTwo || card.matched}  //플립드 속성을 준다.
              handleChoice={handleChoice}                                //조건에 따라 트루 폴스를 줘서 그 상태에 따라
              disabled={disabled}                                                            //css를 변경
            />
          );
        })}
      </Grid>
    </Content>
  );
}
const Content = styled.section`
  display: flex;
  justify-content: center;
  flex-direction: column;
  align-items: center;
  margin-bottom: 30px;
`;

const Grid = styled.div`
  margin-top: 40px;
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1fr;               //카드들이 4행렬로 보이게 한다.
  grid-gap: 20px;
`;

const Text = styled.div`
  display: flex;
  justify-content: center;
  flex-direction: column;
  align-items: center;
  margin-top: 30px;
  font-size: 20px;

  button {
    cursor: pointer;
    background-color: var(--yellow);
    margin-top: 15px;
    border-radius: 5px;
  }
`;

 

<SingleCard.jsx>

import React from "react";
import ball from "../../assets/images/ball.png";
import styled, { css } from "styled-components";
export default function SingleCard({ card, handleChoice, flipped, disabled }) {  //프롭스로 4개를 받는다.
  const handleClick = () => {                    //16장의 카드들의 card와 filipped는 다른값을 갖고
    if (!disabled) {                                            //핸들 초이스 함수와 disalbled 여부는 동일하게 적용받는다.
      handleChoice(card);
    }                                        //카드들이 disabled하면 카드 선택이 안된다.
  };
  return (
    <Card className="card">
      <ImgWrpper flipped={flipped}>    //flipped 의 블리언 속성을 전달 받는다.
        <img className="front" src={card.src} alt="card front" /> //이미지는 두개를 갖는다. 하나는 앞면
        <img                                                                                //두번째는 뒷면
          className="back"
          src={ball}
          onClick={handleClick}           //뒷면은 다 동일하므로 동일한 src를 갖는다.
          alt="card back"                        //뒷면을 누를시 앞면이 보이도록 온클릭은 뒷면에 준다.
        />
      </ImgWrpper>{" "}
    </Card>
  );
}

const Card = styled.div`
  position: relative;            

  & img {
    width: 150px;
    height: 150px;
    display: block;
    border: 3px solid black;
    border-radius: 6px;
  }
`;

const ImgWrpper = styled.div`
  ${(props) =>
    props.flipped                               //전달 반은 프롭스중 flipped 가 트루이냐 폴스 이냐에 따라
      ? css`
          .front {
            transform: rotateY(0deg);               //front인지 back인지가 나오고 그에따라 보이는게 다르다.
            transition-delay: 0.2s;                   //트랜스폼은 변경시켜주는애고 트랜지션은 변화과정설정이다. 
          }
          .back {
            transform: rotateY(90deg);
            transition-delay: 0s;
            display: none;
          }
        `
      : css`
          .front {
            transform: rotateY(90deg);
            transition: all ease-in 0.2s;
            position: absolute;                     //absolute설정으로 이미지를 절대 위치에 놓는다.
          }
          .back {
            transition: all ease-in 0.2s;
            transition-delay: 0.2s;
          }
        `}
`;

//게임 구현 재밌었당 ㅋ