How to make a Snake game using HTML , CSS and JavaScript


Introduction :

A Snake Game is a greate project for improving your skills in Web Development. You can improve your skills in HTML , CSS and JavaScript. You can also learn how Animation Frame works in HTML, Advanced CSS properties and also some intermediate level JavaScript Language in this project.You can mention this project in your resume by some further improving. So lets start...

Setting Up The Project: 

To start the creating the project here we need  is a Text Editor or an IDE. In this tutorial we will use Microsoft Visual Studio Code (VS Code) 

Please download and install VS Code from here .  

Click here to learn how to download VS Code

Now another thing you need to downlod is an extension called Live Server . It will help us to use the live reload for our project. 

After succesful installation of VS Code open it and goto Extension manager and search for Live Server and install it.



Now we are ready to start the project.

Start the Project:

Now to start the project open VS Code and Open a new folder using 
 
File > Open Folder

Now Create a new index.html file which will like this.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
</body>
</html>

Now we will create a div for our board in the body element.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="board"></div>
</body>
</html>
Now lets create and the CSS stylesheet (style.css) and script.js file and include them.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
	<p>Press any key to start and press 'P' to pause</p>
    <div id="board"></div>
    <script src="main.js"></script>
</body>
</html>
Now our initial steps are almost over. Now lets style the html and write the logic.
We will add a logic later to play/pause the game in main.js file 

Style the index.html using CSS: 

Create a style.css file in your project root folder and copy and paste the fillowing css code to your style.css file.

* {
    padding: 0;
    margin: 0;
}

body {

    background: linear-gradient(lightgreen, #77b255);
    background-size: contain;
    min-height: 100vh;
    display: flex;
    flex-direction: row;
    justify-content: space-around;
    align-items: center;
}

#board {

    background: #eee;
    height: 90vmin;
    width: 90vmin;
    border: 1px solid #000;
    display: grid;
    grid-template-rows: repeat(60,1fr);
    grid-template-columns: repeat(60,1fr);
    z-index: 0;
    overflow: hidden;
    border-radius: 1rem;
}

.head {
    background: linear-gradient(red , purple) !important;
    border: .25vmin solid purple !important;
    border-radius: 5px;
    z-index: 3 !important;
}
.snake {
    background-color: purple;
    border-radius: 12px;
    border: .25vmin solid #eee;
    z-index: 2;
}

.food {
    background: linear-gradient(orange,red);
    border-radius: 100%;
    position: relative;
    z-index: 0;
}
.food::after {
    content: "";
    position: absolute;
    bottom: 100%;
    left: 40%;
    border-radius: 1rem;
    margin-left: -3px;
    border-width: 3px;
    border-style: solid;
    border-color: transparent transparent darkgreen darkgreen  ;
}
Here we have just given some styles to improve our ui.
Style for snake and food are also written which we will use later on.

Writing the logic of  Snake Game in Javascript:

So to write the logic for our Snake Game please create a main.js file in your project level root folder. Now lets start to write our logic.
First we declare the variables we use in our project. To play the game engine we will use the requestAnimationFrame function as it is more optimized for animating web page.
  
let board = document.getElementById('board');
let snakeArray = [{x: 7, y: 5}, {x:7,y:4}]
let food = {x:5,y:5}
let movingDirection = {x:0 , y: 1}
let lastPaintTime = 0;
let speed = 05;
let requestId = null
let foodElem;
let snakePart;
let a = 1, b = 30;
  
  
Here we have declared Snake Body using a snakeArray variable, position of food using a food object and default moving direction using movingDirection. the lastPaintTime is the variable to store the last time when the screen was painted. RequestId is id of requested frame, which we will use to start/stop the game. a , b  are the number of grids in the board.speed here is our desired speed.

Now its time to setting up the grids in the board.
  
board.style.gridTemplateColumns = `repeat(${b}, 1fr)`
board.style.gridTemplateRows = `repeat(${b}, 1fr)`
  
Now lets write the logic to controll the framerate of the game. In this case we will use the recursive requestAnimationFrame function to get our desired frame rate

  // Creating Animation or frame rate
function loop(time) {
    requestId =  window.requestAnimationFrame(loop)
    if ((time - lastPaintTime) / 1000 < 1 / speed ) return;
    lastPaintTime = time;
    gameEngine();
}
  
now we will create an gameEngine() function to write the logic behind the gameplay.

Now before writing the gameEngine() function  we need some functions to create which will assist us for gameplay.
1st, let's create the drawSnakeAndFood() function which will help us to draw the snake and food for us.
  
  // function to draw the snake and the food
function drawSnakeAndFood() {
    board.innerHTML = ''

    // creating snake 
    snakeArray.forEach((e,index) => {
        snakePart = document.createElement('div')
        snakePart.classList.add('snake')
        snakePart.style.gridRowStart = e.y
        snakePart.style.gridColumnStart = e.x
        if (index === 0) snakePart.classList.add('head')
        board.appendChild(snakePart)
    })

    // creating food

    foodElem = document.createElement('div')
    foodElem.style.gridRowStart = food.y
    foodElem.style.gridColumnStart = food.x
    foodElem.classList.add('food')
    board.appendChild(foodElem)
}
  
  
Always remember that in grid in CSS the x axis from left to right (columns) and y axis is from top to bottom (rows). While controlling the snake movement this information is needed to design the controlling system.

2nd, lets create snakeHaveEatenFood() function which will handle the event when the snake will eat the food.
    // fuction to handle the condition when snake will eat food
        function snakeHaveEatenFood(sarr) { 
            sarr.unshift({x : sarr[0].x + movingDirection.x,
            			  y: sarr[0].y + movingDirection.y})   
            food = {x: generateRandom(b,a), y: generateRandom(b,a)}
        }
    
The function will be run when the snake will eat the food
Explanation: The logic behind this is when the Head of Snake and Food have same coordinates that means the snake have eaten the food and then the length of the snake will be increased by one and food position will be changed using the generateRandom(max , min) function which will generate a random number between the max and min number (i.e. - we want to generatea coordinte value between a and b)
But... where is generateRandom(max , min) function ?!!!

let's create it.
    // function to generate random number between max and min
        function generateRandom(max,min) { 
            return Math.round(min + (max - min ) * Math.random())
        }
    
Now we are ready to code our gameEngine() function 
6 parts.
1. We will check if the Snake Head  has walked on it Body. If so we will end the game and start a new one.
2. We will check if the Snake eat the food.
3. In this part, we will change the coordinates of the snake's body parts (except Head)  so that the snake can move
4. We will move the snakes Head position according to the instructions from the Controls.
5. Here we write the logic to make the snake wrap when crossing the wall.
6. Finally, we draw the snake using drawSnakeAndFood() function.

Now lets code: 
    
        function gameEngine() {
            // PART 1:  Adding logic to eat food and move
            snakeArray.forEach((e , i)=> {
                if (i !== 0 && snakeArray[0].x === e.x && snakeArray[0].y === e.y) {
                    alert(`Game over your score is ${score}`)
                    snakeArray = [{x: 7, y: 5},{x:7,y:4}];
                    food = {x : generateRandom(b,a), y: generateRandom(b,a)}
                    movingDirection = {x:0,y:1} 
                    setScore(0)
                }
            })
        
            // Snake eat food 
        
            if (snakeArray[0].x === food.x && snakeArray[0].y === food.y) 
            	snakeHaveEatenFood(snakeArray);
        
            // Move the snake 
        
            for (let index = snakeArray.length - 2; index >=0 ; index--) 
                {snakeArray[ index + 1] = {...snakeArray[index]}}
        
            // snake is moving
        
            snakeArray[0].x += movingDirection.x
            snakeArray[0].y += movingDirection.y
        
            // wraping the snake at wall
        
            if (snakeArray[0].x > b) snakeArray[0].x -= b
            if (snakeArray[0].x < a ) snakeArray[0].x = b - snakeArray[0].x
            if (snakeArray[0].y > b) snakeArray[0].y -= b
            if (snakeArray[0].y < a ) snakeArray[0].y = b - snakeArray[0].y
        
            // PART 2 : Creating snake and food
            drawSnakeAndFood()
            
        }
    
We are  almost done. Now its time to create game controls. 

  // draw the snake before starting the game
  
  drawSnakeAndFood()
  
  // this is the logic to start and stop the game
  
  function playPauseGame(e) {
            if(requestId !== null && e==='p' ) {
                window.cancelAnimationFrame(requestId)
                requestId = null
            }
            else if(requestId === null) requestId = window.requestAnimationFrame(loop)
            else {}
        }
        // Here is the game controls 
        window.addEventListener('keydown', e => {
            switch (e.key) {
                case "ArrowUp":
                    movingDirection.x = 0;
                    movingDirection.y = movingDirection.y === 1 ? 1 : -1;
                    break;
                case "ArrowLeft":
                    movingDirection.x = movingDirection.x === 1 ? 1 : -1;
                    movingDirection.y = 0;
                    break;
                case "ArrowDown":
                    movingDirection.x = 0;
                    movingDirection.y = movingDirection.y === -1 ? -1 : 1;
                    break;
                case "ArrowRight":
                    movingDirection.x = movingDirection.x === -1 ? -1 : 1;
                    movingDirection.y = 0;
                    break;
                case 'p': 
                    playPauseGame(e.key)
                    break;
                default:
                    playPauseGame(null)
                    break;
            }
        })
        
    
Now we are done with our project. Now open your LiveSever by RIght Click and Go Live to view your project in your browser

Note: the playPauseGame(e)  function takes the input e and play and pause the game accordingly using requestId using the requestAnimationFrame() function (to start the game) and  cancelAnimationFrame() function (to stop the game). Now our project is over.

Finalize: 

Now your project files will look like:

index.html 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
	<p>Press any key to start and press 'P' to pause</p>
    <div id="board"></div>
    <script src="main.js"></script>
</body>
</html>
style.css

  * {
    padding: 0;
    margin: 0;
}

body {

    background: linear-gradient(lightgreen, #77b255);
    background-size: contain;
    min-height: 100vh;
    display: flex;
    flex-direction: row;
    justify-content: space-around;
    align-items: center;
}

#board {

    background: #eee;
    height: 90vmin;
    width: 90vmin;
    border: 1px solid #000;
    display: grid;
    grid-template-rows: repeat(60,1fr);
    grid-template-columns: repeat(60,1fr);
    z-index: 0;
    overflow: hidden;
    border-radius: 1rem;
}

.head {
    background: linear-gradient(red , purple) !important;
    border: .25vmin solid purple !important;
    border-radius: 5px;
    z-index: 3 !important;
}
.snake {
    background-color: purple;
    border-radius: 12px;
    border: .25vmin solid #eee;
    z-index: 2;
}

.food {
    background: linear-gradient(orange,red);
    border-radius: 100%;
    position: relative;
    z-index: 0;
}
.food::after {
    content: "";
    position: absolute;
    bottom: 100%;
    left: 40%;
    border-radius: 1rem;
    margin-left: -3px;
    border-width: 3px;
    border-style: solid;
    border-color: transparent transparent darkgreen darkgreen  ;
}
main.js
  
let board = document.getElementById('board');
let snakeArray = [{x: 7, y: 5}, {x:7,y:4}]
let food = {x:5,y:5}
let movingDirection = {x:0 , y: 1}
let lastPaintTime = 0;
let speed = 05;
let requestId = null
let foodElem;
let snakePart;
let a = 1, b = 30;

board.style.gridTemplateColumns = `repeat(${b}, 1fr)`
board.style.gridTemplateRows = `repeat(${b}, 1fr)`


  // Creating Animation or frame rate
function loop(time) {
    requestId =  window.requestAnimationFrame(loop)
    if ((time - lastPaintTime) / 1000 < 1 / speed ) return;
    lastPaintTime = time;
    gameEngine();
}


  // function to draw the snake and the food
function drawSnakeAndFood() {
    board.innerHTML = ''

    // creating snake 
snakeArray.forEach((e,index) => {
	snakePart = document.createElement('div')
	snakePart.classList.add('snake')
	snakePart.style.gridRowStart = e.y
    snakePart.style.gridColumnStart = e.x
	if (index === 0) snakePart.classList.add('head')
        board.appendChild(snakePart)
    })

    // creating food

    foodElem = document.createElement('div')
    foodElem.style.gridRowStart = food.y
    foodElem.style.gridColumnStart = food.x
    foodElem.classList.add('food')
    board.appendChild(foodElem)
}
  
  
function snakeHaveEatenFood(sarr) { 
	sarr.unshift({x : sarr[0].x + movingDirection.x, y: sarr[0].y + movingDirection.y})   
	food = {x: generateRandom(b,a), y: generateRandom(b,a)}
}


function generateRandom(max,min) { 
	return Math.round(min + (max - min ) * Math.random())
}


function gameEngine() {
	// PART 1:  Adding logic to eat food and move
	snakeArray.forEach((e , i)=> {
	if (i !== 0 && snakeArray[0].x === e.x && snakeArray[0].y === e.y) {
                    alert(`Game over your score is ${score}`)
                    snakeArray = [{x: 7, y: 5},{x:7,y:4}];
                    food = {x : generateRandom(b,a), y: generateRandom(b,a)}
                    movingDirection = {x:0,y:1} 
                    setScore(0)
		}
	})
        
            // Snake eat food 
        
	if (snakeArray[0].x === food.x && snakeArray[0].y === food.y) 
    	snakeHaveEatenFood(snakeArray);
        
	// Move the snake 
        
	for (let index = snakeArray.length - 2; index >=0 ; index--) 
		{snakeArray[ index + 1] = {...snakeArray[index]}}
        
	// snake is moving
        
	snakeArray[0].x += movingDirection.x
	snakeArray[0].y += movingDirection.y
        
	// wraping the snake at wall
        
	if (snakeArray[0].x > b) snakeArray[0].x -= b
	if (snakeArray[0].x < a ) snakeArray[0].x = b - snakeArray[0].x
    if (snakeArray[0].y > b) snakeArray[0].y -= b
    if (snakeArray[0].y < a ) snakeArray[0].y = b - snakeArray[0].y
        
    // Creating snake and food
    drawSnakeAndFood()
    
    }
    
    // draw the snake before starting the game
  
  drawSnakeAndFood()
  
  // this is the logic to start and stop the game
  
  function playPauseGame(e) {
            if(requestId !== null && e==='p' ) {
                window.cancelAnimationFrame(requestId)
                requestId = null
            }
            else if(requestId === null) requestId = window.requestAnimationFrame(loop)
            else {}
        }
        // Here is the game controls 
        window.addEventListener('keydown', e => {
            switch (e.key) {
                case "ArrowUp":
                    movingDirection.x = 0;
                    movingDirection.y = movingDirection.y === 1 ? 1 : -1;
                    break;
                case "ArrowLeft":
                    movingDirection.x = movingDirection.x === 1 ? 1 : -1;
                    movingDirection.y = 0;
                    break;
                case "ArrowDown":
                    movingDirection.x = 0;
                    movingDirection.y = movingDirection.y === -1 ? -1 : 1;
                    break;
                case "ArrowRight":
                    movingDirection.x = movingDirection.x === -1 ? -1 : 1;
                    movingDirection.y = 0;
                    break;
                case 'p': 
                    playPauseGame(e.key)
                    break;
                default:
                    playPauseGame(null)
                    break;
            }
        })
    

You can view a demo of the snake game here .
You can also download the source code from github.

Excercise :

1. Add a scoreboard to the game and display the current score and the highest score.
2. Write a logic to prevent the food to be placed on snaked body.

Follow US:

You can now explore more project from us on github.
Our more blog channel CoolDeveloper, CoMitra .
Subscribe us on youtube




 

Comments

Popular posts from this blog

How To Save Money As Student

Install VS Code in Android