Freedom Sale
Independence Day Special — Unlock the AI Path 70% off our most popular AI course · Limited time offer
--Days
--Hrs
--Min
--Sec
Claim Your Discount
✦ Intermediate ⏱ 40 min

🃏 Build a Memory Card Game with JavaScript

🎯 What You'll Build

A memory card game — 16 shuffled emoji cards, flip animation, match detection, move counter, and a completion timer. Pure HTML, CSS, and JavaScript.

📋 What You'll Need

1

Game data and shuffle logic

Create 8 pairs of emoji cards, double the array, and shuffle with the Fisher-Yates algorithm.

const EMOJIS = ['🐶','🐱','🐭','🐹','🐰','🦊','🐻','🐼'];

function createDeck() {
  // Duplicate each emoji to make pairs
  const deck = [...EMOJIS, ...EMOJIS];
  // Fisher-Yates shuffle
  for (let i = deck.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [deck[i], deck[j]] = [deck[j], deck[i]];
  }
  return deck;
}

console.log(createDeck());
// ['🐻','🐰','🦊','🐶', ...] — random order every time
2

HTML and CSS — card flip effect

CSS 3D transform creates a realistic card flip. The card has a front and back face.

<!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>
  <style>
    * { margin:0; padding:0; box-sizing:border-box; }
    body { font-family:'Segoe UI',sans-serif; background:#0f172a; color:#fff; min-height:100vh; display:flex; flex-direction:column; align-items:center; padding:24px; }
    h1 { font-size:1.4rem; margin-bottom:12px; }
    .stats { display:flex; gap:32px; margin-bottom:20px; font-size:0.95rem; color:#94a3b8; }
    .stats b { color:#fff; }
    .board { display:grid; grid-template-columns:repeat(4,1fr); gap:12px; max-width:400px; width:100%; }
    .card { aspect-ratio:1; perspective:600px; cursor:pointer; }
    .card-inner { width:100%; height:100%; position:relative; transform-style:preserve-3d; transition:transform 0.5s; }
    .card.flipped .card-inner { transform:rotateY(180deg); }
    .card-face { position:absolute; inset:0; border-radius:12px; display:flex; align-items:center; justify-content:center; font-size:2rem; backface-visibility:hidden; }
    .card-back  { background:#1e293b; border:2px solid #334155; }
    .card-back::before { content:'?'; font-size:1.6rem; color:#475569; font-weight:800; }
    .card-front { background:#162447; border:2px solid #3b82f6; transform:rotateY(180deg); }
    .card.matched .card-front { background:#162d1e; border-color:#22c55e; }
    .btn { margin-top:20px; padding:11px 28px; background:#3b82f6; border:none; border-radius:10px; color:#fff; font-size:0.95rem; font-weight:700; cursor:pointer; }
  </style>
</head>
<body>

<?php require_once __DIR__ . '/../promo_banner.php'; ?>

  <h1>Memory Card Game</h1>
  <div class="stats">
    <span>Moves: <b id="moves">0</b></span>
    <span>Matches: <b id="matches">0</b>/8</span>
    <span>Time: <b id="timer">0s</b></span>
  </div>
  <div class="board" id="board"></div>
  <button class="btn" onclick="startGame()">New Game</button>
  <script src="game.js"></script>
</body>
</html>
3

Game logic — flip, match, and win detection

Track flipped cards, check for matches, and handle the lock during animations.

// game.js
const EMOJIS = ['dog','cat','mouse','hamster','rabbit','fox','bear','panda']
  .map(e => ({dog:'🐶',cat:'🐱',mouse:'🐭',hamster:'🐹',rabbit:'🐰',fox:'🦊',bear:'🐻',panda:'🐼'})[e]);

let flipped = [], matched = 0, moves = 0, locked = false, timerRef, seconds = 0;

function createDeck() {
  const d = [...EMOJIS,...EMOJIS];
  for (let i=d.length-1;i>0;i--){const j=Math.floor(Math.random()*(i+1));[d[i],d[j]]=[d[j],d[i]];}
  return d;
}

function startGame() {
  clearInterval(timerRef); flipped=[]; matched=0; moves=0; locked=false; seconds=0;
  document.getElementById('moves').textContent   = 0;
  document.getElementById('matches').textContent = 0;
  document.getElementById('timer').textContent   = '0s';

  const board = document.getElementById('board');
  board.innerHTML = '';
  createDeck().forEach((emoji, i) => {
    const card = document.createElement('div');
    card.className = 'card';
    card.dataset.emoji = emoji;
    card.innerHTML = `<div class="card-inner"><div class="card-face card-back"></div><div class="card-face card-front">${emoji}</div></div>`;
    card.addEventListener('click', () => flipCard(card));
    board.appendChild(card);
  });

  timerRef = setInterval(() => {
    seconds++;
    document.getElementById('timer').textContent = seconds + 's';
  }, 1000);
}

function flipCard(card) {
  if (locked || card.classList.contains('flipped') || card.classList.contains('matched')) return;
  card.classList.add('flipped');
  flipped.push(card);

  if (flipped.length === 2) {
    moves++;
    document.getElementById('moves').textContent = moves;
    locked = true;
    const [a, b] = flipped;

    if (a.dataset.emoji === b.dataset.emoji) {
      a.classList.add('matched'); b.classList.add('matched');
      matched++;
      document.getElementById('matches').textContent = matched;
      flipped = []; locked = false;
      if (matched === EMOJIS.length) {
        clearInterval(timerRef);
        setTimeout(() => alert(`You won! ${moves} moves in ${seconds}s`), 300);
      }
    } else {
      setTimeout(() => {
        a.classList.remove('flipped'); b.classList.remove('flipped');
        flipped = []; locked = false;
      }, 1000);
    }
  }
}

startGame();
💡 Tip: The card flip uses CSS perspective + rotateY(180deg). The "backface-visibility: hidden" property is crucial — without it, both the front and back faces would be visible simultaneously through the card.

🎉 You Did It!

You implemented three classic programming patterns in one game: the Fisher-Yates shuffle, state machine game logic (locked/unlocked), and CSS 3D transforms. The same flip-card technique is used for interactive flashcard apps and product showcase carousels.

Found something wrong?

Spotted a bug, broken code, or something that doesn't look right? Tell us what's off and we'll fix it.