IMG-LOGO

How to create a simple memory game using HTML5, CSS3, and Javascript?

andy - 14 Jan, 2018 22010 Views 10 Comment

In this tutorial, we will create a really simple memory game using HTML5, CSS3, and Javascript. Users will be able to reveal some images and memorize the location of the picture. The purpose of this game is to reveal all the same images. Here is the list of the requirements for our front-end code.

  • We will use the following external libraries which are JQuery and Animated.css (for image effect).
  • We will have a canvas image where our images will be placed. We are going to have 15 different unique images. The total images will be 30 images. Each of a unique image will be duplicated. That's why 15 x 2 = 30 images.
  • We will have statistic game which will display a number of image clicks and number of correct guesses.
  • When creating our game, we will consider the following screen, if a user using a mobile to view our game, it will be automatically resized accordingly. The size of our game width will cover: over 640px, between 321px and 640px and max-width of 320px

Here is the list of our game logic.

  • In our game canvas, there will be two layers available, the first layer will contain our images and while the top layer will have question mark images to cover our images so they are not displayed or revealed.
  • We are going to create an array of 30 images slots which will contain our 15 unique images and a copy of another 15 duplicated images. Once created, we will shuffle the 30 images, so every time the game is loaded, they are randomly placed.
  • Users will be able to reveal the image by clicking the question mark image cover. Users can click two images at a time. If both images do not match, then they will be closed again otherwise if they do match, they will stay open. We will use animated.css effect to flip the image open and close.
  • We will have statistic game which will display a number of image clicks and number of correct guesses.
  • When creating our game, we will consider the following screen, if a user views the game using a mobile, it will be automatically resized accordingly by using css3 media. The size of our game width will cover: over 640px, between 321px and 640px and max-width of 320px
  • When users have revealed all images, we will then display a message box to congrats users. Users will be able to restart the game again. By default this message will be hidden.

Here is the full code of the HTML5 file.

<!doctype html>
<html>
<head>
<title>How to create HTML5 Memory Game - Bytutorial.com</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.5.2/animate.min.css" type="text/css" rel="stylesheet"/>
<link href="game.css" type="text/css" rel="stylesheet"/>
</head>
<body>
<div id="canvas-game">
	<div id="game-content"></div>
</div>
<div id="game-statistic">
	<div id="statistic-left">No of Clicks: <span id="no-of-clicks" class="bold-text">0</span></div>
	<div id="statistic-right">Correct Guess: <span id="correct-guess" class="bold-text">0</span></div>
	<div class="clear"></div>
</div>
<div id="game-message">
	<div class="congrats-message">Congratulations, you have revealed all the images ;-)</div>
	<button id="btnRestart" type="button">Restart</button>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="game-logic.js"></script>

</body>
</html>
Things to remember when to load your javascript external files, always remember to place them at the end of the body. This will enhance the speed of your page load as the scripts files will be loaded at the very end. Just remember to make sure the script to be executed when the page has been fully loaded.

From the above code you can see that we have divided the game structure into 3 sections. They are:

  • canvas-game div
    This section will load the images and cover images content.
  • game-statistic div
    This section will display the statistic of number of clicks and the number of correct guess.
  • game-message div
    This section will display the game message to congrats users when the game is over. Users will be able to restart the game by clicking the button Restart. By default this section will be hidden using CSS.

CSS3 Fulle Code.

html,body{
	margin:0;
	padding:0;
	text-align:center;
}

#canvas-game{
	margin:0 auto;
	text-align:left;
	width:612px;
	margin-top:20px;
	border:Solid 1px #ebe5e5;
	height:385px;
	position:relative;
}

#game-statistic{
	margin:0 auto;
	text-align:left;
	width:612px;
	margin-top:20px;
}


#statistic-left, #statistic-right{
	font-style:italic;
	font-size:12px;
	float:left;
}

#statistic-right{
	float:right;
}

.bold-text{
	font-weight:bold;
}

.clear{
	clear:both;
}

.box-picture{
	float:left;
	width:100px;
	height:75px;
	border:solid 1px #ebe5e5;
        display:none;
}

.box-picture > img{
	width:100px;
	height:75px;
}

.box-cover-wrapper{
	position:absolute;
	left:0;
	top:0;
	z-index:100;
}

.box-cover{
	width:100px;
	height:75px;
	background:url(game-images/image-cover.jpg) no-repeat;
	border:solid 1px #ebe5e5;
	float:left;
	cursor:pointer;
}

#game-message{
	margin:0 auto;
	font-size:20px;
	background:#f7f5b5;
	padding:15px;
	border:solid 1px #ccc;
	border-radius:5px;
	width:612px;
	display:none;
}

#btnRestart{
	padding:15px;
	font-weight:bold;
	text-align:center;
	background:#043255;
	text-decoration:uppercase;
	margin-top:20px;
	color:#fff;
	border:none;
	border-radius:5px;
	cursor:pointer;
}

/**** IF SCREEN SIZE IS NO LARGER THAN 640px ****/
@media (max-width: 640px) {
	#canvas-game{
		width:385px;
		height:348px;
	}
	
	.box-picture, .box-picture > img, .box-cover{
		width:75px;
		height:56px;
	}
	
	.box-cover{
		background:url(game-images/image-cover-75.jpg) no-repeat;
	}
	
	#game-statistic{
		margin-top:20px;
	}
	
	#game-message, #game-statistic{
		width:385px;
	}
	
}

/**** IF SCREEN SIZE IS NO LARGER THAN 320px ****/
@media (max-width: 320px) {
	#canvas-game{
		width:156px;
		height:390px;
	}
	
	.box-picture, .box-picture > img, .box-cover{
		width:50px;
		height:38px;
	}
	
	.box-cover{
		background:url(game-images/image-cover-50.jpg) no-repeat;
	}
	
	#game-statistic{
		margin-top:20px;
	}
	
	#game-message, #game-statistic{
		width:156px;
	}
	
	#statistic-left, #statistic-right{
		float:none;
	}
}

If you see above code, you will notice I have included 3 styles of the game if the game is viewed using different screen size. I will explain some of the CSS codes that I think is important and what they do for the game.

html,body{
	margin:0;
	padding:0;
	text-align:center;
}

Based on the above code, we want to make sure there is no padding or margin applied on the default html5 document body. The text-align: center is used to center all the body content in the center.

#canvas-game{
	margin:0 auto;
	text-align:left;
	width:612px;
	margin-top:20px;
	border:Solid 1px #ebe5e5;
	height:385px;
	position:relative;
}

In the canvas game, we want to set the margin position to 0 auto, this will ensure the canvas game is positioned centrally and by applying text-align:left, this will make sure inside the content of the canvas-game will be aligned to left. You also notice I include the position:relative to canvas-game this is because when displaying the cover images layer, I want it to be displayed in the exact same location as the images layer. The cover images layer will be positioned as absolute against this layer. You can see the following CSS code, I use the position left and top to 0 positions and with z-index property to 100, it will make it sits nicely on top of this layer. You can check more details about z-index by clicking this tutorial link.

.box-cover-wrapper{
	position:absolute;
	left:0;
	top:0;
	z-index:100;
}

For the images and image wrapper, it will be sized to 100px x 75px and with layout images slots as 6 images in each row and up to 5 rows which in total will be 30 image slots. That's when you see our game-canvas width it will be 612px, this number comes from: 6 x 100px which end up to 600px. The 12px comes from the border size of each picture, each side on left and right hand side will have 1px which end up 6 x 2px for each picture which is 12px. If we sum up the total it will be 600px + 12px. For the height, the logic of calculating the height will be the same as calculating the width.

.box-picture{
	float:left;
	width:100px;
	height:75px;
	border:solid 1px #ebe5e5;
}

.box-picture > img{
	width:100px;
	height:75px;
}

For the game message, by default this section will be hidden, this can be done by setting the display css to none.

#game-message{
	margin:0 auto;
	font-size:20px;
	background:#f7f5b5;
	padding:15px;
	border:solid 1px #ccc;
	border-radius:5px;
	width:612px;
	display:none;
}

For adjusting the game screen, you can see I have included 2 extra media queries for a size up to 320px and 640px. This can be identified by specifying the following CSS.

@media (max-width: 640px) {....}
@media (max-width: 320px) {....}

The last part of the code will be the logic itself which is the javascript.

Javascript Fulle Code.

//variables
var noOfBoxGame = 30;
var boxIndexes = [];
var noOfClick = 0;
var clickCounter = 0;
var correctGuess = 0;
var clickImages = [];
var timeOutRestore = 1000;

//page load
$(function(){
	//render the game
	bytutorialHTML5Game.renderGameLayout();
	
	$("#btnRestart").on("click", function(){
		bytutorialHTML5Game.renderGameLayout();
	});
});

//game class
bytutorialHTML5Game = {
	
	//This will load the default game array and perform a shiffle
	initData: function(){
		for(var x=0;x<=1;x++){for(var i=0; i<= (noOfBoxGame/2)-1;i++){boxIndexes.push(i);}}
		this.shuffleArray(boxIndexes);
	},
	
	//function to shuffle array
	shuffleArray: function(array){
		for (var i = array.length - 1; i > 0; i--) {
			var j = Math.floor(Math.random() * (i + 1));
			var temp = array[i];
			array[i] = array[j];
			array[j] = temp;
		}
	},
	
	
	buildGameBox: function(){
		var boxes = "";
		var boxCover = "";
		
		//load the images and image cover
		for(var i = 1; i <= noOfBoxGame; i++){
			boxes += "<div id='box-" + i + "' class='box-picture'><img src='game-images/" + (parseInt(boxIndexes[i-1]) + 1) + ".jpg'/></div>";
			boxCover += "<div id='box-cover-" + i + "' class='box-cover' data-id='" + (parseInt(boxIndexes[i-1]) + 1) + "'></div>";
		}
		boxCover = "<div class='box-cover-wrapper'>" + boxCover + "</div>";
		$("#game-content").html(boxes + boxCover);
		$(".box-picture").show();
		//add event to click the box cover image
		$(".box-cover").off("click");
		$(".box-cover").on("click", function(){
			if(noOfClick <= 1){
				clickCounter++;
				$("#no-of-clicks").html(clickCounter);
				
				noOfClick++;
				$(this).addClass('animated flipOutX'); 
				
				var clickCover = {
					ImageID: $(this).attr("data-id"),
					CoverID: $(this).attr("id").replace("box-cover-","")
				}
				clickImages.push(clickCover);
				
				if(noOfClick >= 2){
					//check if the revealed images are correct
					if(clickImages[0].ImageID == clickImages[1].ImageID && clickImages[0].CoverID !== clickImages[1].CoverID){
						correctGuess++;
						$("#correct-guess").html(correctGuess);
						
						//reset the variables
						noOfClick = 0;
						clickImages = [];
						
						//if the game is completed then perform a reset
						if(correctGuess >= (noOfBoxGame/2)){
							$("#canvas-game, #game-statistic").fadeOut(1000); 
							$("#game-message").addClass('animated bounceInDown').css('animation-delay', '1s').show(); 
							correctGuess = 0;
							$("#correct-guess").html(correctGuess);
							clickCounter = 0;
							$("#no-of-clicks").html(clickCounter);
						}
					}else{
						//if not the same then close the image cover again.
						setTimeout(function(){
							clickImages.forEach(function(item, index){
								$("#box-cover-" + item.CoverID).removeClass("flipOutX").addClass('animated flipInX'); 
							});
							//reset
							noOfClick = 0;
							clickImages = [];
						}, timeOutRestore);
					}
					
					
				}
			}
		});
	},
	
	//function to call main functions to render the game
	renderGameLayout: function(){
		$("#game-message").hide();
		$("#canvas-game, #game-statistic").show();
		this.initData();
		this.buildGameBox();
	}
}

Let me explain of what the following codes do.

initData: function(){
	for(var x=0;x<=1;x++){for(var i=0; i<= (noOfBoxGame/2)-1;i++){boxIndexes.push(i);}}
	this.shuffleArray(boxIndexes);
}

The above code is pretty simple to understand, it basically perform two loops one loop is to fill in 15 numbers (starting from 0) and a copy of the duplicated 15 numbers. This array is used to represent the images name. Once the array has been filled in, we then called a shuffleArray function to shuffle the content of the array.

var boxes = "";
var boxCover = "";
		
//load the images and image cover
for(var i = 1; i <= noOfBoxGame; i++){
	boxes += "<div id='box-" + i + "' class='box-picture'><img src='game-images/" + (parseInt(boxIndexes[i-1]) + 1) + ".jpg'/></div>";
	boxCover += "<div id='box-cover-" + i + "' class='box-cover' data-id='" + (parseInt(boxIndexes[i-1]) + 1) + "'></div>";
}
boxCover = "<div class='box-cover-wrapper'>" + boxCover + "</div>";
$("#game-content").html(boxes + boxCover);

//add event to click the box cover image
$(".box-cover").off("click");
$(".box-cover").on("click", function(){
	if(noOfClick <= 1){
		clickCounter++;
		$("#no-of-clicks").html(clickCounter);
		
		noOfClick++;
		$(this).addClass('animated flipOutX'); 
		
		var clickCover = {
			ImageID: $(this).attr("data-id"),
			CoverID: $(this).attr("id").replace("box-cover-","")
		}
		clickImages.push(clickCover);
		
		if(noOfClick >= 2){
			//check if the revealed images are correct
			if(clickImages[0].ImageID == clickImages[1].ImageID && clickImages[0].CoverID !== clickImages[1].CoverID){
				correctGuess++;
				$("#correct-guess").html(correctGuess);
				
				//reset the variables
				noOfClick = 0;
				clickImages = [];
				
				//if the game is completed then perform a reset
				if(correctGuess >= (noOfBoxGame/2)){
					$("#canvas-game, #game-statistic").fadeOut(1000); 
					$("#game-message").addClass('animated bounceInDown').css('animation-delay', '1s').show(); 
					correctGuess = 0;
					$("#correct-guess").html(correctGuess);
					clickCounter = 0;
					$("#no-of-clicks").html(clickCounter);
				}
			}else{
				//if not the same then close the image cover again.
				setTimeout(function(){
					clickImages.forEach(function(item, index){
						$("#box-cover-" + item.CoverID).removeClass("flipOutX").addClass('animated flipInX'); 
					});
					//reset
					noOfClick = 0;
					clickImages = [];
				}, timeOutRestore);
			}
			
			
		}
	}
});

The above code will insert images into a div tag repeated at 30 times. It also inserts image cover (question image) which will sit nicely at top of the image. We then add a click event to the box-cover. So when users click this cover image, it will reveal the behind image. I have included the comments into the code, the idea is users can only reveal two images at a time, if they are matched, they will be kept open, otherwise, it will be flipped and closed. Once it reveals all the images, it will then display a congrats message and users will be able to restart the game to play again.

Demo of Memory Game

If you want to see the Memory Game in action, you can click this link.

Demo Files

You can download the sample code below.

Download

Do you have any questions? Feel free to post your comment below.

Comments

Dirk
05 Dec, 2018
Hi, Thank you for this nice game. I want to use this for a little website for people with intellectual disability. It works well but the problem with some of them with motoric problems is that they click twice very fast so the script thinks he or she had clicked on two different pictures. What can I do to avoid this? Best regards Dirk
Dirk
06 Dec, 2018
Hi About my question: I found a solution: if (clickImages[0].ImageID == clickImages[1].ImageID && clickImages[0].CoverID !== clickImages[1].CoverID) {.... Best Regards Dirk D
andy
06 Dec, 2018
That is good ;-)
Pushparaj
16 Feb, 2019
It breaks if i double click on the open card. It remains open for the rest of the game.
andy
17 Feb, 2019
Hi Pushparaj, Please use the solution provided by Dirk. In the codes on line 88. Use the following condition. if(clickImages[0].ImageID == clickImages[1].ImageID && clickImages[0].CoverID !== clickImages[1].CoverID){}
sonia
25 Apr, 2019
how can i insert images , please provide the link
andy
27 Apr, 2019
Hi Sonia, the images are inserted via Javascript. Please see the function buildGameBox. In there, I perform a for loop and insert the images path. Change this logic if you want to insert another images or add extra images.
Jenny
25 Oct, 2019
Could you provide a more detailed description about how to insert images or how they should be named and saved on the computer please. Thank you for the great code.
andy
25 Oct, 2019
Hi Jenny, To help you out. I have included the code. You can download it. Basically the images are stored under a folder called game-images. While for the image names, we have to use numbers. There are 15 images that represent 30 boxes of images. So if you need 60 boxes, you have to create up to 30 images. Let me know if you need further help.
toby swale
24 Mar, 2020
How do i change the pictures so i can make it what i wanr?
Write Comment
0 characters entered. Maximum characters allowed are 1000 characters.

Related Articles