SOURCE

console 命令行工具 X clear

                    
>
console
const selectors = {
    boardContainer: document.querySelector(".board-container"),
    board: document.querySelector(".board"),
    moves: document.querySelector(".moves"),
    timer: document.querySelector(".timer"),
    start: document.querySelector("button"),
    win: document.querySelector(".win")
};

const state = {
    gameStarted: false,
    flippedCards: 0,
    totalFlips: 0,
    totalTime: 0,
    loop: null
};

const shuffle = array => {
    const clonedArray = [...array];

    for (let i = clonedArray.length - 1; i > 0; i--) {
        const randomIndex = Math.floor(Math.random() * (i + 1));
        const original = clonedArray[i];

        clonedArray[i] = clonedArray[randomIndex];
        clonedArray[randomIndex] = original;
    }
    return clonedArray;
}

const pickRandom = (array, items) => {
    const clonedArray = [...array];
    const randomPicks = [];

    for (let i = 0; i < items; i++) {
        const randomIndex = Math.floor(Math.random() * clonedArray.length);

        randomPicks.push(clonedArray[randomIndex]);
        clonedArray.splice(randomIndex, 1);
    }
    return randomPicks;
}

const generateGame = () => {
    const dimensions = selectors.board.getAttribute("data-dimension");

    if (dimensions % 2 !== 0) {
        throw new Error("The dimension of the board must be an even number.")
    }

    const emojis = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']
    const picks = pickRandom(emojis, (dimensions * dimensions) / 2);
    const items = shuffle([...picks, ...picks]);
    const cards = ` 
        <div class="board" style="grid-template-colums: repeat(${dimensions}, auto)">
            ${items.map(item => `
                <div class="card">
                    <div class="card-front"></div>
                    <div class="card-back">${item}</div>
                </div>
            `).join("")}
        </div>
    `

    const parser = new DOMParser().parseFromString(cards, "text/html");
    selectors.board.replaceWith(parser.querySelector(".board"))
};

const startGame = () => {
    state.gameStarted = true;
    selectors.start.classList.add("disabled");

    state.loop = setInterval(() => {
        state.totalTime++;

        selectors.moves.innerText = `${state.totalFlips} moves`;
        selectors.timer.innerText = `Time: ${state.totalTime} sec`
    }, 1000)
}

const flipBackCards = () => {
    document.querySelectorAll(".card:not(.matched)").forEach(card => {
        card.classList.remove("flipped");
    });

    state.flippedCards = 0;
}

const flipCard = card => {
    state.flippedCards++;
    state.totalFlips++;

    if (!state.gameStarted) {
        startGame()
    }
    if (state.flippedCards <= 2) {
        card.classList.add("flipped")
    }
    if (state.flippedCards === 2) {
        const flippedCards = document.querySelectorAll(".flipped:not(.matched)");
        if (flippedCards[0].innerText === flippedCards[1].innerText) {
            flippedCards[0].classList.add("matched");
            flippedCards[1].classList.add("matched");
        }

        setTimeout(() => {
            flipBackCards()
        }, 1000)
    }
    if (!document.querySelectorAll(".card:not(.flipped)").length) {
        setTimeout(() => {
            selectors.boardContainer.classList.add("flipped")
            selectors.win.innerHTML = `
                    <span class="win-text">
                        You won!<br/>
                        with <span class="highlight">${state.totalFlips}</span>
                        moves<br/>
                        under <span class="highlight">${state.totalTime}</span>
                        seconds
                    </span>
                `

            clearInterval(state.loop)
        }, 1000)
    }
}

const attachEventListeners = () => {
    document.addEventListener("click", event => {
        const eventTarget = event.target;
        const eventParent = eventTarget.parentElement;

        if (eventTarget.className.includes("card") && !eventParent.className.includes("flipped")) {
            flipCard(eventParent)
        }
        else if (eventTarget.nodeName === "BUTTON" && !eventTarget.className.includes("disabled")) {
            startGame();
        }
    })
}

generateGame();
attachEventListeners();
<!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, inital-scale=1.0" />
    <!-- <link rel="stylesheet" href="18.css" />
    <script src="18.js" defer></script> -->
    <title>CSS</title>
  </head>
  <body>
    <div class="game">
      <div class="controls">
        <button>Start</button>
        <div class="stats">
          <div class="moves">0 moves</div>
          <div class="timer">Time: 0 sec</div>
        </div>
      </div>
      <div class="board-container">
        <div class="board" data-dimension="4"></div>
        <div class="win">You won!</div>
      </div>
    </div>
  </body>
</html>
html {
  width: 100%;
  height: 100%;
  background: linear-gradient(
    325deg,
    #03001e 0%,
    #7303c0 30%,
    #ec38bc 70%,
    #fdeff9 100%
  );
  font-family: Arial, Helvetica, sans-serif;
  overflow: hidden;
}

.game {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

.controls {
  display: flex;
  gap: 20px;
  margin-bottom: 20px;
}

button {
  background: #282a3a;
  color: #fff;
  border-radius: 5px;
  padding: 10px 20px;
  border: 0;
  cursor: pointer;
  /* font-family: Arial, Helvetica, sans-serif; */
  font-size: 18pt;
  font-weight: bold;
}

.disabled {
  color: #757575;
}

.stats {
  color: #fff;
  font-size: 14pt;
  font-weight: bold;
}

.board-container {
  position: relative;
}

.board,
.win {
  border-radius: 5px;
  box-shadow: 0 25px 50px rgb(33 33 33 / 25%);
  background: linear-gradient(
    135deg,
    #03001e 0%,
    #7303c0 0%,
    #ec38bc 50%,
    #fdeff9 100%
  );
  transition: transform 0.6s cubic-bezier(0.4, 0, 0.2, 1);
  backface-visibility: hidden;
}

.board {
  padding: 20px;
  display: grid;
  grid-template-columns: repeat(4, auto);
  grid-gap: 20px;
}

.board-container.flipped .board {
  transform: rotateY(180deg) rotateZ(50deg);
}

.board-container.flipped .win {
  transform: rotateY(0) rotateZ(0);
}

.card {
  position: relative;
  width: 100px;
  height: 100px;
  cursor: pointer;
}

.card-front,
.card-back {
  position: absolute;
  border-radius: 5px;
  width: 100%;
  height: 100%;
  background: #282a3a;
  transition: transform 0.6s cubic-bezier(0.4, 0, 0.2, 1);
  backface-visibility: hidden;
}

.card-back {
  transform: rotateY(180deg) rotateZ(50deg);
  font-size: 28pt;
  user-select: none;
  text-align: center;
  line-height: 100px;
  background: #fdf8e6;
}

.card.flipped .card-front {
  transform: rotateY(180deg) rotateZ(50deg);
}

.card.flipped .card-back {
  transform: rotateY(0) rotateZ(0);
}

.win {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  text-align: center;
  background: #fdf8e6;
  transform: rotateY(180deg) rotateZ(50deg);
}

.win-text {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-size: 21pt;
  color: #282a3a;
}

.highlight {
  color: #7303c0;
}