Back to Projects Expert

Memory Card Game

Build a classic flip-and-match memory game with smooth card flip animations. Shuffle cards, track matches, and manage game state.

HTML, CSS, JavaScript ~1-2 hours to build Free source code

What You'll Learn

  • How to shuffle an array using the Fisher-Yates algorithm
  • How to create HTML elements dynamically and build a game grid
  • How to add smooth 3D flip animations using CSS transforms
  • How to manage game state (first card, second card, locked state)
  • How to add a delay with setTimeout() so animations look smooth

How It Works

Shuffle cards before every game

We start with an array of emoji pairs, then shuffle them randomly using the Fisher-Yates algorithm. This ensures every game is different. The algorithm works by swapping each element with a random earlier element.

CSS handles the 3D flip animation

The flip effect is pure CSS. Each card has a front and back face. When we add a 'flipped' class, CSS rotates the card 180 degrees to reveal the front emoji. No JavaScript animation needed!

JavaScript manages the game state

We track the first and second flipped cards. If their emojis match, they stay face up and get a 'matched' class. If they don't match, we wait 800ms then flip them back. We lock the board during this delay to prevent cheating.

Source Code

HTML Structure

HTML
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Memory Card Game</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <div class="game-wrapper">
    <div class="header">
      <h1>🧠 Memory Game</h1>
      <div class="stats">
        <span>Moves: <strong id="moves">0</strong></span>
        <span>Pairs: <strong id="pairs">0</strong>/8</span>
      </div>
    </div>
    <div id="game-board" class="game-board"></div>
    <button id="restartBtn">New Game</button>
  </div>
  <script src="script.js"></script>
</body>
</html>

CSS Styling

CSS
* { margin:0; padding:0; box-sizing:border-box; }
body { min-height:100vh; background:linear-gradient(135deg,#4a1080,#8e44ad);
  display:flex; align-items:center; justify-content:center;
  font-family:'Segoe UI',sans-serif; padding:20px; }
.game-wrapper { text-align:center; max-width:500px; width:100%; }
.header { display:flex; justify-content:space-between; align-items:center; margin-bottom:20px; }
h1 { color:white; font-size:1.8rem; font-weight:800; }
.stats { color:rgba(255,255,255,0.85); font-size:0.95rem; display:flex; gap:20px; }
.stats strong { color:white; font-size:1.1rem; }
.game-board { display:grid; grid-template-columns:repeat(4,1fr); gap:12px; margin-bottom:20px; }
.card { aspect-ratio:1; cursor:pointer; perspective:600px; }
.card-inner { width:100%; height:100%; position:relative; transform-style:preserve-3d; transition:transform 0.5s; }
.card.flipped .card-inner, .card.matched .card-inner { transform:rotateY(180deg); }
.card-face { width:100%; height:100%; position:absolute; border-radius:12px;
  display:flex; align-items:center; justify-content:center; font-size:2rem; backface-visibility:hidden; }
.card-back { background:rgba(255,255,255,0.15); border:2px solid rgba(255,255,255,0.25); font-size:1.5rem; color:rgba(255,255,255,0.4); }
.card-front { background:white; transform:rotateY(180deg); box-shadow:0 4px 12px rgba(0,0,0,0.2); }
.card.matched .card-front { background:#e8f8ee; border:2px solid #27ae60; }
#restartBtn { padding:12px 36px; background:rgba(255,255,255,0.2); color:white;
  border:2px solid rgba(255,255,255,0.4); border-radius:50px; font-weight:700; font-size:1rem; cursor:pointer; }
#restartBtn:hover { background:rgba(255,255,255,0.35); }

JavaScript Logic

JavaScript
const emojis = ['🎸','🎯','🎪','🦊','🚀','🌈','🎭','💎'];
const allCards = [...emojis, ...emojis];
let flipped = [], matched = 0, moves = 0, locked = false;

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

function createBoard() {
  const board = document.getElementById('game-board');
  board.innerHTML = '';
  flipped = []; matched = 0; moves = 0; locked = false;
  document.getElementById('moves').textContent = '0';
  document.getElementById('pairs').textContent = '0';

  shuffle(allCards).forEach(emoji => {
    const card = document.createElement('div');
    card.className = 'card';
    card.dataset.emoji = emoji;
    card.innerHTML = `
      
?
${emoji}
`; card.addEventListener('click', flipCard); board.appendChild(card); }); } function flipCard() { if (locked || this.classList.contains('flipped') || this.classList.contains('matched')) return; this.classList.add('flipped'); flipped.push(this); if (flipped.length === 2) { locked = true; moves++; document.getElementById('moves').textContent = moves; if (flipped[0].dataset.emoji === flipped[1].dataset.emoji) { flipped.forEach(c => c.classList.add('matched')); matched++; document.getElementById('pairs').textContent = matched; flipped = []; locked = false; if (matched === emojis.length) { setTimeout(() => alert('You won in ' + moves + ' moves! 🎉'), 300); } } else { setTimeout(() => { flipped.forEach(c => c.classList.remove('flipped')); flipped = []; locked = false; }, 800); } } } document.getElementById('restartBtn').addEventListener('click', createBoard); createBoard();

Download Source Code

Single ready-to-run HTML file. Open it in any browser!

Download Project

Single HTML file · All code included