카드 짝맞추기 게임 - 이벤트루프

카드 생성하기

내가 한 방법

const colors = [
  "red",
  "orange",
  "yellow",
  "green",
  "white",
  "pink",
  "purple",
  "skyblue",
  "#D2691E",
  "#FF1493",
  "#808080",
];

const createCards = () => {
  const colorNeeded = colors.slice(0, CARDSNEEDED / 2);
  let doubleColor = [...colorNeeded, ...colorNeeded];
  for (let i = 0; i < CARDSNEEDED; i++) {
    const card = document.createElement("div");
    card.setAttribute("class", "card");
    card.innerHTML = ` <div class="card-inner">
 <div class="card-front"></div>
 <div class="card-back"></div>
 </div>`;
    const randomIndex = Math.floor(Math.random() * doubleColor.length);
    card.querySelector(".card-back").style.backgroundColor =
      doubleColor[randomIndex];
    doubleColor.splice(randomIndex, 1);
    $wrapper.append(card);
  }
};

게임 시작할 때 사용자가 어떤 난이도를 선택하느냐에 따라 카드 갯수가 달라진다
제일 어려울 때 22장이 필요하므로 색은 최대 11개가 필요하다
필요한 색을 slice해주고 spread oeprator을 사용하여 똑같은 element를 2개씩 만들어 줬다
for문을 돌면서 카드를 생성해주고 중복된 것을 뽑는 것을 방지해 주기 위해 splice로 제거해주었다

강의에서는

함수들을 더 잘게 쪼개었다

let shuffled = [];
let colorSliced = color.slice(0, CARDSNEEDED / 2);
let doubleColor = colorSliced.concat(colorSliced);

function shuffle() {
  for (let i = 0; i < colorNeeded; i++) {
    const randomIndex = Math.floor(Math.random() * doubleColor.length);
    shuffled = shuffled.concat(doubleColor.splice(randomIndex, 1));
  }
}

function createCards(index) {
  const card = document.createElement("div");
  card.setAttribute("class", "card");
  card.innerHTML = ` <div class="card-inner">
 <div class="card-front"></div>
 <div class="card-back"></div>
 </div>`;
  card.querySelector(".card-back").style.backgroundColor = shuffled[index];
  return card;
}

function startGame() {
  shuffle();
  for (let i = 0; i < shuffled.length; i++) {
    const card = createCards(shuffled[i]);
    $wrapper.append(card);
  }
}

무작위로 섞는 함수 따로
카드를 만드는 함수 따로 만들어주었다
innerHTML로 만들어card-back 을 querySelector를 사용해 가져오지 않고
card와 자식 div들을 createElment를 사용해 하나하나 만들어 주었다
그리고 최종 카드를 return 해주었다

카드 잠깐 보여 줬다 뒤집기

0.1 초 간격으로 카드를 다 보여준뒤 3초 외우는 시간 준뒤 다시 뒤집을 것

const showAnswer = () => {
  $playBtn.style.display = "none";
  
  const cards = document.querySelectorAll(".card");
  for (let i = 0; i < cards.length; i++) {
    setTimeout(() => {
      cards[i].classList.add("flipped");
    }, 1000 + 100 * i + 1);
  }

  setTimeout(() => {
    cards.forEach((card) => card.classList.remove("flipped"));
    clickable = true;
    startTime = new Date();
  }, SHOWTIME + 1000 + 100 * cards.length);
};

index를 이용해 첫번째 카드부터 1.1초, 1.2초 1.3초 후 뒤집어 지도록 했다
SHOWTIME은 3초로 전역으로 선언해 주었고 카드가 다 뒤집어 지고 난후 3초의 외우는 시간을 주도록 하였다

카드 클릭 처리하기

let clicked = []
let completed = []

const handleCardClick = (e) => {
  if (!clickable) return;
  const target = e.target;
  if (!target.classList.contains("card-front")) return;
  const previousCard = cards[cards.length - 1];
  const currentCard = target.parentNode.parentNode;
  currentCard.classList.add("flipped");
  cards.push(currentCard);
  if (!previousCard) return;

  clickable = false;
  const previousCardColor =
    previousCard.querySelector(".card-back").style.backgroundColor;
  const currentCardColor =
    currentCard.querySelector(".card-back").style.backgroundColor;

  if (previousCardColor === currentCardColor) {
    cards.forEach((card) => completed.push(card));
    clicked = [];
    if (correctCards.length === CARDSNEEDED) {
      finishGame();
      return;
    }
    clickable = true;
  } else {
    setTimeout(() => {
      cards.forEach((card) => card.classList.remove("flipped"));
      clicked = [];
      clickable = true;
    }, 1000);
  }

clickable 이란 flag 변수를 전역으로 선언해주어서 setTimeout 이 진행중일 때 클릭하지 못하도록 막아주었다
카드가 앞면일때만 선택할 수 있도록 class를 사용하여 if 문을 작성해 주었다
2번째 카드 선택이 아닐 때도 코드가 더 진행되지 않도록 return 해주었다
책에서와 달리 이벤트를 card를 담고 있는 컨테이너에 달아주어서 card를 선택하기 위해 parentNode를 2번 작성했는데
강의에서는 card를 생성할 때 이벤트리스너를 다 달아주었다

효과 발생 중 카드 클릭 막기

나는 card container에 이벤트 리스너를 달아줘서 버그를 막은 방법이 조금 다른데 책에서 발생한 버그를 살펴보았다
책에서 발생한 버그

  1. 카드를 보여 줬다가 다시 뒤집는 동안에는 카드를 클릭할 수 없어야 한다
  2. 이미 짝이 맞춰진 카드를 클릭해도 카드가 다시 뒤집힌다
  3. 한 카드를 두번 연이어 클릭하면 더이상 해당 카드가 클릭 되지 않는다
  4. 서로 다른 네 가지 색의 카드를 연달아 클릭하면 마지막 두 카드가 앞면을 보인 채 남아 있다

1, 2, 3 번 해결법

if(!clickable||completed.includes(this)||clicked[0]===this) return

  • clickable flag 변수 사용하여 setTimeout 의 콜백함수 실행될 때 clickable = true 로 변경하기
  • 클릭한 카드가 이미 성공한 카드면 바로 return 해준다
  • 클릭한 카드를 한번더 클릭했을 경우 바로 return해준다
function startGame() {
  shuffle();
  for (let i = 0; i < shuffled.length; i++) {
    const card = createCards(shuffled[i]);
    card.addEventListener("click", handleCardClick);
    $wrapper.append(card);
  }
}

function handleCardClick() {
  if (!clickable || completed.includes(this) || clicked[0] === this) return;
  this.classList.toggle("flipped");
  clicked.push(this);
  if (clicked.length !== 2) {
    return;
  }
}

addeventlister에서 화살표 함수가 아닌 function 키워드를 사용하여 함수를 선언하면 this는 이벤트리스너의 event.target인 card가 된다

4번

호출 스택 - LIFO

백그라운드

타이머 처리, 이벤트 리스너 저장하는 공간
setTimeout 같은 함수가 실행되면 백그라운드에서 시간을 재고 시간이 되면 setTimeout의 콜백 함수를 태스트 큐로 보낸다
이벤트리스너로 추가한 이벤트를 저장했다가 이벤트가 발생하면 콜백함수를 태스트 큐로 보낸다

태스트큐

실행되야 할 콜백함수들이 대기하고 있는 공간 - FIFO
태스트큐와 백그라운드는 함수를 직접 실행 하지 않고 호출스택에서만 실행된다

이벤트 루프

호출스택이 비면 테스트큐에 있는 콜백함수를 하나씩 호출 스택으로 옮겨주고 실행시켜 준다
호출 스택이 비면 옮겨주기 때문에 setTimeout에서 전달한 인자인 시간을 확실하게 보장해주지는 못한다

flag변수 사용하지 않고 서로다른 4개의 카드 클릭했을 때의 문제점

이벤트리스너의 콜백함수가 콜백함수안에 있는 타이머의 콜백함수보다 먼저 테스트큐로 가서 실행된다

서로 색이 다른 2번 5번 8번 9번 카드를 클릭했다고 해보자
2번 을 클릭했을 땐 setTimeout이 실행되지 않고 바로 호출 스택에서 나가게 되지만 5번을 클릭하게 되면 전에 클릭된 카드와 색이 다르니 타이머를 백그라운드에 생성한다 = > clicked=[2,5]이 된다
타이머가 시간이 되어 테스트큐에 옮겨가기전 8번을 클릭하면 색깔이 다르니 다시 타이머를 백그라운드에 생성한다 = > clicked=[2,5,8]이 된다
9번카드의 클릭 콜백함수까지 실행되면 clicked 배열에는 [2,5,8,9]가 들어있게 되고 5번카드에 대한 setTimeout의 콜백함수가 실행되면 0번째, 1번째 index의 카드만 뒤집도록 되있으므로 뒤집은 뒤 [] 빈배열로 초기화 하게 된다 8번 9번 카드의 settimeout 콜백함수가 실행되어도 빈배열에 대해 실행하므로 8번과 9번 카드는 뒤집히지 않은 채 남아있게 된다

좋은 웹페이지 즐겨찾기