Building a Simple Memory Game with Vanilla JavaScript: My Creative Coding Journey
Discover how I developed a fun and interactive memory game using only vanilla JavaScript, HTML, and CSS. Follow my step-by-step process and learn practical tips for creating engaging web games.
Ajmal Razaq
Introduction
As a passionate web developer, I always look for exciting projects to sharpen my skills and create engaging experiences. Recently, I decided to challenge myself by building a simple memory game using only vanilla JavaScript, HTML, and CSSโno frameworks or libraries involved. This project not only helped me understand core JavaScript concepts better but also enabled me to craft a fun game that I could share with friends and the online community. In this blog post, I will walk you through my development process, the challenges I faced, and the lessons I learned along the way.
Setting the Foundation
Before diving into the code, I outlined the basic features and functionalities I wanted to implement:
- A grid of face-down cards.
- Clicking a card reveals its face.
- The player can select two cards at a time.
- If the cards match, they stay face-up; if not, they flip back.
- The game ends when all pairs are matched.
- A move counter to track the player's attempts.
- A restart button for replayability.
With these goals in mind, I felt ready to start coding.
Creating the HTML Structure
First, I set up a simple HTML structure that would serve as the canvas for my game. My goal was to keep it minimalistic yet functional.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Memory Game with Vanilla JS</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<h1>Memory Game</h1>
<div class="game-container">
<div class="card-grid"></div>
</div>
<div class="status-container">
<p>Moves: <span id="moveCount">0</span></p>
<button id="restartBtn">Restart Game</button>
</div>
<script src="script.js"></script>
</body>
</html>
This simple HTML provides a header, a container for the game grid, a status section for move counts and a restart button, and links to the CSS and JavaScript files.
Styling with CSS
Next, I kept the CSS clean and straightforward to enhance user experience and make the cards visually appealing.
body {
font-family: Arial, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
background-color: #f0f0f0;
margin: 0;
padding: 20px;
}
h1 {
margin-bottom: 10px;
}
.game-container {
background-color: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
.card-grid {
display: grid;
grid-template-columns: repeat(4, 80px);
grid-gap: 10px;
}
.card {
width: 80px;
height: 80px;
background-color: #4CAF50;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 36px;
color: white;
cursor: pointer;
user-select: none;
transition: transform 0.2s;
}
.card:hover {
transform: scale(1.05);
}
.card.face-down {
background-color: #777;
font-size: 0;
}
.status-container {
margin-top: 15px;
display: flex;
justify-content: center;
align-items: center;
gap: 15px;
}
button {
padding: 8px 16px;
border: none;
border-radius: 4px;
background-color: #2196F3;
color: #fff;
font-size: 16px;
cursor: pointer;
transition: background-color 0.2s;
}
button:hover {
background-color: #1976D2;
}
This CSS styles the grid, cards, and controls, creating a clean, user-friendly interface.
Implementing JavaScript Functionality
Now comes the core part: scripting the game logic. I aimed for clear, modular code to handle shuffling, card flipping, matching logic, and game resets.
Here's an overview of my approach:
- Initialize the game with an array of paired symbols (for example, emojis or numbers).
- Shuffle the array to randomize card positions, using the Fisher-Yates algorithm.
- Generate card elements dynamically based on the shuffled array.
- Add event listeners to handle card clicks.
- Manage game state with variables tracking selected cards, matches, and move count.
- Implement match logic to determine if two selected cards are the same.
- Handle unmatched pairs with a slight delay before flipping back.
- Check for game completion when all pairs are matched.
- Provide a restart function to reset the game.
Here's the full JavaScript code:
// script.js
// Define the symbols for the pairs
const symbols = ['๐', '๐', '๐', '๐', '๐', '๐ฅ', '๐ฅฅ', '๐'];
let cardArray = [];
let firstCard = null;
let secondCard = null;
let lockBoard = false;
let matchedPairs = 0;
let moveCount = 0;
const cardGrid = document.querySelector('.card-grid');
const moveCounterSpan = document.getElementById('moveCount');
const restartBtn = document.getElementById('restartBtn');
// Initialize game
function initGame() {
// Reset variables
cardArray = [];
firstCard = null;
secondCard = null;
lockBoard = false;
matchedPairs = 0;
moveCount = 0;
moveCounterSpan.textContent = moveCount;
// Clear existing cards
cardGrid.innerHTML = '';
// Prepare paired symbols
const pairedSymbols = [...symbols, ...symbols];
// Shuffle the array
shuffleArray(pairedSymbols);
// Generate cards
pairedSymbols.forEach((symbol, index) => {
const card = document.createElement('div');
card.classList.add('card', 'face-down');
card.dataset.symbol = symbol;
// Add click event
card.addEventListener('click', handleCardClick);
// Append to grid
cardGrid.appendChild(card);
});
}
// Fisher-Yates Shuffle Algorithm
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}
// Handle card clicks
function handleCardClick(e) {
if (lockBoard) return;
const clickedCard = e.currentTarget;
// Ignore if already face-up
if (clickedCard.classList.contains('face-down') === false) return;
revealCard(clickedCard);
if (!firstCard) {
firstCard = clickedCard;
return;
}
secondCard = clickedCard;
// Increment move count
moveCount++;
moveCounterSpan.textContent = moveCount;
// Check for match
if (firstCard.dataset.symbol === secondCard.dataset.symbol) {
// Keep cards face-up
disableCards();
matchedPairs++;
// Check for game completion
if (matchedPairs === symbols.length) {
setTimeout(() => {
alert(`Congratulations! You completed the game in ${moveCount} moves.`);
}, 500);
}
} else {
// Not a match: flip back after delay
lockBoard = true;
setTimeout(() => {
hideCard(firstCard);
hideCard(secondCard);
resetSelection();
}, 1000);
}
}
// Reveal card
function revealCard(card) {
card.classList.remove('face-down');
card.textContent = card.dataset.symbol;
}
// Hide card
function hideCard(card) {
card.classList.add('face-down');
card.textContent = '';
}
// Disable matched cards
function disableCards() {
firstCard.removeEventListener('click', handleCardClick);
secondCard.removeEventListener('click', handleCardClick);
resetSelection();
}
// Reset selection variables
function resetSelection() {
[firstCard, secondCard] = [null, null];
lockBoard = false;
}
// Handle game restart
restartBtn.addEventListener('click', initGame);
// Start the game on page load
window.addEventListener('load', initGame);
Challenges I Faced
While building this game, I encountered several hurdles that helped me learn more about vanilla JavaScript:
- Managing State: Keeping track of the selected cards and preventing multiple clicks during animations required careful variable management.
- Asynchronous Timing: Using
setTimeoutto flip cards back introduced challenges in ensuring the game flow remained smooth. - Event Handling: Dynamically generating cards meant dynamically attaching event listeners, which I handled carefully to avoid memory leaks.
- Responsive Design: Making sure the game looked decent on different screen sizes pushed me to tweak CSS media queries later.
Lessons Learned and Future Improvements
This project was a fantastic way to deepen my understanding of DOM manipulation, event handling, and game logic. Some areas I plan to improve include:
- Adding a Timer: To track how long players take to finish.
- Implementing Difficulty Levels: Changing grid sizes or symbol complexity.
- Persistent Score Tracking: Saving high scores in local storage.
- Enhancing UI/UX: Adding animations and better visual feedback.
Conclusion
Building this memory game from scratch using vanilla JavaScript was both challenging and rewarding. It reinforced core programming concepts, boosted my confidence in DOM manipulation, and resulted in a playable, enjoyable web game. If you're looking to improve your JavaScript skills or create simple web-based games, I highly recommend trying a project like this. Remember, starting small and iterating gradually is key.
I hope my walkthrough inspires you to craft your own web games. Feel free to reach out with questions or share your projectsโIโd love to see what you create!
Happy coding!