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>
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.
Do you have any questions? Feel free to post your comment below.