같은 그림의 카드를 짝지어 찾는 게임
24개의 카드를 사용할 것이니 총 12개의 이미지가 필요합니다. 이미지는 무료 아이콘 사이트인 flaticon에서 Cute Fast Food Mascot을 사용했습니다.
randomCard 배열에 이미지를 담고, 이 배열은 항상 게임 시작전에 랜덤으로 섞일 수 있도록 해주어야 합니다.
const card_img = [
//카드에 넣을 이미지 배열
...
];
randomCard = card_img;
randomCard.push(...randomCard); //2쌍씩
function shuffleCard(array) {
array.sort(() => Math.random() - 0.5);
}
Math.random() 메서드는 0 ~ 1 사이의 난수를 반환합니다. 반환되는 난수의 중간 값인 0.5를 뺀 값은 50% 확률로 양수가 되거나 음수가 됩니다.
무작위로 양수와 음수를 반환하는 식을 sort 정렬에 대입해 배열의 요소를 무작위로 바꿀 수 있는 방법을 사용했습니다.
게임은 4X6 게임판에서 진행됩니다. 24장의 카드가 필요하고 한 카드div는 카드앞면, 카드뒷면 div를 가지고 있도록 해줍니다.
<div class="card" id=${index} data-img=${item}>
<div class="card__front" id=${item}.jpg></div>
<div class="card__back"></div>
</div>
이 구조의 카드를 innerHTML을 통해 그려줄게요. 랜덤 이미지를 담고 있는 randomCard 배열에 map함수를 적용해 그려지는 카드의 인덱스에 맞는 이미지까지 바로 넣어주었습니다.
randomCard.map((item, index) => {
cards.innerHTML =
cards.innerHTML +
`
<div class="card" id=${index} data-img=${item}>
<div class="card__front" id=${item}.jpg></div>
<div class="card__back"></div>
</div>
`;
});
randomCard 배열에 담긴 이미지 순서대로 카드가 생성됩니다. randomCard 안의 이미지는 매 게임마다 랜덤으로 섞이기 때문에 항상 다른 순서로 카드르를 그릴 수 있습니다.
게임 시작 전 한 카드씩 앞면을 보여주고 마지막 카드까지 공개된 이후에는 전체 카드를 다시 뒤집고 게임을 시작합니다.
한 카드의 앞면이 보일 땐 뒷면이 보이지 않도록 cardFront와 cardBack에 backface-visibility: hidden을 주어 뒷면은 보이지 않도록 해줍니다.
backface-visibility: hidden;
cardFront는 처음 생성 시 rotateY(180deg)를 적용해 뒷면을 보인 채 그려집니다. 이를 뒤집어 뒷면을 숨기고 앞면을 보여주기 위해 cardFront는 rotateY(360deg), cardBack에는 rotateY(180deg)를 적용합니다.
//처음 실행시에 카드를 전체적으로 보여주는 함수
function firstShowCard() {
let cnt = 0;
const showInterval = setInterval(() => {
cardBack[cnt].style.transform = 'rotateY(180deg)';
cardFront[cnt].style.transform = 'rotateY(360deg)';
++cnt;
}, 350);
}
마지막 카드까지 공개된 이후에는 closeCard() 메서드를 불러 다시 카드 뒷면을 보이도록 해줍니다.
if (cnt === GAME_SIZE - 1) {
clearInterval(showInterval);
setTimeout(() => {
closeCard();
}, 3000);
}
이미지 공개와는 반대로 cardBack은 rotateY(360deg)로 다시 보여지고, cardFront는 rotateY(180deg)로 다시 숨겨집니다.
//카드를 다시 닫는 함수
function closeCard() {
for (let i = 0; i < GAME_SIZE; i++) {
cardBack[i].style.transform = 'rotateY(360deg)';
cardFront[i].style.transform = 'rotateY(180deg)';
}
}
첫번째 클릭된 카드와 두번째로 클릭한 카드를 위한 변수를 선언해줍니다.
let firstCard = 0;
let secondCard = 0;
클릭 이벤트를 사용해 클릭된 카드를 타겟카드에 넣어주고, 해당 카드 click이라는 클래스를 삽입해 클릭된 카드임을 알 수 있도록 해줍니다.
cards.addEventListener('click', (e) => {
let targetCard = e.target;
//카드를 선택했을때만 작동되도록
if (targetCard.parentNode.className === 'card' && secondCard === 0) {
targetCard.parentNode.classList.add('click');
let targetCardId = targetCard.parentNode.id;
openCard(targetCardId);
}
});
클릭된 카드의 id를 targetCardId로 넘겨주고, openCard() 메서드를 호출해 targetCardId를 가지는 카드를 오픈합니다.
두번째 카드가 공개되면 matchCard()메서드를 호출해 두 카드를 비교합니다. 두 카드의 이미지가 같은 경우 카드가 오픈된 상태로 두고, 이미지가 다른 경우에는 두 카드를 다시 뒤집어야 합니다.
let matchArr = [];
맞춘 카드들을 담아둘 matchArr 배열을 선언해줍니다.
function matchCard(first, second) {
if (first.dataset.img === second.dataset.img) {
matchArr.push(first, second);
} else {
// 카드 뒤집기
}
firstCard = 0;
secondCard = 0;
}
사용자는 원하는 속성을 data-속성
의 형식으로 특정 요소에 지정해 사용할 수 있습니다. data-속성
으로 시작되는 속성들은 dataset
이라는 속성을 통해 접근합니다.
first.dataset.img === second.dataset.img
data-img로 img속성에 이미지 이름을 넣어주고, 이를 통해 카드 이미지를 비교해주었습니다.
setTimeout(() => {
first.animate(keyframes, options);
second.animate(keyframes, options);
}, 1000);
let keyframes = [
{ transform: 'rotate(3deg)' },
{ transform: 'rotate(-3deg)' },
{ transform: 'rotate(5deg)' },
{ transform: 'rotate(-5deg)' },
];
let options = {
duration: 300,
easing: 'ease-in',
};
rotate를 이용해 카드가 양 옆으로 진동하는듯한 느낌을 주었습니다.
맞춘 이미지들을 담아두는 matchArr 배열에 모든 카드가 들어온 경우 게임을 종료합니다.
if (matchArr.length === 24) {
console.log('스테이지 완료');
gameClearModal();
}