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.
Start the Project:
<!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>
<!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.
Style the index.html using 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 ;
}
Here we have just given some styles to improve our ui.Writing the logic of Snake Game in Javascript:
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.
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.
// 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. // 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)}
}
// 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
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 browserFinalize:
<!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 ;
}
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;
}
})
Comments
Post a Comment