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;
}
`}
`;
//게임 구현 재밌었당 ㅋ