Chào các bạn, ở bài viết trước, chúng ta đã thực hiện setup khi người chơi chọn level và bắt đầu game, ở bài viết này chúng ta sẽ thực hiện các chức năng còn lại để hoàn thiện game Memory card bao gồm:

  • Xử lý sự kiện mở card
  • Kiểm tra khi card trùng nhau hoặc không?
  • Điều kiện win game
  • Hiển thị phần end game, thông báo kết quả
  • Xử lý chơi lại, thoát game

OK, bắt đầu thôi nào 😊😊

Xử lý sự kiện mở card

Trong bài viết trước, trong function renderCard(), step Hiển thị lên trên giao diện, mình có gọi function flipCard() trong này (nhưng mà chưa định nghĩa). Mục đích của function flipCard() chính là lật card lại khi người chơi click vào, bây giờ chúng ta sẽ đi định nghĩa function này

let lockBoard = false; // Khóa không cho ấn
let firstCard = null; // Chứa phần tử DOM CARD khi mở lần 1
let secondCard = null; // Chứa phần tử DOM CARD khi mở lần 2

function flipCard(card) {
    if (lockBoard) {
        return;
    }
    // Người chơi không được click 2 lần trên cùng 1 card
    if (card === firstCard) {
        return;
    }

    card.classList.toggle('flip');

    // Khi click CARD đầu tiên
    if (!firstCard) {
        firstCard = card;
        return;
    }

    // Khi click CARD thứ 2
    secondCard = card;
    checkForMatch();

    // Update step
    updateStep();
}

Khi người chơi click vào 1 card sẽ có 1 số trường hợp xảy ra như sau

  • TH1 : lockBoard == true (game đang ở trạng thái khóa, ví dụ trong trường hợp đang kiểm tra khi người chơi mở 2 card) => return kết thúc hàm
  • TH2 : Khi người chơi đã chọn 1 card và lần mở tiếp theo cũng chọn card đó => return kết thúc hàm

Nếu không phải trong 2 trường hợp trên, lúc này chúng ta sẽ toggle class "flip" để card lật lại

Khi mở đủ 2 card, lúc này chúng ta sẽ gọi function checkForMatch() để kiểm tra xem 2 card chúng ta mở ra có chính xác hay không?

function checkForMatch() {
    // Kiểm tra xem NAME của 2 CARD có giống nhau không?
    let isMatch = firstCard.dataset.name === secondCard.dataset.name;

    // isMatch = true => xóa sự kiện click ở 2 CARD đó
    // isMatch = false => úp CARD xuống
    isMatch ? disableCards() : unflipCards();
}

Để kiểm tra xem 2 card sau khi mở có trùng nhau hay không chúng ta định nghĩa function checkForMatch(). trong function này chúng ta sẽ kiểm tra thuộc tính data-name của 2 card này có trùng nhau hay không?(data-name được set khi render card - các bạn có thể inspect lên để quan sát) và lưu kết quả và biến isMatch

Trường hợp isMatch = true (trùng nhau) chúng ta thực hiện function disableCards(), còn trường hợp isMatch = false (không trùng nhau) thì thực hiện function unflipCards()

Trường hợp mở đúng

function disableCards() {
    firstCard.removeEventListener('click', flipCard);
    secondCard.removeEventListener('click', flipCard);

    resetBoard();

    score++;
    checkWin();
}

Trường hợp 2 card mà trùng nhau, lúc này chúng ta sẽ remove phần lắng nghe sự kiện trên 2 card này để người chơi không click vào được nữa

Tăng giá trị điểm của người chơi score lên 1 đơn vị, và sau đó kiểm tra trường hợp win game của người chơi thông qua function checkWin()

function checkWin() {
    if (score == level) {
        clearInterval(interval);

        setTimeout(() => {
            gameEl.style.display = 'none';
            endGameEl.style.display = 'flex';

            updateEndGame();
        }, 1500);
    }
}

Trong function checkWin() đơn giản là chúng ta kiểm tra xem số điểm mà chúng ta có qua mỗi lần mở đúng có bằng level hay không (score == level). Nếu trường hợp này xảy ra tức là chúng ta đã mở đúng hết toàn bộ số card đang có, lúc này màn hình sẽ đi đến màn end game

setTimeout(() => {
        gameEl.style.display = 'none';
        endGameEl.style.display = 'flex';

        updateEndGame();
}, 1500);

Chúng ta sẽ đợi sau 1.5s, để ẩn màn game => show màn end game, và đồng thời hiển thị 1 số thông tin trong màn end game thông qua function updateEndGame()

function updateEndGame() {
    document.querySelector('.time-box p').innerText = convertTime(time);
    document.querySelector('.step-box p').innerText = `${step} bước`
}

Ở đây chúng ta sẽ cập nhật thời gian chúng ta chơi để hoàn thành game và số step đã đi lên trên giao diện

Và 1 điều quan trọng nữa là chúng ta cần ngừng quá trình chạy time lại

clearInterval(interval);

Để ngừng đếm thời gian chúng ta sử dụng hàm clearInterval(). Điều này rất quan trọng

Trường hợp mở sai

function unflipCards() {
    lockBoard = true;

    setTimeout(() => {
        firstCard.classList.remove('flip');
        secondCard.classList.remove('flip');

        resetBoard();
    }, 1000);
}

function resetBoard() {
    lockBoard = false;
    firstCard = null;
    secondCard = null;
}

Còn trong trường hợp 2 card mở không trùng nhau, lúc này chúng ta sẽ đợi 1000ms (1s) và sau đó cho 2 card up xuống bằng cách remove class "flip" trên mỗi card

Trong quá trình đợi chúng ta sẽ set giá trị lockBoard = true để người chơi không thể click vào card khác trong quá trình này

Xử lý chơi lại và thoát game

Chơi lại game

const btnPlayAgain = document.querySelector('#btn-play-again');
btnPlayAgain.addEventListener('click', function () {
    // Reset điểm về 0
    score = 0;
    time = 0;
    step = 0;

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

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

    // Dựa vào level đã có => khởi tạo game
    renderCards(level);

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

Đầu tiên chúng ta sẽ lắng nghe sự kiện khi người chơi click vào button "Chơi lại" (id="btn-play-again")

Khi thực hiện chơi lại game tức là người chơi đang từ màn End game -> quay trở lại màn chơi game với level được giữ nguyên như cũ. Vì vậy trong hàm xử lý sự kiện chúng ta cần phải thực hiện reset 1 số thành phần sau :

  • Cho score, time, step reset về 0 và hiển thị lại các giá trị này ở trên giao diên
  • Bắt đầu đếm thời gian
  • Hiển thị lại danh sách card trên giao diện
  • Ẩn màn end game -> show màn chơi game

Thoát game

const btnReload = document.querySelector('#btn-reload');
btnReload.addEventListener('click', function () {
    window.location.reload();
});

Để thực hiện chức năng thoát game, đơn giản chúng ta chỉ cần reload trang lại là xong, javascript đã hỗ trợ sẵn cho chúng ta điều này


Nếu các bạn có phần nào thắc mắc, thì hãy comment cho mình biết nhé ❤️

Source code của phần này mình để ở đây nhé : https://github.com/buihien0109/memory-card-blog/tree/main/part-2

Các bạn có thể tham khảo thêm khóa học này nhé:

  • Javascript căn bản - Tổng hợp 12 game huyền thoại - tại đây.
  • Lập trình Game Javascript (trực tuyến có tương tác) - tại đây.