Let's Get IT 자바스크립트 프로그래밍 - 카드 짝 맞추기
* 출처 : https://thebook.io/080270/
* 두 장의 색이 다른 카드 맞추기
- 색상이 같은 것 2개 이상 = 색상.concat(색상.splice(index(Math.random된 것), 1);
- 클릭한 경우 clicked 배열에 넣기
- 카드를 뒤집을 때 초반 카드 공개 시 각각의 카드에 시간차 둘 것, 카드 감출 때도 마찬가지
- onClickCard에서 this는 클릭한 카드가 됨(onClickCard는 startGame 내 card의 이벤트 리스너의 콜백함수)
* 카드 뒤집기 중 버그 존재
1) 처음 카드 보여줬다가 다시 뒤집는 동안 카드를 클릭할 수 없어야 하는데, 카드를 클릭하면 카드 뒤집힘
2) 이미 짝이 맞춰진 카드를 클릭해도 카드가 다시 뒤집힘
3) 한 카드를 두 번 연이어 클릭 시 더 이상 해당 카드 클릭되지 않음
//1), 2), 3) 해결 코드
//해결책
let completed = [];
let clickable = false;
function onClickCard() {
if(!clickable }} completed.includes(this) || clicked[0] == this) {
return
}
function startGame() {
setTimeout(() => {
...
});
clickable = true;
}, 5000);
}
function resetGame() {
...
clickable = false;
startGame();
}
startGame();
4) 서로 다른 네 가지 색의 카드 연달아 클릭 시 마지막 두 카드의 앞면이 보인 채 남아있음
- 호출 스택과 이벤트 루프를 이용할 것
- 카드를 뒷면으로 뒤집고 clicked를 []로 초기화 하기 전 총 4개의 카드 중 세 번째, 네 번쨰 카드의 클릭 이벤트 콜백 함수가 실행되는 것이 문제
-> 카드가 2장 될 떄 clickable를 false로 만들어 세 번쨰 카드부터는 클릭해도 아무런 일이 일어나지 않게 함
- 호출 스택 : 동기 코드 담당
- 이벤트 루프 : 비동기 코드 담당(태스크 큐에서 호출 스택으로 함수를 이동시키는 존재)
- 백그라운드 : 타이머를 처리하고 이벤트 리스너를 저장하는 공간
- 태스크 큐 : 실행돼야 할 콜백 함수들이 줄을 서서 대기하는 공간
카드 짝 맞추기
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>짝 맞추기</title>
</head>
<style>
.card {
display: inline-block;
margin-right: 20px;
margin-bottom: 20px;
width: 70px;
height: 100px;
perspective: 140px;
}
.card-inner {
position: relative;
width: 100%;
height: 100%;
text-align: center;
transition: transform 0.8s;
transform-style: preserve-3d;
}
.card.flipped .card-inner {
transform: rotateY(180deg);
}
.card-front {
background: navy;
}
.card-front,
.card-back {
position: absolute;
width: 100%;
height: 100%;
border: 1px solid black;
backface-visibility: hidden;
}
.card-back {
transform: rotateY(180deg);
}
</style>
<body>
<div id="wrapper"></div>
<script>
const $wrapper = document.querySelector('#wrapper');
const total = 12;
const colors = ['red', 'orange', 'yellow', 'green', 'white', 'pink'];
let colorCopy = colors.concat(colors); //총 12개
let shuffled = [];
let clicked = [];
let completed = []; //완료 배열 변수
let clickable = false;
function shuffle() {
for (let i = 0; colorCopy.length > 0; i += 1) {
const randomIndex = Math.floor(Math.random() * colorCopy.length);
shuffled = shuffled.concat(colorCopy.splice(randomIndex, 1)); //배열에 뽑은 것 넣기
}
}
function createCard(i) {
const card = document.createElement('div');
card.className = 'card'; //.card 태그 생성
const cardInner = document.createElement('div');
cardInner.className = 'card-inner'; //.card-inner 태그 생성
const cardFront = document.createElement('div');
cardFront.className = 'card-front'; //.card-front 태그 생성
const cardBack = document.createElement('div');
cardBack.className = 'card-back'; //.card-back 태그 생성
cardBack.style.backgroundColor = shuffled[i];
cardInner.append(cardFront);
cardInner.append(cardBack);
card.append(cardInner);
return card;
};
function resetGame() {
$wrapper.innerHTML = '';
colorCopy = colors.concat(colors);
shuffled = [];
completed = [];
clickable = false;
startGame();
}
function onClickCard() {
if (!clickable || completed.includes(this) || clicked[0] === this) {
return;
}
this.classList.toggle('flipped');
clicked.push(this);
if (clicked.length !== 2) {
return;
}
const firstBackColor = clicked[0].querySelector('.card-back').style.backgroundColor;
const secondBackColor = clicked[1].querySelector('.card-back').style.backgroundColor;
if (firstBackColor === secondBackColor) { //두 카드가 같은 카드면
completed.push(clicked[0]);
completed.push(clicked[1]);
clicked = [];
if (completed.length !== total) {
return;
}
setTimeout(() => {
alert('축하합니다! 모두 맞추셨네요!');
console.log('clicked', clicked);
console.log('completed', completed);
resetGame();
}, 1000);
return;
}
//두 카드가 다른 카드면
clickable = false; //세 번째 카드부터는 클릭해도 실해되지 않게 함
setTimeout(() => {
clicked[0].classList.remove('flipped');
clicked[1].classList.remove('flipped');
clicked = [];
clickable = true;
}, 500);
}
function startGame() {
shuffle();
for (let i = 0; i < total; i++) {
const card = createCard(i);
card.addEventListener('click', onClickCard);
$wrapper.appendChild(card);
}
//초반 카드 공개
document.querySelectorAll('.card').forEach((card, index) => {
setTimeout(() => {
card.classList.add('flipped');
}, 1000 + 100 * index); //1초 -> 1.1초 -> 1.2초 ... 2.1초가 마지막
});
//카드 감추기
setTimeout(() => {
document.querySelectorAll('.card').forEach((card) => {
card.classList.remove('flipped');
});
clickable = true;
}, 5000);
}
startGame();
</script>
</body>
</body>
</html>
카드 짝 맞추기 - 몇 초 만에 끝났는지 시간 확인
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>짝 맞추기</title>
</head>
<style>
.card {
display: inline-block;
margin-right: 20px;
margin-bottom: 20px;
width: 70px;
height: 100px;
perspective: 140px;
}
.card-inner {
position: relative;
width: 100%;
height: 100%;
text-align: center;
transition: transform 0.8s;
transform-style: preserve-3d;
}
.card.flipped .card-inner {
transform: rotateY(180deg);
}
.card-front {
background: navy;
}
.card-front,
.card-back {
position: absolute;
width: 100%;
height: 100%;
border: 1px solid black;
backface-visibility: hidden;
}
.card-back {
transform: rotateY(180deg);
}
</style>
<body>
<div id="wrapper"></div>
<script>
const $wrapper = document.querySelector('#wrapper');
const total = parseInt(prompt("카드 개수가 최대 몇 개 필요하신가요?(최대 20개)"));
const colors = ['red', 'orange', 'yellow', 'green', 'white', 'pink', 'cyan', 'violet', 'gray', 'black'];
let colorSlice = colors.slice(0, total / 2);
let colorCopy = colorSlice.concat(colorSlice); //총 12개
let shuffled = [];
let clicked = [];
let completed = []; //완료 배열 변수
let clickable = false;
let startTime; //시작시간
function shuffle() {
for (let i = 0; colorCopy.length > 0; i += 1) {
const randomIndex = Math.floor(Math.random() * colorCopy.length);
shuffled = shuffled.concat(colorCopy.splice(randomIndex, 1)); //배열에 뽑은 것 넣기
}
}
function createCard(i) {
const card = document.createElement('div');
card.className = 'card'; //.card 태그 생성
const cardInner = document.createElement('div');
cardInner.className = 'card-inner'; //.card-inner 태그 생성
const cardFront = document.createElement('div');
cardFront.className = 'card-front'; //.card-front 태그 생성
const cardBack = document.createElement('div');
cardBack.className = 'card-back'; //.card-back 태그 생성
cardBack.style.backgroundColor = shuffled[i];
cardInner.append(cardFront);
cardInner.append(cardBack);
card.append(cardInner);
return card;
};
function resetGame() {
$wrapper.innerHTML = '';
colorCopy = colors.concat(colors);
shuffled = [];
completed = [];
clickable = false;
startGame();
}
function onClickCard() {
if (!clickable || completed.includes(this) || clicked[0] === this) {
return;
}
this.classList.toggle('flipped');
clicked.push(this);
if (clicked.length !== 2) {
return;
}
const firstBackColor = clicked[0].querySelector('.card-back').style.backgroundColor;
const secondBackColor = clicked[1].querySelector('.card-back').style.backgroundColor;
if (firstBackColor === secondBackColor) { //두 카드가 같은 카드면
completed.push(clicked[0]);
completed.push(clicked[1]);
clicked = [];
if (completed.length !== total) {
return;
}
const endTime = new Date(); //끝난 시간
setTimeout(() => {
alert(`축하합니다! 모두 맞추셨네요! 총 시간: ${(endTime - startTime) / 1000}초 소요`);
console.log('clicked', clicked);
console.log('completed', completed);
resetGame();
}, 1000);
return;
}
//두 카드가 다른 카드면
clickable = false; //세 번째 카드부터는 클릭해도 실해되지 않게 함
setTimeout(() => {
clicked[0].classList.remove('flipped');
clicked[1].classList.remove('flipped');
clicked = [];
clickable = true;
}, 500);
}
function startGame() {
shuffle();
for (let i = 0; i < total; i++) {
const card = createCard(i);
card.addEventListener('click', onClickCard);
$wrapper.appendChild(card);
}
//초반 카드 공개
document.querySelectorAll('.card').forEach((card, index) => {
setTimeout(() => {
card.classList.add('flipped');
}, 1000 + 100 * index); //1초 -> 1.1초 -> 1.2초 ... 2.1초가 마지막
});
//카드 감추기
setTimeout(() => {
document.querySelectorAll('.card').forEach((card) => {
card.classList.remove('flipped');
});
clickable = true;
startTime = new Date(); //카드를 감췄을 때부터 시작함
}, 5000);
}
startGame();
</script>
</body>
</body>
</html>