Memory Card Game
Build a classic flip-and-match memory game with smooth card flip animations. Shuffle cards, track matches, and manage game state.
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
<!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
* { 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
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 ProjectSingle HTML file · All code included