How Good Is Your Memory?

https://ngtrangtee.github.io/JavaScript%20DOM/jsdom-bai9-memory-game-start/index.html

HTML Code

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Memory game</title>
    <link rel="preconnect" href="https://fonts.gstatic.com">
    <link href="https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,400;0,600;0,700;0,800;1,400&display=swap"
        rel="stylesheet">
    <link
        href="https://fonts.googleapis.com/css2?family=Bungee+Inline&family=Nunito+Sans:wght@400;600;700;800;900&display=swap"
        rel="stylesheet">
    <link rel="stylesheet" href="./style.css">
</head>

<body>
    <!-- Bắt đầu game -->
    <div id="start-game">
        <h1>Memory card</h1>
        <div class="level-container">
            <p>Chọn level game</p>
            <div class="select-container">
                <img src="https://img.icons8.com/nolan/64/controller.png" />
                <select id="level-option">
                    <option value="2">4 Cards</option>
                    <option value="4">8 Cards</option>
                    <option value="6">12 Cards</option>
                </select>
            </div>
            <button id="btn-start-game">Bắt đầu</button>
        </div>
    </div>

    <!-- Chơi game -->
    <div id="game">
        <div class="control">
            <h1>Memomy game</h1>
            <div class="time-game">
                <img src="https://img.icons8.com/dotty/80/26e07f/love-time.png" />
                <p id="time">00:00s</p> <!-- Thời gian chơi game -->
            </div>
            <div class="step-game">
                <img src="https://img.icons8.com/dotty/80/26e07f/month-in-love.png" />
                <p id="step">0 bước</p> <!-- Số bước chơi -->
            </div>
        </div>
        <div class="game-container">
            <div class="memory-game">
                <div class="memory-card" data-name="luffy" onclick="flipCard(this)">
                    <img src="https://i.pinimg.com/564x/9f/2f/72/9f2f72f1c63e6c62ac0ca3781e270975.jpg" class="front-face" alt="luffy">
                    <img src="https://i.pinimg.com/originals/b9/70/33/b97033a8708d2cbaf7d1990020a89a54.jpg" class="back-face" alt="card-back">
                </div>
                <div class="memory-card" data-name="luffy" onclick="flipCard(this)">
                    <img src="https://i.pinimg.com/564x/9f/2f/72/9f2f72f1c63e6c62ac0ca3781e270975.jpg" class="front-face" alt="luffy">
                    <img src="https://i.pinimg.com/originals/b9/70/33/b97033a8708d2cbaf7d1990020a89a54.jpg" class="back-face" alt="card-back">
                </div>
                <div class="memory-card" data-name="zoro" onclick="flipCard(this)">
                    <img src="https://i.pinimg.com/236x/d3/3f/c0/d33fc0cd1bf76766555436c2307b94d7.jpg" class="front-face" alt="zoro">
                    <img src="https://i.pinimg.com/originals/b9/70/33/b97033a8708d2cbaf7d1990020a89a54.jpg" class="back-face" alt="card-back">
                </div>
                <div class="memory-card" data-name="zoro" onclick="flipCard(this)">
                    <img src="https://i.pinimg.com/236x/d3/3f/c0/d33fc0cd1bf76766555436c2307b94d7.jpg" class="front-face" alt="zoro">
                    <img src="https://i.pinimg.com/originals/b9/70/33/b97033a8708d2cbaf7d1990020a89a54.jpg" class="back-face" alt="card-back">
                </div>
            </div>
        </div>
    </div>

    <!-- Kết thúc game -->
    <div id="end-game">
        <div class="info">
            <h1>Kết thúc</h1>
            <p>Chúc mừng bạn đã hoàn thành game trong</p>
            <div class="box">
                <div class="time-box">
                    <img src="https://img.icons8.com/dotty/80/26e07f/love-time.png" />
                    <p>30s</p> <!-- Hiển thị thời gian khi end game -->
                </div>
                <div class="step-box">
                    <img src="https://img.icons8.com/dotty/80/26e07f/month-in-love.png" />
                    <p>10 bước</p> <!-- Hiển thị số bước khi end game -->
                </div>
            </div>
            <div class="btns">
                <button id="btn-play-again">Chơi lại</button>
                <button id="btn-reload">Thoát game</button>
            </div>
        </div>
    </div>

    http://./main.js
</body>

</html>

CSS Code

* {
    padding: 0;
    margin: 0;
    box-sizing: border-box;
}

body {
    height: 100vh;
    font-family: "Nunito Sans", sans-serif;
    background-image: linear-gradient(to bottom right, #67b26f, #4ca2cd);
}

/* =========== START GAME =========== */
#start-game {
    height: 100vh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;

    color: #333;
}

#start-game h1 {
    font-family: "Bungee Inline", sans-serif;
    font-size: 4rem;
    color: #fff;
    margin-bottom: 4rem;
}

.level-container {
    width: 100%;
    padding: 50px 0;

    display: flex;
    flex-direction: column;
    align-items: center;

    background-color: #fff;
}

.level-container p {
    font-size: 2rem;
    font-family: "Bungee Inline", sans-serif;
}

.select-container {
    margin: 1rem 0;
    display: flex;
    justify-content: center;
    align-items: center;
}

#level-option {
    padding: 1rem 2rem;
    border-radius: 4px;
}

#btn-start-game {
    display: inline-block;
    padding: 1rem 2rem;
    background-color: #333;
    color: #fff;
    border: none;
    cursor: pointer;
}

/* =========== PLAY GAME =========== */
#game {
    display: none;
    height: 100vh;
    width: 100%;
}

.control {
    flex : 0 0 20%;
    background-color: #fff;
    height: 100%;
}

.control h1 {
    font-family: "Bungee Inline", sans-serif;
    text-align: center;
    margin-top: 2rem;
}

.time-game, .step-game {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;

    margin-top: 2rem;
    margin-bottom: 2rem;
}

#time, #step {
    font-size: 1.5rem;
}

.game-container {
    display: flex;
    justify-content: center;
    align-items: center;

    flex: 0 0 80%;
}


.memory-game {
    display: grid;
    grid-template-columns: repeat(2, 190px); /* 2 cột, mỗi cột 190px*/
    grid-template-rows: repeat(2, 250px);
    gap: 10px;
    perspective: 1000px;
}

.memory-card {
    width: 190px;
    height: 250px;
    position: relative;
    margin: 5px;
    transform: scale(1);
    transform-style: preserve-3d;
    transition: transform 0.5s;
}

.memory-card:active {
    transform: scale(0.97);
    transition: transform 0.2s;
}

.memory-card.flip {
    transform: rotateY(180deg);
}

.front-face,
.back-face {
    position: absolute;
    max-width: 100%;
    backface-visibility: hidden;
    width: 190px;
    height: 250px;
    object-fit: cover;
}

.front-face {
    transform: rotateY(180deg);
}

/* =========== END GAME =========== */
#end-game {
    height: 100vh;
    display: none;
    justify-content: center;
    align-items: center;
}

.info {
    background-color: #fff;
    width: 100%;
    padding: 50px 0;

    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}

#btn-play-again, #btn-reload {
    display: inline-block;
    width: 130px;
    height: 50px;
    margin: 0 1rem;
    background-color: #333;
    color: #fff;
    border: none;
    cursor: pointer;
}

.info h1 {
    font-family: "Bungee Inline", sans-serif;
    font-size: 2rem;
    margin-bottom: 2rem;
}

.box {
    display: flex;
    justify-content: center;
    margin: 1.5rem 0;
}

.time-box, .step-box {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    margin: 0 1rem;
}

.time-box p, .step-box p {
    font-weight: 600;
    margin-top: 10px;
}

JS Code

// Truy cập vào các phần tử DOM
const startGameEl = document.querySelector("#start-game"); // Màn start game
const gameEl = document.querySelector("#game"); // Màn chơi game
const endGameEl = document.querySelector("#end-game"); // Màn end game

const levelOptionEl = document.querySelector("#level-option"); // Ô chọn level game
const memoryGameEl = document.querySelector(".memory-game"); // Nơi hiển thị danh sách card

const btnStartGame = document.querySelector("#btn-start-game"); // Nút bắt đầu game
const btnPlayAgain = document.querySelector("#btn-play-again"); // Nút chơi lại
const btnReload = document.querySelector("#btn-reload"); // Nút thoát game

const timeEl = document.querySelector("#time"); // Nơi hiển thị thời gian chơi game
const stepEl = document.querySelector("#step"); // Nơi hiển thị số bước chơi game

// Khai báo biến
let level;
let firstCard;
let secondCard;
let time = 0
let step = 0;
let interval;
let score = 0; //nếu score bằng giá trị của level thì chuyển qua màn endgame

let isLock = false;

let sizes = {
  2: {
    // 4 cards
    row: 2,
    col: 2,
  },
  4: {
    // 8 cards
    row: 2,
    col: 4,
  },
  6: {
    // 12 cards
    row: 3,
    col: 4,
  },
};

let listCards = [
  {
    url:
      "https://i.pinimg.com/564x/9f/2f/72/9f2f72f1c63e6c62ac0ca3781e270975.jpg",
    name: "luffy",
  },
  {
    url:
      "https://i.pinimg.com/236x/d3/3f/c0/d33fc0cd1bf76766555436c2307b94d7.jpg",
    name: "zoro",
  },
  {
    url:
      "https://i.pinimg.com/236x/c2/b4/49/c2b4490285a27881586d3e8c49c4b565.jpg",
    name: "sanji",
  },
  {
    url:
      "https://i.pinimg.com/236x/fb/a8/ce/fba8cec6aa3a5faa06b0d5f9a21401ed.jpg",
    name: "ace",
  },
  {
    url:
      "https://i.pinimg.com/236x/74/60/51/7460514fb2e69b574011f4028fc159e3.jpg",
    name: "rayleigh",
  },
  {
    url:
      "https://i.pinimg.com/236x/22/9c/d0/229cd0ef7f252de6aab514c0fbe5989b.jpg",
    name: "sabo",
  },
];

let cards;

// Đảo vị trí các phần tử trong mảng
function shuffle(arr) {
  return arr.sort(function () {
    return Math.random() - 0.5;
  });
}

function renderCards(level) {
  // Đảo vị trí mảng listCards
  listCards = shuffle(listCards);

  // Cắt số phần tử trong mảng listCards dựa vào level
  let cardsSlice = listCards.slice(0, level);

  // Nhân đôi số lượng phần tử || ví dụ :[1,2] => [1,2,1,2]
  // cards = cardsSlice.concat(cardsSlice) => cách 1
  cards = [...cardsSlice, ...cardsSlice]; // Cách 2

  // Đảo vị trí
  cards = shuffle(cards);

  // Set kích thước cho game
  let size = sizes[level];
  console.log(size);
  memoryGameEl.style.gridTemplateColumns = `repeat(${size.col}, 190px)`;
  memoryGameEl.style.gridTemplateRows = `repeat(${size.row}, 250px)`;

  // Hiển thị lên giao diện
  memoryGameEl.innerHTML = "";
  for (let i = 0; i < cards.length; i++) {
    const c = cards[i];
    memoryGameEl.innerHTML += `
            <div 
                class="memory-card" 
                data-name="${c.name}" 
                onclick="flipCard(this)" //this là
            >
                <img src="${c.url}" class="front-face" alt="${c.name}">
                <img src="https://i.pinimg.com/originals/b9/70/33/b97033a8708d2cbaf7d1990020a89a54.jpg" class="back-face" alt="card-back">
            </div>
        `;
  }
}

// Lật card
// Lật card 1, card 2
// Lưu giá trị của 2 card
// SO sánh giá trị card 1 & 2, nếu giống nhau về data-name thì giữ nguyên mở (thêm class flip)
// Nếu 2 card khác nhau thì úp xuống để thực hiện lần tiếp
function flipCard(card) {
  if (isLock) {
    return; // Không đi xuống các đoạn bên dưới nữa, kết thúc hàm
  }
  // Không cho phép ấn 2 lần vào 1 card
  if (card == firstCard) {
    return; //Kết thúc hàm
  }

  card.classList.toggle("flip");

  // Khi ấn vào first card -> Nếu chưa tồn tại firstCard, thì gán giá trị vào card vừa ấn
  if (!firstCard) {
    firstCard = card;
    return;
  }

  // Khi ấn vào second card ->
  secondCard = card;

  // Kiểm tra 2 card
  // Nếu trùng -> Không cho người dùng click vào trong khi đang kiểm tra
  // Nếu khác
  checkCard();

  // Update số bước
  updateStep();
}

// Kiểm tra xem giá trị name có trùng nhau hay không
function checkCard() {
  // Nếu trùng, không cho ấn nữa; Nếu không trùng, úp lại cards
  if (firstCard.dataset.name == secondCard.dataset.name) {
      disableCards();
      score++ //tăng score
      checkEndGame() //Xử lí end game

  } else {
      unflipCards();
  }
}

function disableCards() {
  // Xóa sự kiện ở card trùng nhau
  firstCard.removeEventListener("click", flipCard);
  secondCard.removeEventListener("click", flipCard);

  //   Thêm thuộc tính disabled cho card
  firstCard.style.pointerEvents = "none";
    secondCard.style.pointerEvents = "none";
    
  // Reset Game Board, cho second và first card về giá trị rỗng để lúc sau gán lại
  resetGame();
}

function checkEndGame() {
    if (score == level) {
        clearInterval(interval) //dừng thời gian
    //chuyển sang màn end game sau khi delay 1.5s
        setTimeout(() => {
            gameEl.style.display = 'none';
            endGameEl.style.display = 'flex'

            // Cập nhật thông tin endgame
            document.querySelector('.time-box p').innerText = convertTime(time);
            document.querySelector('.step-box p').innerText = `${step} bước`
        }, 1500);
    }
}

function resetGame() {
    firstCard = null;
    secondCard = null;
    isLock = false
}

function unflipCards() {
    //delay rồi ms úp
    isLock = true

    // Sau delay 1s thì úp lại - remove class flip
    setTimeout(function () {
        firstCard.classList.remove('flip')
        secondCard.classList.remove('flip')

        resetGame();

    }, 1000)
}
// Xử lý khi bấm vào nút bắt đầu game
btnStartGame.addEventListener("click", function () {
  // Lấy level game
  level = Number(levelOptionEl.value);

  // Ẩn START => show GAME
  startGameEl.style.display = "none";
  gameEl.style.display = "flex";

  // Render cards
    renderCards(level);
    
    // Chạy thời gian 
    inverval = setInterval(updateTime, 1000)
});

function updateTime() {
    time++
    timeEl.innerText = convertTime(time)
}

function updateStep() {
    step++
    stepEl.innerText = `${step} bước`;
}

// Ban đầu: 00:00 => 00:15 => 01:00
function convertTime(time) {
    let minute = `0${Math.floor(time / 60)}`.slice(-2);
    //vd: 40 => 00 slice(-2) => 00
    //vd: 660 => 011 slice(-2) ==> 11
    let second = `0${time % 60}`.slice(-2)
    return `${minute}:${second}s`
}

// Xử lý khi bấm vào nút chơi lại
btnPlayAgain.addEventListener("click", function () {
    // Reset các thành phần 
    score = 0;
    time = 0;
    step = 0;

    timeEl.innerText = convertTime(time);
    stepEl.innerText = `${step} bước`;

    // Chạy lại thời gian
    interval = setInterval(updateTime, 1000)

    // Hiển thị card lại 
    renderCards(level)

    // Ẩn END => show GAME
  endGameEl.style.display = "none";
    gameEl.style.display = "flex";

});

// Xử lý khi bấm vào nút thoát game
btnReload.addEventListener("click", function () {
  // RELOAD lại trang web
  window.location.reload();
});

I look forward to connecting and making great memories with you!


Trang (Tee) Thao Nguyen