How to Make a Simple Snake Game in Javascript?


Snake Game (snake eats apple and grows) is a simple and easy to implement game that you can practice when you learn a new programming language. It is not complex, and usually, it can be designed and completed in a day.

simple-snake-game-in-javascript How to Make a Simple Snake Game in Javascript? games javascript

simple-snake-game-in-javascript

Drawing the Canvas

You would need a canvas to draw the snake (green body pieces) and the red apple. In the browser, you can define a Game canvas in HTML tag:

1
<canvas width="400" height="400" id="game"></canvas>
<canvas width="400" height="400" id="game"></canvas>

Then let’s define a few global variables that we need to use later:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var context;
var canvas;
var score = 0;
var bestscore = 0;
var grid = 16;
var count = 0;
  
var snake = {
  x: 160,
  y: 160,
  
  // snake directin offsets
  dx: grid,
  dy: 0,
  
  // snake body
  cells: [],
  
  // snake body length, grows when eats an apple
  maxCells: 4
};
 
var apple = {
  x: 320,
  y: 320
};
var context;
var canvas;
var score = 0;
var bestscore = 0;
var grid = 16;
var count = 0;
  
var snake = {
  x: 160,
  y: 160,
  
  // snake directin offsets
  dx: grid,
  dy: 0,
  
  // snake body
  cells: [],
  
  // snake body length, grows when eats an apple
  maxCells: 4
};

var apple = {
  x: 320,
  y: 320
};

Game Controls

Then on the body onload event, we need to invoke a onload function:

1
<body onload="windowload()">
<body onload="windowload()">

The onload function will be called once the HTML is loaded and the DOM is finished loading. Then, we listen to the keydown event to control the snake. movement.

The additional checks prevent snake from backtracking on itself by checking that it’s not already moving on the same axis (pressing left while moving left won’t do anything, and pressing right while moving left shouldn’t let you collide with your own body).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function windowload() {
  canvas = document.getElementById('game');
  canvas.setAttribute('tabindex','0');
  canvas.focus();
  context = canvas.getContext('2d');
 
  // arrow keys to control the snake
  document.addEventListener('keydown', function(e) { 
    // left arrow key
    if (e.which === 37 && snake.dx === 0) {
      snake.dx = -grid;
      snake.dy = 0;
    }
    // up arrow key
    else if (e.which === 38 && snake.dy === 0) {
      snake.dy = -grid;
      snake.dx = 0;
    }
    // right arrow key
    else if (e.which === 39 && snake.dx === 0) {
      snake.dx = grid;
      snake.dy = 0;
    }
    // down arrow key
    else if (e.which === 40 && snake.dy === 0) {
      snake.dy = grid;
      snake.dx = 0;
    }
  });
  window.requestAnimationFrame(loop);
}
function windowload() {
  canvas = document.getElementById('game');
  canvas.setAttribute('tabindex','0');
  canvas.focus();
  context = canvas.getContext('2d');

  // arrow keys to control the snake
  document.addEventListener('keydown', function(e) { 
    // left arrow key
    if (e.which === 37 && snake.dx === 0) {
      snake.dx = -grid;
      snake.dy = 0;
    }
    // up arrow key
    else if (e.which === 38 && snake.dy === 0) {
      snake.dy = -grid;
      snake.dx = 0;
    }
    // right arrow key
    else if (e.which === 39 && snake.dx === 0) {
      snake.dx = grid;
      snake.dy = 0;
    }
    // down arrow key
    else if (e.which === 40 && snake.dy === 0) {
      snake.dy = grid;
      snake.dx = 0;
    }
  });
  window.requestAnimationFrame(loop);
}

The window.requestAnimationFrame takes a function call back as a parameter that tells windows to run on painting the next frame. Before the main game loop, we need to define a few helper functions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// return a random integer between [min, max)
function getRandomInt(min, max) {
  return Math.floor(Math.random() * (max - min)) + min;
}
 
function showScore(score) {
    document.getElementById('score').innerHTML = score;    
}
 
function showBestScore(score) {
    document.getElementById('bestscore').innerHTML = score;    
}
 
// reset the game
function resetGame() {
        snake.x = 160;
        snake.y = 160;
        snake.cells = [];
        snake.maxCells = 4;
        snake.dx = grid;
        snake.dy = 0;        
        score = 0;
        showScore(score);
        apple.x = getRandomInt(0, 25) * grid;
        apple.y = getRandomInt(0, 25) * grid;
}
// return a random integer between [min, max)
function getRandomInt(min, max) {
  return Math.floor(Math.random() * (max - min)) + min;
}

function showScore(score) {
    document.getElementById('score').innerHTML = score;    
}

function showBestScore(score) {
    document.getElementById('bestscore').innerHTML = score;    
}

// reset the game
function resetGame() {
        snake.x = 160;
        snake.y = 160;
        snake.cells = [];
        snake.maxCells = 4;
        snake.dx = grid;
        snake.dy = 0;        
        score = 0;
        showScore(score);
        apple.x = getRandomInt(0, 25) * grid;
        apple.y = getRandomInt(0, 25) * grid;
}

Main Game Loop

In the game loop, we need to recursively tell windows to requestAnimationFrame. Then, clear the canvas and draw the snake body pieces and the apple.

The game-over needs to be triggered if the snake hits the wall, or it collides with its body. When it moves, we can pop one from its tail and push it to the front (head).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
function loop() {
  requestAnimationFrame(loop);
 
  // slow game loop to 15 fps instead of 60 (60/15 = 4)
  if (++count < 4) {
    return;
  }
 
  count = 0;
  context.clearRect(0,0,canvas.width,canvas.height);
 
  // move snake by it's velocity
  snake.x += snake.dx;
  snake.y += snake.dy;                 
  
  if ((snake.x < 0) || (snake.x >= canvas.width)) {
    resetGame();
    return;
  }
  
  if ((snake.y < 0) || (snake.y >= canvas.height)) {
    resetGame();
    return;
  }  
 
  // keep track of where snake has been. front of the array is always the head
  snake.cells.unshift({x: snake.x, y: snake.y});
 
  // remove cells as we move away from them
  if (snake.cells.length > snake.maxCells) {
    snake.cells.pop();
  }
 
  // draw apple
  context.fillStyle = 'red';
  context.fillRect(apple.x, apple.y, grid-1, grid-1);
 
  // draw snake one cell at a time
  context.fillStyle = 'green';
  snake.cells.forEach(function(cell, index) {    
    // drawing 1 px smaller than the grid creates a grid effect in the snake body so you can see how long it is
    context.fillRect(cell.x, cell.y, grid-1, grid-1);  
 
    // snake ate apple
    if (cell.x === apple.x && cell.y === apple.y) {
      snake.maxCells++;
 
      // canvas is 400x400 which is 25x25 grids 
      apple.x = getRandomInt(0, 25) * grid;
      apple.y = getRandomInt(0, 25) * grid;
      
      score ++;
      bestscore = Math.max(bestscore, score);
      showBestScore(bestscore);
      showScore(score);
    }
 
    // check collision with all cells after this one (modified bubble sort)
    for (var i = index + 1; i < snake.cells.length; i += 1) {      
      // snake occupies same space as a body part. reset game
      if (cell.x === snake.cells[i].x && cell.y === snake.cells[i].y) {
         resetGame();
         return;
      }
    }
  });
}
function loop() {
  requestAnimationFrame(loop);

  // slow game loop to 15 fps instead of 60 (60/15 = 4)
  if (++count < 4) {
    return;
  }

  count = 0;
  context.clearRect(0,0,canvas.width,canvas.height);

  // move snake by it's velocity
  snake.x += snake.dx;
  snake.y += snake.dy;                 
  
  if ((snake.x < 0) || (snake.x >= canvas.width)) {
    resetGame();
    return;
  }
  
  if ((snake.y < 0) || (snake.y >= canvas.height)) {
    resetGame();
    return;
  }  

  // keep track of where snake has been. front of the array is always the head
  snake.cells.unshift({x: snake.x, y: snake.y});

  // remove cells as we move away from them
  if (snake.cells.length > snake.maxCells) {
    snake.cells.pop();
  }

  // draw apple
  context.fillStyle = 'red';
  context.fillRect(apple.x, apple.y, grid-1, grid-1);

  // draw snake one cell at a time
  context.fillStyle = 'green';
  snake.cells.forEach(function(cell, index) {    
    // drawing 1 px smaller than the grid creates a grid effect in the snake body so you can see how long it is
    context.fillRect(cell.x, cell.y, grid-1, grid-1);  

    // snake ate apple
    if (cell.x === apple.x && cell.y === apple.y) {
      snake.maxCells++;

      // canvas is 400x400 which is 25x25 grids 
      apple.x = getRandomInt(0, 25) * grid;
      apple.y = getRandomInt(0, 25) * grid;
      
      score ++;
      bestscore = Math.max(bestscore, score);
      showBestScore(bestscore);
      showScore(score);
    }

    // check collision with all cells after this one (modified bubble sort)
    for (var i = index + 1; i < snake.cells.length; i += 1) {      
      // snake occupies same space as a body part. reset game
      if (cell.x === snake.cells[i].x && cell.y === snake.cells[i].y) {
         resetGame();
         return;
      }
    }
  });
}

If you allow snake to rewind to other side of the canvas, you can use the following code when checking the boundaries.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  // wrap snake position horizontally on edge of screen
  if (snake.x < 0) {
    snake.x = canvas.width - grid;
  }
  else if (snake.x >= canvas.width) {
    snake.x = 0;
  }
  
  // wrap snake position vertically on edge of screen
  if (snake.y < 0) {
    snake.y = canvas.height - grid;
  }
  else if (snake.y >= canvas.height) {
    snake.y = 0;
  }
  // wrap snake position horizontally on edge of screen
  if (snake.x < 0) {
    snake.x = canvas.width - grid;
  }
  else if (snake.x >= canvas.width) {
    snake.x = 0;
  }
  
  // wrap snake position vertically on edge of screen
  if (snake.y < 0) {
    snake.y = canvas.height - grid;
  }
  else if (snake.y >= canvas.height) {
    snake.y = 0;
  }

The simple snake game can be played here: https://helloacm.com/static/game/snake/

Play Snake Game

Want to play the snake game? Here are two good options:

–EOF (The Ultimate Computing & Technology Blog) —

GD Star Rating
loading...
1032 words
Last Post: Integration Tests Using PHPUnit to Ensure Your Website is Working with Static and Dynamic Contents
Next Post: Microbit Programming: Snake Game with Growing Body and Greedy Strategy

The Permanent URL is: How to Make a Simple Snake Game in Javascript?

Leave a Reply