Techletter #41 | August 12, 2023
Building a snake game is definitely not an easy thing. If you build it, will definitely understand a lot of JavaScript concepts. So, in this techletter, I try to explain how you can build a simple snake game using javascript.
Overview
Before moving ahead with coding, we need to know what things are required to build a snake game.
-
User Interface, where one can see the snake moving and can control the movement of the snake.
-
The logic for
-
moving the snake up/down/right/left within the canvas
-
Placing the food randomly within the specified boundaries.
-
Eating the food as well as growing the snake’s length as it eats more food.
-
Checking the collision
-
You can improve a lot on top of it. But, the core logic remains the same.
Let us first start writing the JavaScript code with all the logic
const board = document.getElementById('board'); // from html file
let inputCoordinates = { x: 0, y: 0 };
let snakesSpeed = 10;
let renderTime = 0;
let snake = [{ x: 13, y: 15}];
let lastRenderTime = 0;
const boardSize = 18;
let food = { x: 4, y:4 };
In the above code, you can see all the variables that I have declared. Here, I am using boardSize as const because it won’t change at any time.
At first, the food is placed at (4, 4) position on the board. The x and y axis is not the same as in the coordinate geometry. It is slightly different.
Here, the X-axis goes from left to right, and the Y-axis goes from top to bottom. The origin point (0, 0) is at the top left corner of the web page.
Now let’s write a function to generate food randomly at a place on the board
const generateFood = () => {
const x = Math.floor(Math.random() * boardSize) + 1;
const y = Math.floor(Math.random() * boardSize) + 1;
return { x, y };
}
So the above function returns the position of the food.
Now we will write a function that helps the snake to move. It’s the main function that calls other functions to perform their actions.
const renderView = (viewTime) => {
window.requestAnimationFrame(renderView);
if ((viewTime - lastRenderTime) / 1000 < 1 / snakesSpeed) {
return;
}
lastRenderTime = viewTime;
gameLoop();
}
window.requestAnimationFrame(renderView);
: This line requests the browser to call the renderView
function on the next available frame repaint. This creates a loop that continually renders the view as the browser refreshes the screen.
(viewTime - lastRenderTime) / 1000 < 1 / snakesSpeed
: This condition checks if the time elapsed since the last render is less than the time interval required for the snake to move.
Let’s check for collision
const checkCollision = (positions) => {
for (let i = 1; i < snake.length; i++) {
if (positions[i].x === positions[0].x && positions[i].y === positions[0].y) {
return true;
}
if (positions[i].x > 18 || positions[0].x <=0 || positions[i].y > 18 || positions[0].y <= 0) {
return true;
}
return false;
}
}
If the snake collides with itself or with the boundaries the above function returns as true. That means the collision occurred. Else the function returns false.
Now, let’s write the gameLoop
function
const gameLoop = () => {
// checks collision
if (checkCollision(snake)) {
inputCoordinates = { x: 0, y: 0 };
alert('Game over');
snake = [{ x: 13, y:15 }];
}
// if snake eats food then increase the size of snake & call generateFood()
if (snake[0].y === food.y && snake[0].x === food.x) {
snake.unshift({ x: snake[0].x + inputCoordinates.x, y: snake[0].y + inputCoordinates.y });
food = generateFood();
}
for (let i = snake.length - 2; i >= 0; i-- ) {
snake[i+1] = { ...snake[i]};
}
snake[0].x += inputCoordinates.x;
snake[0].y += inputCoordinates.y;
// everytime the game loop function is called the board innerHTML is set to ""
board.innerHTML = "";
snake.forEach((e,i) => {
snakeElement = document.createElement('div');
snakeElement.style.gridRowStart = e.y;
snakeElement.style.gridColumnStart = e.x;
snakeElement.classList.add('snake');
board.appendChild(snakeElement);
});
foodElement = document.createElement('div');
foodElement.style.gridRowStart = food.y;
foodElement.style.gridColumnStart = food.x;
foodElement.classList.add('food');
board.appendChild(foodElement);
}
You need to add event listeners in order to know in which direction the snake needs to move and increase or decrease the coordinates accordingly.
// initial call
window.requestAnimationFrame(renderView);
// event listener
window.addEventListener('keydown', (e) => {
inputCoordinates = { x: 0, y: 1 };
switch(e.key) {
case 'ArrowUp':
inputCoordinates.x = 0;
inputCoordinates.y = -1;
break;
case 'ArrowDown':
inputCoordinates.x = 0;
inputCoordinates.y = 1;
break;
case 'ArrowLeft':
inputCoordinates.x = -1;
inputCoordinates.y = 0;
break;
case 'ArrowRight':
inputCoordinates.x = 1;
inputCoordinates.y = 0;
break;
default:
break;
}
});
Now, after completing the javascript code you can write HTML and CSS code
<!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>Snake Game</title>
<link rel="stylesheet" href="styles.css" />
<script src="script.js" defer></script>
</head>
<body>
<div id="board">
</div>
</body>
</html>
To add the styles
*{
padding: 0;
margin: 0;
}
body {
min-height: 100vh;
background-size: 100vw 100vh;
background-repeat: no-repeat;
display: flex;
justify-content: center;
align-items: center;
}
#board {
display: grid;
grid-template-columns: repeat(18, 1fr);
grid-template-rows: repeat(18, 1fr);
width: 90vmin;
height: 92vmin;
gap: 1px;
border: 1px solid #ccc;
}
.snake {
background-color: red;
height: 12px;
width: 12px;
}
.food {
background-color: blue;
border-radius: 50%;
height: 10px;
width: 10px;
}
This is a simple snake game. You can improve on top of it by adding scores, and improved UI with different colors as well as sounds.