과거 프로그래밍 자료들/Javascript&typescript

Let's Get IT 자바스크립트 프로그래밍 - 카드 짝 맞추기

평부 2022. 7. 20. 22:53

* 출처 : https://thebook.io/080270/

 

더북(TheBook): Let's Get IT 자바스크립트 프로그래밍

 

thebook.io

 

* 두 장의 색이 다른 카드 맞추기 

 

- 색상이 같은 것 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로 만들어 세 번쨰 카드부터는 클릭해도 아무런 일이 일어나지 않게 함

 

- 호출 스택 : 동기 코드 담당

- 이벤트 루프 : 비동기 코드 담당(태스크 큐에서 호출 스택으로 함수를 이동시키는 존재)

- 백그라운드 : 타이머를 처리하고 이벤트 리스너를 저장하는 공간

- 태스크 큐 : 실행돼야 할 콜백 함수들이 줄을 서서 대기하는 공간

 

* 이미지 출처 : https://velog.io/@primadonna/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%98%B8%EC%B6%9C-%EC%8A%A4%ED%83%9D-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A3%A8%ED%94%84-%EC%A0%95%EB%A6%AC

 

자바스크립트 호출 스택 / 이벤트 루프 정리

\*\*호출 스택(함수의 호출, 자료구조의 스택)Anonymous은 가상의 전역 컨텍스트(항상 있다고 생각하는게 좋음)함수 호출 순서대로 쌓이고, 역순으로 실행됨함수 실행이 완료되면 스택에서 빠짐LIFO

velog.io

 

 

카드 짝 맞추기

 

더보기
<!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>