Chào các bạn, lâu ngày không làm cái game nào cũng cảm thấy ngứa ngáy chân tay, dạo này covid hoàng hành, cách ly xã hội ở nhà cả tháng nên có nhiều thời gian rảnh, tranh thủ làm cái game bằng Javascript chơi chơi để hôm nay chia sẻ với mọi người. Vậy hôm nay chúng ta cùng làm game Memory Card, chắc nhiều bạn đã từng chơi game này nên cũng quá rành luật chơi rồi phải không nào, 😂😂

Luật chơi của chúng ta như sau:

  • Ban đầu người chơi sẽ lựa chọn level để chơi game, tùy thuộc vào level mà chúng ta sẽ có số lượng card khác nhau

  • Trong màn hình chơi game người chơi sẽ tiến hành tìm kiếm các lá bài trùng nhau

  • Mỗi lượt chơi được mở 2 lá bài, nếu trùng nhau lá bài sẽ được đánh dấu, nếu không trùng nhau các lá bài sẽ được úp lại

  • Mỗi lượt chơi sẽ tính là 1 lượt di chuyển, có phần đếm thời gian để xem thành tích của người chơi

  • Khi người chơi mở hết các card giống nhau, phần popup thông báo người chiến thắng sẽ được hiển thị ra, thông báo số lượt di chuyển và thời gian hoàn thành game cho người chơi

Các bạn có thể tham khảo giao diện sau

Màn hình bắt đầu game

màn hình bắt đầu game

Màn hình chơi game

màn hình chơi game

Màn hình kết thúc game

màn hình kết thúc game

Phần sources code đầu game các bạn có thể tham khảo tại đây: https://github.com/buihien0109/memory-card-blog/tree/main/start

OK!!! Bắt đầu code thôi nào

Xử lý khi bắt đầu game

Khi người chơi muốn bắt đầu game thì cần chọn level cho game (mặc định ở đây là level easy có 4 card) và ấn nút bắt đầu. Vì vậy chúng ta cần lắng nghe sự kiện khi mà người chơi bấm vào nút này

const btnStartGame = document.querySelector('#btn-start-game');

btnStartGame.addEventListener('click', function () {
    // Todo something
});

Vậy thì sau khi người chơi nhấn nút chơi game thì chúng ta phải xử lý các công việc gì tiếp theo?

Và đây là các công việc mà chúng ta cần thực hiện:

  • Lấy giá trị level game mà người chơi đã lựa chọn
  • Ẩn màn START => show màn GAME
  • Khởi tạo game (render card)
  • Bắt đầu đếm thời gian chơi game
const startGameEl = document.querySelector('#start-game');
const gameEl = document.querySelector('#game');
const levelOptionEl = document.querySelector('#level-option');

let level;
let time = 0;
let interval;

btnStartGame.addEventListener('click', function () {
    // Lấy giá trị level game
    level = Number(levelOptionEl.value);

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

    // Khởi tạo game (render card)
    renderCards(level);

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

Để bắt đầu game thì chúng ta cần biết level người chơi lựa chọn là gì, để lấy được level chúng ta sẽ truy cập vào DOM element có id="#level-option" và lấy ra giá trị value

level = Number(levelOptionEl.value);

Tiếp đến chúng ta ẩn màn hình start game và cho hiển thị màn hình chơi game lên

startGameEl.style.display = 'none';
gameEl.style.display = 'flex';

Tiếp theo là quá trình đếm thời gian người chơi bắt đầu chơi game

interval = setInterval(updateTime, 1000);

Ở đây chúng ta thực hiện function updateTime cứ sau mỗi 1000ms (1s). Vậy nhiệm vụ của function updateTime là gì, các bạn có thể quan sát phần định nghĩa bên dưới đây

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

function convertTime(time) {
    let minute = `0${Math.floor(time / 60)}`.slice(-2);
    let second = `0${time % 60}`.slice(-2);
    return `${minute}:${second}s`;
}

Mỗi lần function updateTime được gọi thì biến time sẽ tăng lên 1 đơn vị (mặc định khi mới bắt đầu game thì time = 0). Sau khi time tăng lên, lúc này chúng ta sẽ cập nhật lại giá trị của time lên trên giao diện.

Để hiển thị đúng format mm:ss. Ở đây mình có định nghĩa thêm function convertTime để thực hiện việc format cho đúng chuẩn, các bạn có thể tham khảo

Mockup data

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',
    },
];

Ở đây mình không muốn fix cứng các card game trong mã HTML, vì nó khá bất lợi trong quá trình mình chọn các level khác nhau (nếu có 1 level thì OK). Vì vậy mình có mockup thêm 1 mảng danh sách các card, mỗi card ở đây là một object bao gồm 2 thuộc tính :

  • url : Ảnh hiển thị của card
  • name : Tên card (cái này dùng để xác định 2 card có match với nhau hay không khi chúng ta thực hiện chơi game)

Card layout

let sizes = {
    2: {
        row: 2,
        col: 2,
    },
    4: {
        row: 2,
        col: 4,
    },
    6: {
        row: 3,
        col: 4,
    },
};

Vì ở đây, mình có 1 số level game khác nhau, mỗi level lại có số card khác nhau, vì vậy để hiển thị giao diện cho đẹp đẹp tí, có trật tự, kỷ cương thì mình có định nghĩa thêm biến sizes. Thì tùy thuộc vào giá trị của level là 2,4 hoặc 6 thì mình sẽ sắp xếp số card theo 1 đội hình cụ thể. Ví dụ level 4 card (2 hàng, 2 cột), level 8 card thì (2 hàng, 4 cột), ... Nếu các bạn định nghĩa nhiều level hơn thì có thể cấu hình việc hiển thị tại đây

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

Để tăng thêm phần kịch tính cho game thì ở mỗi lần chơi game mình sẽ đảo ngẫu nhiên vị trí các card trong game, để làm được điều này thì mình có định nghĩa thêm function shuffle(), và sử dụng nó về sau

Hiển thị danh sách card trong giao diện chơi game

function renderCards(level) {
    // Đảo vị trí các phần tử trong mảng card
    listCards = shuffle(listCards);

    // Cắt lấy số phần tử = level
    let cardsSlice = listCards.slice(0, level);

    // Nhân đôi mảng card
    cards = [...cardsSlice, ...cardsSlice];

    // Đảo vị trí phần tử trong mảng
    cards = shuffle(cards);

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

    // Hiển thị lên trê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)"
            >
                <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>
        `;
    }
}

renderCards chính là step cuối cùng khi chúng ta bắt đầu chơi game, tùy thuộc vào level người chơi lựa chọn là gì thì chúng ta sẽ hiển thị ra danh sách card tương ứng, vì vậy chúng ta cần truyền parameter chính là level mà người chơi lựa chọn

Trong function renderCards chúng ta lại thực hiện một số công việc sau:

  • Đảo vị trí các phần tử trong mảng card
  • Cắt lấy số phần tử = level
  • Nhân đôi mảng card
  • Đảo vị trí phần tử trong mảng
  • Set kích thước cho game board
  • Hiển thị danh sách card lên giao diện

Xong bước này chúng ta đã được kết quả như sau

Màn hình bắt đầu game

Màn hình bắt đầu game

Màn hình chơi game

Màn hình chơi game

Thật tuyệt phải không nào 😊😊

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é ❤️


Ở phần tiếp theo của bài viết này, mình sẽ hướng dẫn các bạn các chức năng còn lại 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

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

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.