Hi everyone and welcome to the first tutorial on my blog! Iām criticaster, and this info should be enough for you at this time. You might saw me at YouTube, so donāt wonder, yeap, itās me. Letās better write some code.š
Intro & preconditions
As you can notice weāre going to create a āSnakeā game. Weāll use no frameworks and game engines for this. Just using plain javascript weāll create game step by step. The result shoud be like this (DEMO).
Place where everything begins
Letās create the first file for our tiny project - index.html. After that, we create a javascript file and connect it with index.html
<body>
<script src="src/index.js"></script>
</body>For rendering our game on the screen weāll use canvas. Also, letās add an element for a score (snake length) and some styles:
<body>
<script src="src/index.js"></script>
<style media="screen">
#map {
display: block;
margin: 0 auto;
border: 1px dashed red;
}
.wrapper {
position: relative;
width: 500px;
height: 500px;
margin: 0 auto;
}
#score {
position: absolute;
right: 0;
top: 0;
margin: 10px;
font: 35px Comic Sans MS;
}
</style>
<div class="wrapper">
<p id="score">length: 0</p>
<canvas id="map" width="500" height="500"></canvas>
</div>
</body>All that we have now itās:
Time for javascript
Now, itās time for our index.js from the previous step. We should catch the moment of rendering DOM elements. Do you know how to do that? One of the ways - window.onload method. Letās use it for getting access to the DOM elements and starting our game loop.
window.onload = () => {
const canvas = document.getElementById('map')
const ctx = canvas.getContext('2d')
startGame()
}After that, we can go to the implementation of the startGame function. We need some abstraction to collect all info about the game state. As itās javascript - what can be better than an object?).
const game = {}
startGame(game)But what info should we keep there? We have at least 2 types of game objects: snake and foods. So we have to implement them and place into object game.
Letās start from class Snake Snake and define methods for it. At the start, itāll look like this:
class Snake {
constructor() {}
// drawing snake on the canvas
draw() {}
// snake's moving
running() {}
// snake's control using keyboard
directionControl() {}
// control snake's growing
snakeLengthControl() {}
// checking intersections with map boards
validationCoordinates() {}
// checking inner collision of snake's head with body
findSnakeДollision() {}
}Now we can describe them more details.
At first, we should decide what data we need to our snake, weāll pass that data into a constructor. For determination snakeās head position we can use just x, y, and coordinates array for storing the previous position. Also, we need to know the snake lenght, direction (angle), color, canvas context that connected to snake (ctx).
After that we get constructor:
constructor(x, y, angle, length, ctx) {
this.x = x
this.y = y
this.angle = angle
this.length = length
this.ctx = ctx
this.coordinates = []
}But also we have some common characteristics, that we can separate from inner properties as static properties:
Snake.COLOR = '#ff5050'
Snake.INITIAL_LENGTH = 100
Snake.HEAD_RADIUS = 5
Snake.SPEED = 2 // points per iteration
Snake.ROTATION_SPEED = 5 // degrees per iterationWell, now letās consider drawing method. We can implement that using canvas drawing methods. We should just draw circles of some radius and some color which we keep in our object.
So, itāll look like that:
draw() {
this.ctx.beginPath()
this.ctx.fillStyle = Snake.COLOR
this.ctx.arc(this.x, this.y, Snake.HEAD_RADIUS, 0, 2 * Math.PI)
this.ctx.fill()
this.ctx.closePath()
}But all that we have for now - itās an object which can paint static point on the canvas. As you can guess weāre going to implement the running method. The logic of running is simple. On each iteration, we should change the snakeās head position using speed:
running() {
this.x += Snake.SPEED
this.y += Snake.SPEED
}Ok, to test our running we should return to startGame function and implement the game loop. We stopped on the game object, that collects all data about our game objects and parameters. Now we can pass first of them into the game - the instance of Snake class:
window.onload = () => {
const canvas = document.getElementById('map')
const ctx = canvas.getContext('2d')
const snake = new Snake(100, 100, 0, Snake.INITIAL_LENGTH, ctx) const game = { snake, } startGame(game)}So, now we have access to snake inside startGame. To implement game loop we have to call running with some frequency. There are a few ways to do that, but weāll choose the easier - setInterval. After that our function will be like that:
const startGame = (game) => {
const { snake } = game
game.snakeInterval = setInterval(snake.running, 30)
}But, if you launch this code, youāll get the error (Uncaught TypeError: Cannot read property 'angle' of undefined
at running) inside running method, because setInterval loses context and running method doesnāt know anything about the snake, to solve that we should bind context to the method:
const startGame = (game) => {
const { snake } = game
game.snakeInterval = setInterval(snake.running.bind(snake), 30)}Is your canvas still empty? Weāre changing position on each iteration of setInterval, but we donāt call draw method to repaint our canvas, so some changes:
running() {
this.x += Snake.SPEED
this.y += Snake.SPEED
this.draw()}And now our canvas comes alive:

Ok, move forward and consider our control function. Our snake will turn due to keysā s events. Press on left arrow will turn snake to left and on right to turn it right. So we need to connect directionControl method with an event listener. There a no better place for that then startGame function. We can use eventListener on whole document object:
const startGame = (game) => {
const { snake } = game
game.snakeInterval = setInterval(snake.running, 30)
addEventListener('keydown', snake.directionControl)}Perfection, now directionControl will be called each time when you press on some key. And our method will get all the information about that event via the parameter. So, itās time to handle that. We have a simple condition, if keyCode of the pressed key is 37 weāll call a turnLeft method, that decrease snake angle, and opposite way if keyCode is 39:
directionControl(e) {
switch(e.keyCode) {
case 37: {
this.turnLeft()
break
}
case 39: {
this.turnRight()
break
}
}
}
turnLeft() {
this.angle -= Snake.ROTATION_SPEED
}
turnRight() {
this.angle += Snake.ROTATION_SPEED
}However, we donāt use angle value. Where should we place it? Yep, into running. At this moment our running method just increases the value of x and y. Instead of that for getting coordinates (x, y) changes, we must count projection of speed on the angle. For x itās speed * Math.cos(angle) and for y, as you can guess, speed * Math.sin(speed).
As you can notice we keep snakeās direction in degrees, but for counting direction, we must convert them into radians:
const degToRad = (angle) => ((angle * Math.PI) / 180)After that, weāll get:
running() {
const radian = degToRad(this.angle) this.x += Snake.SPEED * Math.cos(radian) this.y += Snake.SPEED * Math.sin(radian)
this.draw()
}After fixing, weāll get:
Letās check our control. Did you also get an error? Actually, itās the same problem with losing context for directionControl as we had for running inside setInterval. Letās bind our snake to directionControl.
After fixing, weāll get:
const startGame = (game) => {
const { snake } = game
game.snakeInterval = setInterval(snake.running.bind(snake), 30)
addEventListener('keydown', snake.directionControl.bind(snake))}Letās test snakeās controlling:

Itās the first part of two articles about creating āSnakeā game in JavaScript.
In the next part, weāll add foods, collisions, and control of snakeās length.
If you like it you can leave your feedback below.