In this tutorial, we will learn how to create memory card game using HTML, CSS and vanilla Javascript.
HTML Flie
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Memory Game</title>
<!-- Google Fonts -->
<link
href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap"
rel="stylesheet"
/>
<!-- Stylesheet -->
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="wrapper">
<div class="stats-container">
<div id="moves-count"></div>
<div id="time"></div>
</div>
<div class="game-container"></div>
<button id="stop" class="hide">Stop Game</button>
</div>
<div class="controls-container">
<p id="result"></p>
<button id="start">Start Game</button>
</div>
<!-- Script -->
<script src="script.js"></script>
</body>
</html>
CSS File
* {
padding: 0;
margin: 0;
box-sizing: border-box;
font-family: "Poppins", sans-serif;
}
body {
background-color: #f4c531;
}
.wrapper {
box-sizing: content-box;
width: 26.87em;
padding: 2.5em 3em;
background-color: #ffffff;
position: absolute;
transform: translate(-50%, -50%);
left: 50%;
top: 50%;
border-radius: 0.6em;
box-shadow: 0 0.9em 2.8em rgba(86, 66, 0, 0.2);
}
.game-container {
position: relative;
width: 100%;
display: grid;
gap: 0.6em;
}
.stats-container {
text-align: right;
margin-bottom: 1.2em;
}
.stats-container span {
font-weight: 600;
}
.card-container {
position: relative;
width: 6.25em;
height: 6.25em;
cursor: pointer;
}
.card-before,
.card-after {
position: absolute;
border-radius: 5px;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
border: 4px solid #000000;
transition: transform 0.7s ease-out;
backface-visibility: hidden;
}
.card-before {
background-color: #f4c531;
font-size: 2.8em;
font-weight: 600;
}
.card-after {
background-color: #ffffff;
transform: rotateY(180deg);
}
.card-container.flipped .card-before {
transform: rotateY(180deg);
}
.card-container.flipped .card-after {
transform: rotateY(0deg);
}
.controls-container {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
width: 100%;
height: 100%;
background-color: #f4c531;
top: 0;
}
button {
border: none;
border-radius: 0.3em;
padding: 1em 1.5em;
cursor: pointer;
}
#stop {
font-size: 1.1em;
display: block;
margin: 1.1em auto 0 auto;
background-color: #000000;
color: #ffffff;
}
.controls-container button {
font-size: 1.3em;
box-shadow: 0 0.6em 2em rgba(86, 66, 0, 0.2);
}
.hide {
display: none;
}
#result {
text-align: center;
}
#result h2 {
font-size: 2.5em;
}
#result h4 {
font-size: 1.8em;
margin: 0.6em 0 1em 0;
}
JavaScript File:
const moves = document.getElementById("moves-count");
const timeValue = document.getElementById("time");
const startButton = document.getElementById("start");
const stopButton = document.getElementById("stop");
const gameContainer = document.querySelector(".game-container");
const result = document.getElementById("result");
const controls = document.querySelector(".controls-container");
let cards;
let interval;
let firstCard = false;
let secondCard = false;
//Items array
const items = [
{ name: "bee", image: "bee.png" },
{ name: "crocodile", image: "crocodile.png" },
{ name: "macaw", image: "macaw.png" },
{ name: "gorilla", image: "gorilla.png" },
{ name: "tiger", image: "tiger.png" },
{ name: "monkey", image: "monkey.png" },
{ name: "chameleon", image: "chameleon.png" },
{ name: "piranha", image: "piranha.png" },
{ name: "anaconda", image: "anaconda.png" },
{ name: "sloth", image: "sloth.png" },
{ name: "cockatoo", image: "cockatoo.png" },
{ name: "toucan", image: "toucan.png" },
];
//Initial Time
let seconds = 0,
minutes = 0;
//Initial moves and win count
let movesCount = 0,
winCount = 0;
//For timer
const timeGenerator = () => {
seconds += 1;
//minutes logic
if (seconds >= 60) {
minutes += 1;
seconds = 0;
}
//format time before displaying
let secondsValue = seconds < 10 ? `0${seconds}` : seconds;
let minutesValue = minutes < 10 ? `0${minutes}` : minutes;
timeValue.innerHTML = `<span>Time:</span>${minutesValue}:${secondsValue}`;
};
//For calculating moves
const movesCounter = () => {
movesCount += 1;
moves.innerHTML = `<span>Moves:</span>${movesCount}`;
};
//Pick random objects from the items array
const generateRandom = (size = 4) => {
//temporary array
let tempArray = [...items];
//initializes cardValues array
let cardValues = [];
//size should be double (4*4 matrix)/2 since pairs of objects would exist
size = (size * size) / 2;
//Random object selection
for (let i = 0; i < size; i++) {
const randomIndex = Math.floor(Math.random() * tempArray.length);
cardValues.push(tempArray[randomIndex]);
//once selected remove the object from temp array
tempArray.splice(randomIndex, 1);
}
return cardValues;
};
const matrixGenerator = (cardValues, size = 4) => {
gameContainer.innerHTML = "";
cardValues = [...cardValues, ...cardValues];
//simple shuffle
cardValues.sort(() => Math.random() - 0.5);
for (let i = 0; i < size * size; i++) {
/*
Create Cards
before => front side (contains question mark)
after => back side (contains actual image);
data-card-values is a custom attribute which stores the names of the cards to match later
*/
gameContainer.innerHTML += `
<div class="card-container" data-card-value="${cardValues[i].name}">
<div class="card-before">?</div>
<div class="card-after">
<img src="${cardValues[i].image}" class="image"/></div>
</div>
`;
}
//Grid
gameContainer.style.gridTemplateColumns = `repeat(${size},auto)`;
//Cards
cards = document.querySelectorAll(".card-container");
cards.forEach((card) => {
card.addEventListener("click", () => {
//If selected card is not matched yet then only run (i.e already matched card when clicked would be ignored)
if (!card.classList.contains("matched")) {
//flip the cliked card
card.classList.add("flipped");
//if it is the firstcard (!firstCard since firstCard is initially false)
if (!firstCard) {
//so current card will become firstCard
firstCard = card;
//current cards value becomes firstCardValue
firstCardValue = card.getAttribute("data-card-value");
} else {
//increment moves since user selected second card
movesCounter();
//secondCard and value
secondCard = card;
let secondCardValue = card.getAttribute("data-card-value");
if (firstCardValue == secondCardValue) {
//if both cards match add matched class so these cards would beignored next time
firstCard.classList.add("matched");
secondCard.classList.add("matched");
//set firstCard to false since next card would be first now
firstCard = false;
//winCount increment as user found a correct match
winCount += 1;
//check if winCount ==half of cardValues
if (winCount == Math.floor(cardValues.length / 2)) {
result.innerHTML = `<h2>You Won</h2>
<h4>Moves: ${movesCount}</h4>`;
stopGame();
}
} else {
//if the cards dont match
//flip the cards back to normal
let [tempFirst, tempSecond] = [firstCard, secondCard];
firstCard = false;
secondCard = false;
let delay = setTimeout(() => {
tempFirst.classList.remove("flipped");
tempSecond.classList.remove("flipped");
}, 900);
}
}
}
});
});
};
//Start game
startButton.addEventListener("click", () => {
movesCount = 0;
seconds = 0;
minutes = 0;
//controls amd buttons visibility
controls.classList.add("hide");
stopButton.classList.remove("hide");
startButton.classList.add("hide");
//Start timer
interval = setInterval(timeGenerator, 1000);
//initial moves
moves.innerHTML = `<span>Moves:</span> ${movesCount}`;
initializer();
});
//Stop game
stopButton.addEventListener(
"click",
(stopGame = () => {
controls.classList.remove("hide");
stopButton.classList.add("hide");
startButton.classList.remove("hide");
clearInterval(interval);
})
);
//Initialize values and func calls
const initializer = () => {
result.innerText = "";
winCount = 0;
let cardValues = generateRandom();
console.log(cardValues);
matrixGenerator(cardValues);
};
Subscribe: https://www.youtube.com/@CodingArtist/featured
#javascript #html #css #game-development #programming