๐ŸŸจ My First Browser Game ยท Episode 2 of 6 ยท See All Episodes
๐Ÿง  Episode 2 ยท Beginner ยท DOM & CSS Skills

Match Maker!
Memory Card Game

No canvas this time, build a card-flipping memory game out of real HTML elements, CSS 3D flips and DOM events. This is the same skill set as building web apps, disguised as a game.

๐Ÿ‘ถ Ages 10+ โฑ๏ธ ~1.5 Hours ๐ŸŸจ JavaScript โœ“ Free
๐Ÿƒ DOM elements ๐Ÿ”€ Array shuffle ๐ŸŽจ CSS 3D flips ๐Ÿ–ฑ๏ธ Click events ๐Ÿง  Match logic โฑ๏ธ Move counter
โญ
0 XP
Level 1
๐Ÿ”ฅ0
Your Progress 0 / 6 steps
1
๐Ÿƒ
The Board from Code
Generate 16 card divs with JavaScript
Active
๐ŸŽฏ
Goal for this step

Build a 4ร—4 grid of cards entirely from JavaScript.

  • 1New file memory.html: a <div id="board"> styled with display: grid, 4 columns.
  • 28 emoji pairs: double the array, so 16 values.
  • 3Loop the values: document.createElement("div"), add class "card", store its emoji in card.dataset.value, append to the board.
  • 4Cards show "?" until flipped. The page builds itself, that is the DOM.
memory.html
<div id="board"></div>
<style>
#board { display: grid; grid-template-columns: repeat(4, 90px); gap: 10px; }
.card  { height: 90px; background: #2a2d3a; border-radius: 12px;
         display: flex; align-items: center; justify-content: center;
         font-size: 2rem; cursor: pointer; color: #fff; }
</style>
<script>
const EMOJI = ["๐Ÿ","๐Ÿš€","๐ŸŽ","๐ŸŽฎ","๐Ÿฑ","โšก","๐ŸŒ™","๐ŸŽฒ"];
let values = [...EMOJI, ...EMOJI];      // 8 pairs = 16 cards

const board = document.getElementById("board");
for (const v of values) {
    const card = document.createElement("div");
    card.className = "card";
    card.dataset.value = v;
    card.textContent = "?";
    board.appendChild(card);
}
</script>
โœ๏ธ
Fill in the Blanks
+15 XP
New elements are made with document. and attached with board.. Each card remembers its emoji in card.dataset..
๐Ÿง 
Knowledge Check
+15 XP
[...EMOJI, ...EMOJI] createsโ€ฆ
AA copy with 8 items
BOne array containing every emoji twice, the 16 card faces
CA syntax error
2
๐Ÿ”€
The Fisher-Yates Shuffle
Shuffle the deck the CORRECT way
Locked
๐ŸŽฏ
Goal for this step

Randomise card order with the classic fair-shuffle algorithm.

  • 1Shuffling arrays has a right answer: Fisher-Yates, walk backwards, swap each item with a random earlier one.
  • 2It is provably fair: every arrangement equally likely. (The lazy .sort(() => Math.random()-0.5) is genuinely biased!)
  • 3Shuffle values BEFORE building the cards.
  • 4Refresh a few times, different board every game.
memory.html
function shuffle(arr) {
    for (let i = arr.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [arr[i], arr[j]] = [arr[j], arr[i]];   // swap!
    }
    return arr;
}

values = shuffle(values);
๐Ÿ’ก
[a, b] = [b, a] is JavaScriptโ€™s one-line swap, destructuring assignment. No temp variable needed.
โŒจ๏ธ
Code Challenge
+20 XP
Complete Fisher-Yates:
memory.html
for (let i = arr.length - 1; i > ; i--) {
    const j = Math.floor(Math.random() * (i + ));
    [arr[i], arr[j]] = [arr], arr[i]];
}
๐Ÿ’ก Hint: Walk down to index 1; the random pick includes position i itself (hence +1); then swap the two.
๐Ÿง 
Knowledge Check
+15 XP
Why learn a named algorithm for something as small as shuffling?
AInterviews only
BBecause the obvious hack is subtly WRONG (biased), some problems have known correct answers worth knowing
CShuffling is rare
3
๐ŸŽจ
CSS Flip Animation
Cards flip in 3D with a .flipped class
Locked
๐ŸŽฏ
Goal for this step

Flip cards over with a smooth CSS 3D rotation.

  • 1The animation is pure CSS: transition: transform 0.35s and a .flipped class that sets transform: rotateY(180deg).
  • 2JavaScript only toggles the class, CSS does the motion. That division of labour is how modern web apps animate.
  • 3On flip, also reveal the emoji (swap textContent from "?" to the dataset value).
  • 4Click a card โ†’ click handler adds .flipped. Satisfying already!
memory.html
<style>
.card { transition: transform 0.35s; }
.card.flipped { transform: rotateY(180deg); background: #ffd166; color: #222; }
</style>
<script>
board.addEventListener("click", function(e) {
    const card = e.target.closest(".card");
    if (!card || card.classList.contains("flipped")) return;
    card.classList.add("flipped");
    card.textContent = card.dataset.value;
});
</script>
โœ๏ธ
Fill in the Blanks
+15 XP
One listener on the whole handles every card via e.target.closest, that pattern is called event . The motion itself is defined in , not JavaScript.
๐Ÿง 
Knowledge Check
+15 XP
One click listener on the board instead of 16 on the cards isโ€ฆ
ASlower but tidier
BEvent delegation, fewer listeners, and it even works for cards added later
CRequired by browsers
4
๐Ÿง 
Match Logic
Two flipped cards: stay or flip back
Locked
๐ŸŽฏ
Goal for this step

Compare pairs and lock matches or flip mismatches back.

  • 1Track state: let first = null; let locked = false;
  • 2First click โ†’ remember the card. Second โ†’ compare dataset values.
  • 3Match: both get .matched (and stop being clickable). No match: lock the board, wait 0.8 s (setTimeout), flip both back, unlock.
  • 4The lock stops players spam-flipping a third card during the wait, the classic memory-game bug.
memory.html
let first = null, locked = false;

function onCardClick(card) {
    if (locked || card.classList.contains("flipped")) return;
    flip(card);

    if (!first) { first = card; return; }

    if (first.dataset.value === card.dataset.value) {
        first.classList.add("matched");
        card.classList.add("matched");
        first = null;
    } else {
        locked = true;
        setTimeout(function() {
            unflip(first); unflip(card);
            first = null;
            locked = false;
        }, 800);
    }
}
โŒจ๏ธ
Code Challenge
+20 XP
Handle the mismatch with a delay:
memory.html
locked = ;
(function() {
    unflip(first); unflip(card);
    first = null;
    locked = ;
}, 800);
๐Ÿ’ก Hint: Freeze input, schedule the flip-back for later with the browser timer function, then unfreeze inside it.
๐Ÿง 
Knowledge Check
+15 XP
What breaks WITHOUT the locked flag?
ACSS stops working
BPlayers can flip a 3rd card during the 0.8 s wait, tangling the two-card state machine
CsetTimeout fails
5
โฑ๏ธ
Moves, Timer & Winning
Count pairs tried, time the game, detect victory
Locked
๐ŸŽฏ
Goal for this step

Add a move counter, a live timer and the win banner.

  • 1Moves = pair attempts: increment on every SECOND flip.
  • 2Timer: setInterval every second updates a seconds counter on screen; start on first flip, stop on win.
  • 3Win check: count .matched cards === 16 โ†’ show "You won in 14 moves and 63 seconds!"
  • 4Show moves and time in a HUD line above the board.
memory.html
let moves = 0, seconds = 0, timerId = null;

function startTimer() {
    if (timerId) return;                 // only once
    timerId = setInterval(function() {
        seconds++;
        document.getElementById("time").textContent = seconds + "s";
    }, 1000);
}

function checkWin() {
    if (document.querySelectorAll(".matched").length === 16) {
        clearInterval(timerId);
        document.getElementById("msg").textContent =
            "๐ŸŽ‰ You won in " + moves + " moves and " + seconds + "s!";
    }
}
โœ๏ธ
Fill in the Blanks
+15 XP
A repeating timer uses with 1000 ms, and is stopped with (timerId). The win condition counts elements with the class.
๐Ÿง 
Knowledge Check
+15 XP
setTimeout vs setInterval, which is which?
AThey are identical
BsetTimeout fires ONCE after a delay (flip-back); setInterval fires REPEATEDLY (the ticking clock)
CsetInterval is newer
6
๐Ÿ†
Difficulty & Best Scores
4ร—4 vs 6ร—6 boards and localStorage records
Locked
๐ŸŽฏ
Goal for this step

Add difficulty levels and persistent best scores per level.

  • 1Difficulty buttons: Easy 4ร—4 (8 pairs) and Hard 6ร—6 (18 pairs, extend the emoji list!). Rebuild the board from a size variable; set the grid columns in code.
  • 2Store bests per mode: keys "memory-best-4" and "memory-best-6", fewest moves wins.
  • 3Show the record next to each button, beat-your-best is the retention loop.
  • 4You just built interface, state, animation and persistence. That IS front-end development. Episode 3 returns to canvas!
memory.html
function newGame(size) {                  // 4 or 6
    board.style.gridTemplateColumns =
        "repeat(" + size + ", 90px)";
    const pairs = (size * size) / 2;
    values = shuffle([...EMOJI.slice(0, pairs),
                      ...EMOJI.slice(0, pairs)]);
    buildBoard(values);
    moves = 0; seconds = 0;
}

// after a win:
const key = "memory-best-" + size;
const best = Number(localStorage.getItem(key) || 999);
if (moves < best) localStorage.setItem(key, moves);
โŒจ๏ธ
Code Challenge
+20 XP
Compute the pair count for any board size:
memory.html
const pairs = (size * ) / ;
๐Ÿ’ก Hint: A square board has sizeร—size cards, and every pair covers two of them.
๐Ÿง 
Knowledge Check
+15 XP
The fairest "best score" for memory is fewest moves, not fastest time, becauseโ€ฆ
ATimers are buggy
BMoves measure MEMORY skill; time also measures mouse speed and panic
ClocalStorage prefers integers
๐ŸŽ‰๐Ÿ†๐ŸŽฎโœจ๐ŸŽ‰
Workshop Complete!
DOM building, shuffling, CSS animation and state logic, that is literally web-app development. Episode 3: back to canvas for Breakout!
0
Total XP
1
Level
0
Best Streak
0%
Accuracy
โ–ถ Episode 3: Breakout โ†’
โญ View My Progress & Certificates

This workshop was free and took many hours to build. If it helped you learn something new, consider supporting the project.

☕ Support on Ko-fi