<div id="app"></div>
.board {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(60px, 1fr));
gap: 8px; /* space between cards */
max-width: 420px; /* optional, center on larger screens */
margin: 0 auto;
}
/* Individual card button */
.card,
.card-matched {
aspect-ratio: 1 / 1; /* keep square */
font-size: 1.25rem;
border: 1px solid #333;
border-radius: 6px;
background: #f5f5f5;
transition: transform 0.2s;
}
.card:hover:not(:disabled) {
transform: scale(1.05);
}
.card-matched {
visibility: hidden; /* hide matched cards (keeps grid gap) */
}
/*
* https://frontendeval.com/questions/memory-game
*
* Create a card matching game to test the player's memory
*/
function shuffle(array) {
return array.sort(() => Math.random() - 0.5);
}
function getInitDeck() {
const set = Array.from({ length: 18 })
.fill(0)
.map((_, i) => i);
return shuffle([...set, ...set]).map((value) => ({
value,
hidden: true,
matched: false
}));
}
const App = () => {
const [cards, setCards] = React.useState(getInitDeck());
const [first, setFirst] = React.useState(null);
const [second, setSecond] = React.useState(null);
const won = cards.every((c) => c.matched);
React.useEffect(() => {
let timeoutId;
if (first === null || second === null) return;
const isMatch = cards[first].value === cards[second].value;
if (isMatch) {
setCards((prev) =>
prev.map((c, i) =>
i === first || i === second ? { ...c, matched: true } : c
)
);
setFirst(null);
setSecond(null);
return;
} else {
timeoutId = setTimeout(() => {
setCards((prev) =>
prev.map((c, i) =>
i === first || i === second ? { ...c, hidden: true } : c
)
);
setFirst(null);
setSecond(null);
}, 3000);
}
return () => clearTimeout(timeoutId);
}, [first, second]);
function reveal(index) {
setCards((prev) =>
prev.map((c, i) => (i === index ? { ...c, hidden: false } : c))
);
}
function handleClick(index) {
// Dont reveal if
if (cards[index].matched || !cards[index].hidden || second !== null) return;
reveal(index);
first === null ? setFirst(index) : setSecond(index);
}
function startOver() {
setCards(getInitDeck());
setFirst(null);
setSecond(null);
}
return (
<div>
{won ? (
<>
<p className="win">You won 🎉</p>
<button onClick={startOver}>Start over</button>
</>
) : (
<div className="board">
{cards.map(({ value, hidden, matched }, i) => (
<button
key={i}
aria-label={hidden ? "hidden card" : String(value)}
className={`card${matched ? "-matched" : ""}`}
onClick={() => handleClick(i)}
disabled={(first && second) || matched}
>
{!hidden && !matched && value}
</button>
))}
</div>
)}
</div>
);
};
ReactDOM.render(<App />, document.getElementById("app"));
View Compiled
This Pen doesn't use any external CSS resources.