Open-Source Internship opportunity by OpenGenus for programmers. Apply now.
In this article at OpenGenus, we will look into implementing a memory game using JS and HTML.
Contents
- Game and the rules.
- Design Overview and Approach.
- Folder structure.
- Implementation
- index.html
- style.css
- script.js
- GamePlay.
Game and the rules
Memory game is a fun way to pass the time. In this, the player chooses 2 random tiles or cards depending on the way the game is implemented. A player has to match 2 cards and if he can successfully match all the cards, he will win.
Rules:
- The player can only two cards in a given move.The selection is done in such a way that, after one card is selected he can choose only one from the rest, even if he tries to select others it won't function.
- If the two selected cards have the same color or they are similar then, it's a match and either the cards will be disabled.
- If the two selected cards don't match then they will flip back to their original states after a second.
- The player wins after he matches all the cards.
- The player cannot double click on the same tile or card.
Design overview and approach
To design this game we are going to need responsive site for the clicks that we are going to make on the tiles or cards. We will be using HTML, CSS and Javascript. HTML is hypertext markup language which is the default language used to make documents that are displayed on the browsers. CSS is cascading style sheets which is used to beautify the documents which is containg the information to be displayed on the browser. JS is the most powerful one of them all, using javascript we can create responsive websites.
In this project we would be adding and remove the needed tiles using DOM manipulation using JS. DOM stands for document object model and all the tags or the elements are part of this, in DOM every element is part of a tree structure where the root node is the html tag. Using DOM methods we can perform actions on the elements i.e addition or removal of values to it.
We are going to approach this by creating the needed files for implementation. Every website has an index.html page it's the default page or also called as the landing page or homepage, which the user visits. When hosting sites on a server, they usually pickup the file name 'index.html' as it's the standard practice. We can actually set any file as our main page or homepage with some additonal configuration. To beautify the index.html page we are using an external css file. The code necessary for the responsive stuff goes in the script.js file.
Folder structure
mkdir memory_game
touch index.html, style.css, script.js
We are going to make a folder or package which are going to contain html,css and Js files.
- memory_game
|
|_ index.html
|_ style.css
|_ script.js
Implementation
The index.html contains the basic template which will assist browser to parse the page for the necessary information and present it. The contents of the file are as follows.
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="author" content="Sathya">
<title> Memory Game</title>
<link rel="stylesheet" href="styles.css">
<script src="script.js" defer></script>
</head>
<body>
<h1 class="name">MEMORY GAME!</h1>
<div class="tileContainer">
</div>
</body>
</html>
Every HTML document starts with the html tag and the doctype declaration. Our html document has two parts. One, the head tag and other the body. The head tag contains all the metadata which is enclosed in the opening and the closing tags. The metadata is the data about data, in our case it is the information about the html document. It describes the author, title, characterset, styles and scripts that we are using in association with this document. meta tag is used to describe the metadata. The meta tag contains name and content attributes.
The body tag is the place where all the main contents of the page goes. Think of it as a canvas or a draw area. Its the part that we see on the screen. We are creating a heading tag and a div. The div is to hold all the tiles which are part of the game.
style.css
The style.css is the external css which beautifies the page which is displayed on the browser. To establish a connection between the html page and the css file we use link tag. The contents of css are as follows.
*{
margin:0;
padding:0;
box-sizing: border-box;
}
We are setting the entire body margin and padding to 0 and maintaining a consistent box-sizing which is border-box. We are using box-sizing property because, in the olden days width of an element box was equal to the width of the element, padding of the element and the border. But when we are using responsive designs we need the element's box to stay consistent when we want to experiment. Default value of the box-sizing is content-box which doesn't include the padding and the border of the element into consideration and whenever they are added the element's size increases. Whereas using border-box we are simplifying things easily by also including the padding and border to the width and heights of the elements.
body{
background-color: black;
}
.tileContainer {
margin: 200px auto;
width:max-content;
display:grid;
grid-template-columns: repeat(4,100px);
gap: 20px;
}
.tile{
height: 100px;
/* background-color: white;
border: 3px solid grey; */
cursor: pointer;
background: #fff;
border-radius: 10px;
box-shadow: -5px -5px 5px 4px rgba(255, 253, 253, 0.4);
}
#transparent{
opacity:0;
background-color: transparent;
}
.name{
width:40%;
color:azure;
font-size: 60px;
margin: auto;
text-align: center;
}
We have created classes tileContainer, tile, name and gameover. The names are pretty self explanatory of where they are used. The name class is used in the div where 'memory game' is written. We have used grid to place the tiles in the tileContainer. To create an effect, we have shaded the tile.
.popup {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
}
.popup-content {
background-color: #fff;
padding: 20px;
border-radius: 5px;
max-width: 100%;
text-align: center;
}
.popup-close {
position: absolute;
top: 40%;
right: 40%;
font-size: 3em;
cursor: pointer;
color:red;
}
We have used a popup which open up at the start of the game with rules. The popup, the content and the close button all need css. We have set the width and height of the popup, the display is set to flex to better align the box at the center with ease. The popup content is set with different properties like background color, padding etc to give a better look. The close button is given red color. The other attributes are self explanatory. The position fixed and absolute are used to set the element properly, using fixed the element sticks on the screen. Using absolute we can adjust the element with respect to the root element.
@keyframes gameove{
0% {color:black; background-color: white; }
25% {color:black; background-color: white; }
50% {position:absolute; top:10%; left:30%; animation-timing-function: ease-out;}
}
.gameover{
width:40%;
color:azure;
font-size: 60px;
margin: auto;
text-align: center;
animation-name:gameove;
animation-iteration-count: 2;
animation-duration: 10s;
}
To create the animation we are using the rule keyframes which gives us the steps which we are going to take when the animation progresses. There is an animation which gets executed after all the tiles are matched.
script.js
const tileContainer= document.querySelector(".tileContainer");
const popup = document.createElement('div');
popup.className = 'popup';
const popupContent = document.createElement('div');
popupContent.className = 'popup-content';
popupContent.innerHTML=
`
<p> Rules: <p>
<br>
<ol>
<li> Match the cards before 30 moves or 5 minutes.</li>
<li> After the close button is hit the time starts to tick. </li>
<li> The page will auto reload when game is over. </li>
<li> You can't choose other tiles nor can you match self.</li>
<li> After wrong matching, wait for a second to match again. </li>
</ol>
`;
const closeButton = document.createElement('span');
closeButton.className = 'popup-close';
closeButton.innerHTML = '×'
popupContent.appendChild(closeButton);
closeButton.addEventListener('click', () => {
document.body.removeChild(popup);
start()
});
popup.appendChild(popupContent);
document.body.appendChild(popup);
const Name=document.createElement('h1');
Name.classList.add('name');
Name.style.fontSize='20px';
Name.style.marginTop='4em'
tileContainer.insertAdjacentElement('beforebegin', Name);
In the above code we are using dom methods to get the div with the class tileCOntainer. We are creating a popup using DOM method createElement. The popup contains popupcontent and also the closepopup. The popupcontent has rules and closepopup is used to close the popup to run the game. Event listeners respond to the action made by the user and executes a function based off it.
console.log(tileContainer)
// const items=[];
const colors = ["aqua", "aquamarine", "crimson", "blue", "dodgerblue", "gold", "greenyellow", "teal"];
const pickList=[...colors,...colors];
// const pickList=[...items,...items];
const tileCount=pickList.length;
colors is the array which contains all the necessary colors for the tiles. The picklist contains colors which are repeated. The tile count is also calculated.
// Game state
let matchedCount=0;
let activeTile=null;
let flipped= false;
// build up tiles
console.log(pickList)
matchedcount variable gives the number of cards that have been matched until now. The activeTile is used to get the first selected tile. It acts as the helping variable in comparision later. flipped is used to check if the tiles are flipped or not.
function buildTile(color)
{
const tileElement=document.createElement("div");
tileElement.classList.add("tile");
tileElement.setAttribute('data-color',color);
tileElement.addEventListener('click', ()=>{
if(flipped)
{
return;
}
tileElement.style.backgroundColor= color;
if(activeTile==tileElement)
{
return;
}
if(!activeTile)
{
activeTile=tileElement;
return;
}
if( c == 30|| m == 5 )
{
const tiles=document.querySelectorAll('.tile');
tiles.forEach((tile)=>{
tile.remove();
})
clearInterval(interval);
c=0;
const gameover=document.querySelector('.name');
gameover.innerHTML= `<h3>You Lost<h3> <br> <p> Try again</p> `;
gameover.classList.remove('name');
gameover.classList.add('lost');
setTimeout(() => {
location.reload()
}, 10000);
}
colorToMatch=activeTile.getAttribute('data-color');
console.log(colorToMatch,color);
if(colorToMatch==color)
{
activeTile.classList.remove('tile');
activeTile.setAttribute('id','transparent');
tileElement.classList.remove('tile');
tileElement.setAttribute('id','transparent');
matchedCount+=2;
if(matchedCount==tileCount)
{
// alert('All Matched, refresh to start again.');
const tiles=document.querySelectorAll('#transparent');
tiles.forEach((tile)=>{
tile.remove();
})
// const gameover=document.createElement('div');
const gameover=document.querySelector('.name');
gameover.innerHTML='GAME OVER!!';
gameover.classList.remove('name');
gameover.classList.add('gameover');
setTimeout(() => {
location.reload()
}, 10000);
}
}
flipped=true;
console.log(activeTile, tileElement)
setTimeout(()=>{
tileElement.style.backgroundColor=null;
activeTile.style.backgroundColor=null;
activeTile=null;
flipped =false;
},1000)
})
return tileElement;
}
buildTile is an important function in our implementation. We are creating an element using the dom method createElement, we are adding class and data attribute which will be helpful in comparision. For a tile to be responsive we need to add an event listener to the tile. First we check if two tiles are flipped, if they are we shouldn't allow the flip of others. If the activetile is the element itself then we would be matching it with itself, so we need to exit out of the function. If the activetile is null, the the tile which is flipped first is set to the activetile and the subsequent operations are done. Now, when the second tile is flipped it bypasses everything and gets to the color check. While making comparisions, we are going to check if color of tileElement and the color of activeTile is similar. If they are similar, we have found a match. To add an effect we are going to make the tiles transparent and increase the matchedCount to 2.
If the matchedCount is equal to the total tileCount we have to end the game. We will convert the 'Memory Game' element to 'Game Over' along with some animations which we have already seen in the style.css part.
If the user is unable to complete the game within 30 moves or 5 minutes the user is deemed lost and the game reloads.
Once that cards are flipped and if they don't match, we are going to give it 1 sec or 1000ms and change the set properties of flipped to false and activeTile to null along with the color of the background color.
for(let i=0; i<tileCount;i++)
{
const randomIndex=Math.floor(Math.random() * pickList.length );
const item=pickList[randomIndex];
const tile=buildTile(item);
pickList.splice(randomIndex,1);
console.log(item, tile);
tileContainer.appendChild(tile);
}
This for loop is used to create the tiles which we are observing on the screen. We are selecting a color at random from the picklist using the random method from Math module. since the random method returns a float value we are using floor to round it down.
The splice function is used to remove the color from the picklist such that only one other match of that color is present in the picklist. If the color is not removed there is a possibility of creating 3 similar color tiles when the randomindex hits them.
Finally, we are appending the tile to the tileContainer div using the appendChild method.
Gameplay
This is how the final result is going to look like.