The memory game is a pretty interesting game, and has been around for a while. You are given a set of cards containing images which are turned to the back, when you click on an image, it flips over and reveals the image. If you click on two images that are exactly the same, that is a win for you.
I would like to walk you through how to build a memory game using plain HTML, CSS and JavaScript.
Here is what we will be doing :
- Setting up the basic HTML and CSS
- Defining the variables and data in JS
- Setting up the board on DOM load in JS
- Check if the cards clicked is correct
- Restart the game
Setting up the basic HTML and CSS
The HTML and CSS for this project is pretty simply. We are creating the skeleton which will house data from the JS.
HTML
<div class="container">
<div class="data">
<div class="score">Score : <span class="scoreBoard">0</span></div>
<div class="click">Click : <span class="clickBoard">0</span></div>
</div>
<div class="grid">
<div class="popup">
<button class="playAgain">play Again</button>
</div>
</div>
</div>
From above, we have a div with a class score and click that will be populated via JS, a grid class that will house the images which is the game board and a popup class with a button to easily renew the game.
CSS
.grid {
width: 90%;
height: 300px;
display: flex;
flex-wrap: wrap;
margin: 60px auto;
}
.grid img {
margin: 2px;
transform-style: preserve-3d;
transition: transform 0.5s;
}
.grid img.flip {
transform: rotateY(180deg);
}
.popup {
background: red;
width: 200px;
height: 200px;
z-index:20;
position: fixed;
top: 100px;
left: 100px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.data {
display: flex;
justify-content: space-between;
padding: 20px 30px;
font-size: 23px;
}
.data span {
color: rgb(206, 121, 11);
font-size: 30px;
}
From here, we are setting up the grid (gameboard) and the grid img styling. The grid img has the transform-style and the transition property which allows the card to flip when the flip class is added to it(i.e via JS) and the popup is styled as a fixed div.
Defining the variables and data in JS
Most of the heavy lifting of this game will be carried out in the JS file, the board will be populated etc
let cardArray = [
{ name: "fries", img: "fries.png", },
{ name: "fries", img: "fries.png", },
{ name: "pizza", img: "pizza.png", },
{ name: "pizza", img: "pizza.png", },
{ name: "milkshake", img: "milkshake.png", },
{ name: "milkshake", img: "milkshake.png", },
{ name: "ice-cream", img: "ice-cream.png", },
{ name: "ice-cream", img: "ice-cream.png", },
{ name: "hotdog", img: "hotdog.png", },
{ name: "hotdog", img: "hotdog.png", },
{ name: "cheeseburger", img: "cheeseburger.png", },
{ name: "cheeseburger", img: "cheeseburger.png", },
];
//define variables and get DOM element
let grid = document.querySelector(".grid");
let scoreBoard = document.querySelector(".scoreBoard");
let popup = document.querySelector(".popup");
let playAgain = document.querySelector(".playAgain");
let clickBoard = document.querySelector(".clickBoard");
let imgs;
let cardsId = [];
let cardsSelected = [];
let cardsWon = 0;
let clicks = 0;
So we have a cardArray, which is just a list of objects containing images and their names. The names will be used when we want to compare two images that were clicked. You will notice each object is actually double, and that is because you will be trying to compare two images on the board.
The grid, scoreboard, popup, playAgain and clickBoard are elements from the HTML that we are getting into the JS and will inject data into them.
imgs is a variable that we will initialize from here and will hold the images created, cardsId and cardsSelected are arrays that will contain the cards clicked on. While cardsWon and clicks will record the wins and no of clicks respectively
Setting up the board on DOM load in JS
This will be done with an eventListener called DOMContentLoaded, which will be added to the document itself. The eventListener works immediately the DOM loads (hence the name).
This eventListener will house some functions that will start the game
document.addEventListener("DOMContentLoaded", function () {
//define functions
createBoard(grid, cardArray);
arrangeCard();
playAgain.addEventListener("click", replay);
//add a click function for images
imgs = document.querySelectorAll("img");
Array.from(imgs).forEach(img =>
img.addEventListener("click", flipCard)
)
});
I called a function here (createBoard) that will create the board with the images and all, I scattered the images for difficulty (arrangeCard), and I added an eventListener for each image to watch out for clicks (flipCard)
//createBoard function
function createBoard(grid, array) {
popup.style.display = "none";
array.forEach((arr, index) => {
let img = document.createElement("img");
img.setAttribute("src", "blank.png");
img.setAttribute("data-id", index);
grid.appendChild(img);
})
}
// arrangeCard function
function arrangeCard() {
cardArray.sort(() => 0.5 - Math.random())
}
// flip Card function
function flipCard() {
let selected = this.dataset.id;
cardsSelected.push(cardArray[selected].name);
cardsId.push(selected);
this.classList.add("flip");
this.setAttribute("src", cardArray[selected].img);
if (cardsId.length === 2) {
setTimeout(checkForMatch, 500);
}
}
The createBoard function removes the popup, loops over the image array with forEach, this receives two arguments. Each object in the array and the index(optional) which is the position of that object in the array and starts from zero. For each object, simply create an image element, set the src attribute to display the image and add a data attribute (data attribute are simply objects that holds values in the html5 which you can use in JS via dataset). The value of the id will be the index i.e from 0 etc.
The flipCard function looks out for click. Once an image is clicked, it gets the id (remember the attribute we set above data-id, we can access it via dataset.id or if it was data-name it would be dataset.name). The this keyword basically tells it to select the image that was clicked. So we get the id of the image clicked to a variable selected, we then use this variable to get the object clicked and then push the name property of that object into the cardsSelected array. The id of the image clicked will also be pushed into the cardsId array. We add a class of flip to the img clicked to create a sort of flipping effect and then change the image to reveal the image beneath.
This process will be repeated when the second image is clicked and then we will check to see if they are the same image
Check if the cards clicked is correct
Once two images has been clicked, we wait for about .5s (to give a nice user experience ), and then we call the checkForMatch function
// checkForMatch function
function checkForMatch() {
let imgs = document.querySelectorAll("img");
let firstCard = cardsId[0];
let secondCard = cardsId[1];
if (cardsSelected[0] === cardsSelected[1] && firstCard !== secondCard) {
alert("you have found a match");
cardsWon += 1;
scoreBoard.innerHTML = cardsWon;
setTimeout(checkWon,500)
} else {
imgs[firstCard].setAttribute("src", "blank.png");
imgs[secondCard].setAttribute("src", "blank.png"); alert("wrong, please try again"); imgs[firstCard].classList.remove("flip"); imgs[secondCard].classList.remove("flip");
}
cardsSelected = [];
cardsId = [];
clicks += 1;
clickBoard.innerHTML = clicks;
}
function checkWon() {
if (cardsWon == cardArray.length / 2) {
alert("You won")
setTimeout(()=> popup.style.display = "flex" ,300);
}
}
The checkForMatch function first gets all the images on the board, then gets the Ids of the images clicked from the "cardsId" array ( This will allow us easily get access to their object). We will then checked if the first value in cardsArray is equal to the second and if it is not the same image that was clicked twice.
If the images are the same we tell the person they got it and adds to the number of cardsWon, and then check if all the cards have been selected with the gameWon function. If the images are not the same (else), we simply remove the flip Class and change the image back. After the if/else statement, we want to clear the arrays for the next time the user clicks on an image and add to the number of clicks
The checkWon function simply check if the value of cardsWon is equal to the length of the card divided by 2. If it is tell the person the game is over and display the popup replay button
Restart the game
The restart game function is a simply function that clears the game board and scoreboard and prepares for another game. This is the replay function.
// The replay function
function replay() {
arrangeCard();
grid.innerHTML = "";
createBoard(grid, cardArray);
cardsWon = 0;
clicks = 0;
clickBoard.innerHTML = 0;
scoreBoard.innerHTML = 0;
popup.style.display = "none";
}
It basically arranges the cardsArray again, empties the gameBoard (grid), create a new board and resets the scores.
And that is how you create a memory game. Here is a codepen link for the finished project.
Like the popular saying in programming, there is a thousand ways to do one thing, so please try creating your memory game and if your method is different let me know in the comments.
Thank you
You can follow me on twitter @fakoredeDami
Top comments (16)
Hello :) Thank you for posting a nice solution to this thrilling game. I want to use it on my website but I want to use my custom pictures. I copied the code from codepen but it doesn't work on the desktop. I put all the files in one folder and linked them together. Then I replaced your pictures with mine and placed them in the same folder. Still doesn't work. Do you know what can be the problem?
Hi, thank you for going through it, I really appreciate it.
If you want to use this project locally, the only issue that s suppose to occur is with the images. I am using a lot of images in the imgArray, so make sure you point all of them to your local images.
Also I have a blank image that I am using in like three places (createBoard function and checkMatch function). Make sure you point those also to your local images i.e blank.jpg.
Hope that answers your question.
Thank you.
thank you so much for your reply, I solved that problem, it's working with my own images now after I extracted zip file from Codepen (probably I linked sth wrong myself first ;) Now I'm struggling with another problem. I want to play sound each time we click pair of cards. I'm making a game for my students to learn English.
I don't know how to select the specific card from the array and put it into variable. Query selector won't work, document by ID either, maybe sth similar to this: function playSound() {
let selected = this.dataset.id;
cardsSelected.push(cardArray[selected].name => should I write the name of the card here?); every help much appreciated.
that's the address of my website where I placed it:
english-quiz.cba.pl/Memory%20game/...
Sorry for the late reply,
I have updated my codepen link and added sounds like you said. So if two different images are selected, an error sound, and if they are the same picture, a success sound. I am not really good with the audio api, but I hope that is what you want, tell me if you want any change.
It works like this, when you click on two images and it checks if they are the same or not, it sets the src of the audio source and loads it before playing it. Note that the loading is very important,
Thank you
Hi, thank you so much for your help! it really works! I will include it on my website. It's a little different than what I wanted but also nice. I want to include voice of a lector who will say the correct pronunciation of each card, eg. when they click "trousers" it should say: "trousers, etc. I will think about it. I'm looking for ways to access objects from the array with pictures (cardArray) and make a new variable, eg. var trousers = cardArray[0] and later function: playSound() case: trousers =>"trousers.mp3". Sth like that. I will work on it meantime. Thanks a lot!
Ok I think I have worked on it , the names you will use in each object will be basically the same name has the mp3 like you said.
So what I did was, everytime a card is clicked the name is pushed to an array, I got the name and I appended .mp3 to it and played it.
You can check the link again, but note that mine won't work since I dont have a fries.mp3 :).
And if your music will be in a different folder, you can do this.
source.src=
./music/data/${cardsSelected[1]}.mp3
you can use the first or second value since they are basically the same name
Thank you so much. It's really kind of you to work on this problem for me. (it's not so common recently).
I will try your code and see how it works ;) All the best for you and good luck in your programming career!
No problem
I am glad I helped
and thank you
for me is better if I declare sound variable eg. let trousers = new Audio("./trousers.mp3") and later addEvent listener to a picture. Function playSound {trousers.play();}. it words well but how can I let the program know which condition has to be met to play sound. Because if I add event listener to grid or to img he plays sound for all of the pictures in the grid.
But how to choose these specific pictures? We need to label these picture somehow that if they are clicked sound will be played. Can you give me a hint cause I am quite frustrated with this problem and your first solution only play sound if 2 cards are picked either match or wrong. But what I meant is to play sound when every single card is clicked. And sound should be different depending on different card. For trousers => trousers.mp3 for skirt => skirt mp3, etc.
Good day,
like I said earlier, I am not really familiar with the audio api, so I cannot really tweak your method. So this is what I came up with.
Since everytime a card is clicked, we get the dataset-id and then use this to get the name from the array, you can easily play the sound from there
let clicked =cardArray[selected].name
cardsSelected.push(clicked);
source.src=
${clicked}.mp3
audio.load()
audio.play()
I also updated it in the link.
But for your method, you can try
let clicked =cardArray[selected].name
cardsSelected.push(clicked);
let sound =new Audio(
./${clicked}.mp3
)function playSound(sound){
sound.play()
}
playSound(sound).
This will be called each time a card is clicked, you can just simply interchange it with the one above. I commented this out in the codepen link.
hello bro ;) I did it
check my game out here:
english-quiz.cba.pl/Memory%20game/...
thanks for all your help
Whoa, I love this.
Well done :) :)
hello sir can you please help me i added audio in my game but 'm facing some issue with the audio
Hi there, I made my own Halloween version of this, but I noticed a problem. When you press the replay button to play again, clicking on the images doesn't do anything. I can't figure out what the problem is.
Ever solved the issue?
hello can you share the code with me