Skip to Content
Course content

187: Building a Memory Card Matching Game

Click on the "Edit" button in the top corner of the screen to edit your slide content.

We've spent a lot of time talking about arrays and DOM events in isolation. Now, we're going to actually glue those concepts together. We're building a Memory Card Matching game. It sounds simple, but this is where you start to see how "state"β€”the current condition of your appβ€”really drives the user experience.

Defining the Deck and the Shuffle

First, I need a set of pairs. I'll use an array of emojis because it saves us from having to deal with external image assets. Since we need pairs, I'll create a list and then duplicate it. To make the game playable, we obviously can't have the cards in the same order every time.

const symbols = ['🍎', '🍌', 'πŸ‡', 'πŸ“', 'πŸ₯', '🍍'];
const deck = [...symbols, ...symbols];

// A quick and dirty shuffle
deck.sort(() => Math.random() - 0.5);

Now, I'm going to loop through this shuffled array and generate the HTML elements. I'll wrap each card in a div and give it a data-attribute to store its symbol. This is a trick I use all the time: keep the "truth" of the element in the HTML attributes so the JavaScript doesn't have to maintain a massive secondary mapping object.

Wiring Up the Flip Logic

The core of the game is the click handler. When a card is clicked, we flip it over. But we can't just flip everything; we need to keep track of which cards are currently "active" to see if they match.

let flippedCards = [];
let matchedPairs = 0;

function flipCard(card) {
    if (card === this.previousElementSibling || card.classList.contains('matched')) return;
    
    card.classList.add('flipped');
    flippedCards.push(card);

    if (flippedCards.length === 2) {
        checkMatch();
    }
}

The "Three-Card" Bug

Here is where I usually trip up when I first build these. I'll be honest: when I first wrote this logic, I noticed a glitch. If I clicked two cards and they didn't match, there was a half-second delay before they flipped back. During that tiny window, I could click a third or fourth card. This completely broke the `flippedCards` array, and the game would just stop working because it was trying to match the wrong pairs.

The fix is a "lock." I need a boolean variable that tells the game, "Hey, we are currently processing a match, stop listening to clicks for a moment."

let lockBoard = false;

function flipCard(card) {
    if (lockBoard) return; // This is the fix!
    if (card === this.previousElementSibling || card.classList.contains('matched')) return;
    
    card.classList.add('flipped');
    flippedCards.push(card);

    if (flippedCards.length === 2) {
        checkMatch();
    }
}

Evaluating the Match

Now that we've protected the board from rapid-fire clicking, we can handle the actual comparison. If the symbols match, we leave them flipped and mark them as matched. If they don't, we use setTimeout to flip them back after a short delay so the player can actually see what the second card was.

function checkMatch() {
    lockBoard = true;
    const [card1, card2] = flippedCards;

    if (card1.dataset.symbol === card2.dataset.symbol) {
        card1.classList.add('matched');
        card2.classList.add('matched');
        matchedPairs++;
        resetTurn();
        if (matchedPairs === symbols.length) alert('You won!');
    } else {
        setTimeout(() => {
            card1.classList.remove('flipped');
            card2.classList.remove('flipped');
            resetTurn();
        }, 1000);
    }
}

function resetTurn() {
    flippedCards = [];
    lockBoard = false;
}

By isolating the resetTurn logic, I ensure that the lockBoard flag and the flippedCards array are always cleaned up together. This prevents the game from getting stuck in a "locked" state forever.




πŸ“‹ Practical Task

Implement a Move Counter and Win-Condition Timer

Take the memory game logic from this lesson and add a "Scoreboard" to the top of the page. You need to implement the following:

  • Move Counter: Create a variable that increments every time a pair is checked (regardless of whether it was a match or not). Display this number in the HTML.
  • Game Timer: Start a timer the moment the first card is clicked. Stop the timer when the final pair is matched.
  • Final Result: When the user wins, instead of a simple alert, display a message saying: "You won in [X] moves and [Y] seconds!"
Rating
0 0

There are no comments for now.

to be the first to leave a comment.