Memory Game
Solution
Step 1: Structure the HTML
Create a Container: Set up a container that will hold the game board. This container will be used to dynamically generate and display the cards.
Step 2: Style the Game with CSS
- Create a Grid Layout: Use CSS to style the game board as a grid with 4 rows and 4 columns. Define styles for the cards, including their size, background color, and how they should look when flipped.
- Implement 3D Flip Animation: Add CSS transitions to enable a smooth 3D flip effect when the cards are clicked. This will create the effect of the cards flipping over to reveal their hidden content.
Step 3: Implement the Game Logic with JavaScript
- Prepare the Card Data: Define an array of 8 unique letters (or symbols) and duplicate it to create pairs. Shuffle the array to randomize the card positions on the board.
- Generate the Cards Dynamically: Loop through the shuffled array and create card elements for each letter. Each card has two sides: a front (hidden) and a back (revealing the letter when flipped).
- Handle Card Click Events: Add an event listener to detect when a card is clicked. When a card is clicked, flip it to reveal its letter.
- Check for Matching Pairs: Track the first and second cards that are flipped. If the two cards match, leave them flipped and reset the tracking variables. If they do not match, wait a moment, then flip both cards back over and reset the tracking variables.
- Manage the Game Flow: Continue flipping cards and checking for matches until all pairs are found. Optionally, you could add a mechanism to track the number of attempts or the time taken to find all pairs.
Step 4: Test and Refine
- Test the Game: Test the game thoroughly to ensure that all pairs can be matched and that cards flip correctly. Ensure that the game resets correctly after a match or a non-match.

React JS - one file
import React, { useState, useEffect, useRef, useCallback } from "react";
import "./styles.css";
// Create a list of letters for the cards
const letters = ["A", "B", "C", "D", "E", "F", "G", "H"];
// Function to shuffle the cards
const shuffleCards = (array) => {
const length = array.length;
for (let i = length; i > 0; i--) {
const randomIndex = Math.floor(Math.random() * i);
const currentIndex = i - 1;
const temp = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temp;
}
return array;
};
function App() {
const [cards, setCards] = useState(() =>
shuffleCards(
letters.concat(letters).map((letter, index) => ({ letter, id: index })),
),
);
const [openCards, setOpenCards] = useState([]);
const [clearedCards, setClearedCards] = useState({});
const [shouldDisableAllCards, setShouldDisableAllCards] = useState(false);
const [moves, setMoves] = useState(0);
const timeout = useRef(null);
// Disable all cards during the check
const disable = () => {
setShouldDisableAllCards(true);
};
// Enable all cards after the check
const enable = () => {
setShouldDisableAllCards(false);
};
// Check if all pairs are found
const checkCompletion = useCallback(() => {
if (Object.keys(clearedCards).length === letters.length) {
alert(`Congratulations! You completed the game in ${moves} moves.`);
}
}, [clearedCards, moves]);
// Evaluate if the selected cards match
const evaluate = useCallback(() => {
const [first, second] = openCards;
enable();
if (cards[first].letter === cards[second].letter) {
setClearedCards((prev) => ({ ...prev, [cards[first].letter]: true }));
setOpenCards([]);
} else {
timeout.current = setTimeout(() => {
setOpenCards([]);
}, 500);
}
}, [cards, openCards]);
// Handle card click
const handleCardClick = (index) => {
if (openCards.length === 1) {
setOpenCards((prev) => [...prev, index]);
setMoves((moves) => moves + 1);
disable();
} else {
clearTimeout(timeout.current);
setOpenCards([index]);
}
};
// Check for match when two cards are open
useEffect(() => {
if (openCards.length === 2) {
const timeout = setTimeout(evaluate, 300);
return () => clearTimeout(timeout);
}
}, [openCards, evaluate]);
// Check if game is completed
useEffect(() => {
checkCompletion();
}, [clearedCards, checkCompletion]);
// Check if the card is flipped
const checkIsFlipped = (index) => {
return openCards.includes(index);
};
// Check if the card is inactive (matched)
const checkIsInactive = (card) => {
return Boolean(clearedCards[card.letter]);
};
// Handle restart
const handleRestart = () => {
setClearedCards({});
setOpenCards([]);
setMoves(0);
setShouldDisableAllCards(false);
setCards(
shuffleCards(
letters.concat(letters).map((letter, index) => ({ letter, id: index })),
),
);
};
// Handle reveal all cards
const handleRevealAllChange = (event) => {
if (event.target.checked) {
setClearedCards(
letters.reduce((acc, letter) => ({ ...acc, [letter]: true }), {}),
);
setMoves(0);
} else {
handleRestart();
}
};
return (
<div className="container">
<div className="controls">
<button onClick={handleRestart}>
{Object.keys(clearedCards).length === letters.length
? "Play Again"
: "Restart"}
</button>
<label>
<input type="checkbox" onChange={handleRevealAllChange} /> Reveal All
</label>
</div>
<div className="game-board">
{cards.map((card, index) => (
<div
key={card.id}
className={`card ${checkIsFlipped(index) || checkIsInactive(card) ? "flipped" : ""}`}
onClick={() =>
!shouldDisableAllCards &&
!checkIsFlipped(index) &&
!checkIsInactive(card) &&
handleCardClick(index)
}
>
<div className="card-inner">
<div className="card-front">?</div>
<div className="card-back">{card.letter}</div>
</div>
</div>
))}
</div>
</div>
);
}
export default App;
React JS - few files
App
import React, { useState, useEffect, useRef, useCallback } from "react";
import GameBoard from "./GameBoard";
import "./styles.css";
// Create a list of letters for the cards
const letters = ["A", "B", "C", "D", "E", "F", "G", "H"];
// Function to shuffle the cards
const shuffleCards = (array) => {
const length = array.length;
for (let i = length; i > 0; i--) {
const randomIndex = Math.floor(Math.random() * i);
const currentIndex = i - 1;
const temp = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temp;
}
return array;
};
function App() {
const [cards, setCards] = useState(() =>
shuffleCards(
letters.concat(letters).map((letter, index) => ({ letter, id: index })),
),
);
const [openCards, setOpenCards] = useState([]);
const [clearedCards, setClearedCards] = useState({});
const [shouldDisableAllCards, setShouldDisableAllCards] = useState(false);
const [moves, setMoves] = useState(0);
const timeout = useRef(null);
// Disable all cards during the check
const disable = () => {
setShouldDisableAllCards(true);
};
// Enable all cards after the check
const enable = () => {
setShouldDisableAllCards(false);
};
// Check if all pairs are found
const checkCompletion = useCallback(() => {
if (Object.keys(clearedCards).length === letters.length) {
alert(`Congratulations! You completed the game in ${moves} moves.`);
}
}, [clearedCards, moves]);
// Evaluate if the selected cards match
const evaluate = useCallback(() => {
const [first, second] = openCards;
enable();
if (cards[first].letter === cards[second].letter) {
setClearedCards((prev) => ({ ...prev, [cards[first].letter]: true }));
setOpenCards([]);
} else {
timeout.current = setTimeout(() => {
setOpenCards([]);
}, 500);
}
}, [cards, openCards]);
// Handle card click
const handleCardClick = (index) => {
if (openCards.length === 1) {
setOpenCards((prev) => [...prev, index]);
setMoves((moves) => moves + 1);
disable();
} else {
clearTimeout(timeout.current);
setOpenCards([index]);
}
};
// Check for match when two cards are open
useEffect(() => {
if (openCards.length === 2) {
const timeout = setTimeout(evaluate, 300);
return () => clearTimeout(timeout);
}
}, [openCards, evaluate]);
// Check if game is completed
useEffect(() => {
checkCompletion();
}, [clearedCards, checkCompletion]);
// Handle restart
const handleRestart = () => {
setClearedCards({});
setOpenCards([]);
setMoves(0);
setShouldDisableAllCards(false);
setCards(
shuffleCards(
letters.concat(letters).map((letter, index) => ({ letter, id: index })),
),
);
};
// Handle reveal all cards
const handleRevealAllChange = (event) => {
if (event.target.checked) {
setClearedCards(
letters.reduce((acc, letter) => ({ ...acc, [letter]: true }), {}),
);
setMoves(0);
} else {
handleRestart();
}
};
return (
<div className="container">
<div className="controls">
<button onClick={handleRestart}>
{Object.keys(clearedCards).length === letters.length
? "Play Again"
: "Restart"}
</button>
<label>
<input type="checkbox" onChange={handleRevealAllChange} /> Reveal All
</label>
</div>
<GameBoard
cards={cards}
openCards={openCards}
clearedCards={clearedCards}
onCardClick={handleCardClick}
shouldDisableAllCards={shouldDisableAllCards}
/>
</div>
);
}
export default App;
GameBoard
import React from "react";
import Card from "./Card";
function GameBoard({
cards,
openCards,
clearedCards,
onCardClick,
shouldDisableAllCards,
}) {
const checkIsFlipped = (index) => {
return openCards.includes(index);
};
const checkIsInactive = (card) => {
return Boolean(clearedCards[card.letter]);
};
return (
<div className="game-board">
{cards.map((card, index) => (
<Card
key={card.id}
index={index}
card={card}
isFlipped={checkIsFlipped(index)}
isInactive={checkIsInactive(card)}
onClick={onCardClick}
/>
))}
</div>
);
}
export default GameBoard;
Card
import React from "react";
function Card({ card, index, isFlipped, isInactive, onClick }) {
return (
<div
className={`card ${isFlipped || isInactive ? "flipped" : ""}`}
onClick={() => !isInactive && onClick(index)}
>
<div className="card-inner">
<div className="card-front">?</div>
<div className="card-back">{card.letter}</div>
</div>
</div>
);
}
export default Card;