<main>
      <div id="select-difficulty">
        <label for="difficulty">Difficulty</label>
        <select name="difficulty" id="difficulty" autocomplete="off">
          <option value="easy">Easy</option>
          <option value="medium" selected>Medium</option>
          <option value="hard">Hard</option>
        </select>
      </div>
      <div id="container-cards"></div>
      <button id="play-again">Play again</button>
    </main>
@import url("https://fonts.googleapis.com/css2?family=Fredoka+One&family=Signika+Negative&display=swap");
html,
body {
  height: 100vh;
}
body {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
  margin: 0;
  box-sizing: border-box;
  background-color: #61d4b3;
  font-family: "Fredoka One", cursive;
}
main {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  width: 50vw;
  height: 80vh;
  padding: 1em;
  gap: 1em;
  border-radius: 12px;
  background-color: #fdd365;
}
#select-difficulty {
  display: flex;
  flex-wrap: wrap;
  gap: 1em;
  margin-right: 70%;
}
select {
  border: 2px solid #fd2eb3;
  border-radius: 5px;
  background: transparent;
  font-family: "Signika Negative", sans-serif;
}
#container-cards {
  display: grid;
  justify-content: center;
  align-content: center;
  grid-gap: 0.5em;
  width: 100%;
  height: 100%;
}
button[id^="card"] {
  display: flex;
  justify-content: center;
  align-items: center;
  perspective: 600px;
  background: transparent;
  border: none;
  width: 100%;
  height: 100%;
  cursor: pointer;
}
button[id^="card"] .front,
button[id^="card"] .back {
  position: absolute;
  transform-style: preserve-3d;
  backface-visibility: hidden;
  width: inherit;
  height: inherit;
  border-radius: 5px;
  font-family: "Fredoka One", cursive;
  transition: all 0.4s ease-in-out;
}
button[id^="card"] .front {
  transform: rotateX(0deg) rotateY(0deg);
  background: #fb8d62;
  z-index: 3;
}
button[id^="card"].hover .front {
  transform: rotateY(180deg);
}
button[id^="card"] .back {
  display: flex;
  justify-content: center;
  align-items: center;
  transform: rotateX(0deg) rotateY(180deg);
  background: #fd2eb3;
  color: white;
  z-index: 1;
}
button[id^="card"].hover .back,
button[id^="card"].selected .back {
  transform: rotateY(0deg);
  z-index: 6;
}
button[id^="card"].found {
  visibility: hidden;
  opacity: 0;
}
button[id^="play-again"] {
  position: absolute;
  top: 50%;
  left: auto;
  z-index: 10;
  width: 10em;
  padding: 1em;
  background: #adffe8;
  border: 3px solid #fd2eb3;
  border-radius: 10px;
  font-family: "Fredoka One", cursive;
  cursor: pointer;
  visibility: hidden;
  opacity: 0;
}
button[id^="play-again"]:hover {
  border: 3px solid #ff50c2;
  background: #77ffd8;
}
button[id^="play-again"].visible {
  visibility: visible;
  opacity: 1;
}

@media (max-width: 650px) {
  main {
    width: 85vw;
    height: 50vh;
  }
}
@media (max-width: 800px) {
  main {
    width: 85vw;
    height: 60vh;
  }
}
const difficulty = {
  //difficulty : quantity of cards
  easy: 16,
  medium: 36,
  hard: 64,
};
let difficultySelected = difficulty.medium; // Default difficulty
let availableCards = [];
let previousRevealedCard = null;
const container = document.getElementById("container-cards");
const playAgainButton = document.getElementById("play-again");

document
  .getElementById("difficulty")
  .addEventListener("change", selectDifficulty);
playAgainButton.addEventListener("click", playAgain);

function selectDifficulty(e) {
  difficultySelected = difficulty[e.currentTarget.value];

  // When we select a difficulty in the middle of a game
  // we restart the game before loading the cards
  if (
    availableCards.length > 0 ||
    playAgainButton.classList.contains("visible")
  ) {
    restartGame();
  }

  loadCards();
}

function restartGame() {
  playAgainButton.classList.remove("visible");
  container.innerHTML = "";
  availableCards = [];
  previousRevealedCard = null;
}

function randomCardsValues() {
  // This is a function for get random numbers to place cards randomly

  while (availableCards.length < difficultySelected) {
    let randomNum =
      Math.floor(Math.random() * (difficultySelected / 2 - 1 + 1)) + 1;
    let index = availableCards.indexOf(randomNum);

    // If the random number is not in the list of available cards at least twice,
    // we add it to the list
    if (index === -1 || availableCards.indexOf(randomNum, index + 1) === -1) {
      availableCards.push(randomNum);
    }
  }
}

function loadCards() {
  randomCardsValues();

  // CSS grid template
  const grid = Math.sqrt(difficultySelected);
  const gridTemplate = `repeat(${grid}, ${Math.floor(100 / grid - 1)}%)`;
  container.style.gridTemplateColumns = gridTemplate;
  container.style.gridTemplateRows = gridTemplate;

  for (let i = 0; i < availableCards.length; i++) {
    container.innerHTML += `
    <button id='card-${i}'>
      <div class='front'></div>
      <div class='back'>
        ${availableCards[i]}
      </div>
    </button>
  `;
  }

  // Adding a event to each card
  cardsEvent((card) => {
    card.addEventListener("mouseover", onMouseOver);
    card.addEventListener("mouseout", onMouseOut);
    card.addEventListener("click", onClickCard);
  });
}

function changeStateCards() {
  // This function is for disable or enable cards
  cardsEvent((card) => {
    card.disabled = !card.disabled;
    if (card.disabled) {
      card.classList.remove("hover");
      card.removeEventListener("mouseover", onMouseOver);
      card.removeEventListener("mouseout", onMouseOut);
    } else {
      card.addEventListener("mouseover", onMouseOver);
      card.addEventListener("mouseout", onMouseOut);
    }
  });
}

function onClickCard(event) {
  let currRevealedCard = event.currentTarget;
  currRevealedCard.classList.add("selected");

  if (previousRevealedCard) {
    changeStateCards(); // Disabling all the cards to not allow the selection of another card

    // We check if the previous selected card is equal to the current selected card
    if (
      previousRevealedCard.innerText === currRevealedCard.innerText &&
      previousRevealedCard.id !== currRevealedCard.id
    ) {
      setTimeout(() => {
        currRevealedCard.classList.add("found");
        previousRevealedCard.classList.add("found");
        previousRevealedCard = null; // Set the previous selected card to null
        // to select another card and play
        changeStateCards();
        updateAvailableCards(currRevealedCard.innerText);
      }, 1000);
    } else {
      setTimeout(() => {
        previousRevealedCard.classList.remove("selected");
        currRevealedCard.classList.remove("selected");
        previousRevealedCard = null;
        changeStateCards();
      }, 1000);
    }
  } else {
    previousRevealedCard = currRevealedCard;
  }
}

function updateAvailableCards(card) {
  let firstCard = availableCards.indexOf(card);
  let secondCard = availableCards.indexOf(card, firstCard + 1);

  // Removing the uncovered cards from the list of available cards
  availableCards.splice(firstCard, 1);
  availableCards.splice(secondCard - 1, 1);

  // If there are not any cards, we can play again
  if (availableCards.length === 0) {
    setTimeout(() => {
      playAgainButton.classList.add("visible");
    }, 1000);
  }
}

function playAgain() {
  playAgainButton.classList.remove("visible");
  container.innerHTML = "";
  loadCards();
}

function onMouseOver(event) {
  const card = event.currentTarget;
  card.classList.add("hover");
}
function onMouseOut(event) {
  const card = event.currentTarget;
  card.classList.remove("hover");
}
function cardsEvent(fn) {
  Object.values(container.children).forEach((card) => fn(card));
}

window.addEventListener("load", () => loadCards());
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.