Many people who are starting out with a new project get caught up in deciding on technologies, what frameworks to use and what database to pick. When often — especially if you are a beginner — it is a better idea to just get started, and make something with the tools you know.

This is the first part of a tutorial that will show how you can build a snake game in less than a hundred lines of JavaScript, without any frameworks. There is some HTML and CSS in addition, but nothing scary. Any beginner to the web platform should be able to go through the guide, and experienced developers will learn something new as well.

Getting started with Snake

To start with, let’s create an empty HTML file on your desktop (Create new file, name it snake.html) and open it in Chrome by dragging it ontop of a Chrome browser window. It should show up entirely white.

Also open the document in your text editor of choice (Notepad works, but I would recommended downloading Visual Studio Code) and insert the following code to setup an almost empty HTML document, then refresh the page.

<!DOCTYPE html5>
<html>
<head>
<title>Snake in pure HTML/JavaScript/CSS</title>
<style>
    html {
        text-align: center;
        font-family: Helvetica, Arial, Helvetica, sans-serif;
    }
</style>
</head>
<body onload="initGame()">
    <h1>Simple Snake</h1>
    <div id="board"></div>

    <script>
    function initGame() {

    }
    </script>
</body>
</html>

View this in a new tab

Let’s start from the top: The head tag. In here we have a title to put a document title on the page, as well as a style tag containing styling to center the contents of the page horizontally and change the font to a sans-serif one.

The the body element, which contains a h1 element with the title of the game and a single div element that will contain the game board. We have it tagged with an ID so we can easily fetch if from JavaScript. We also have a script that will soon contain all of our game logic.

The body element also has an onload handler, which calls the initGame function defined in the script tag. This means that once the document is completely loaded, the code inside initGame will run. There is nothing there yet, so lets’ get started!

Note: If you view the source code on all examples, there will be a script tag at the bottom that handles integration with the iframe when the examples are included on this page. You can inspect it if you like, but it is not necessary to understand it.

Creating the board

Our first code will be to create the game board.

The board will be represented as a two-dimensional array, that is, an array of arrays. We create this as a constant variable inside the script tag called board, we also define the width and height of the board with two constant variables. Here I have picked 26 by 16 cells, as I know this to be a convenient size to fit the final product.

<script>
const board = [];
const boardWidth = 26, boardHeight = 16;

function initGame() {
    // Logic to come
}
</script>

To make it simple, we will build the entire board out of div elements, and will style these to look like cells, apples and the snake.

To create the board, we will iterate over the Y and X axes, to create columns and rows of items:

function initGame() {
    for (var y = 0; y < boardHeight; ++y) {
        for (var x = 0; x < boardWidth; ++x) {
            var cell = {};
        }
    }
}

Each cell will be defined by an empty object {}. In this object we will put the state for each cell.

We need to fetch the board from the DOM (remember the <div id="board></div>"?). We do this using document.getElementById and store it in a variable. This is done outside the loops.

We then create a new div element for every cell on the board via document.createElement. Then we add this cell to the boardElement, meaning every cell will have a matching div in the DOM.

function initGame() {
    for (var y = 0; y < boardHeight; ++y) {
        for (var x = 0; x < boardWidth; ++x) {
            var cell = {};
            
            // Create a <div></div> and store it in the cell object
            cell.element = document.createElement('div');
            
            // Add it to the board
            boardElement.appendChild(cell.element);
        }
    }
}

You can use the developer tools for Chrome to look at the document structure, and you will see all the elements.

Screenshot of a very long list of div elements in the Chrome dev tools.
There should be 26 * 16 = 416 div elements in this list.

Finally, we need to store all these cells inside the board array of arrays. We do this by creating an array for every row, and pushing the cells into this row in the inner loop, and every row into the board array for every outer loop, giving us our final initGame function:

function initGame() {
    for (var y = 0; y < boardHeight; ++y) {
        var row = [];
        for (var x = 0; x < boardWidth; ++x) {
            var cell = {};
            
            // Create a <div></div> and store it in the cell object
            cell.element = document.createElement('div');
            
            // Add it to the board
            boardElement.appendChild(cell.element);

            // Add to list of all
            row.push(cell);
        }

        // Add this row to the board
        board.push(row);
    }
}

However, if you run this code, nothing will show up. This is because div elements by default are invisible, we need to give the some styling to make them visible on the page.

To make the divs visible, we assign a black background color, a width and a height. The styling will be done with the selector #board div, to target the all the divs we created with the code above.

If you try only this they will all lay out in a loooong column down the page that is 24 pixels wide. To fix this, we need to change the display property to inline-block (the default is display: block). This changes the layout so that the blocks are laid out in a line (like text).

#board div {
    background-color: black;
    border: 1px solid grey;
    box-sizing: border-box;
    display: inline-block;
    width: 24px;
    height: 24px;
}

View this in a new tab

The box-sizing property is necessary so that the width of the borders don’t make the cells wider than 24px. You can read up on box-sizing if you are curious about this.

If you view the above example, you will notice that the grid is not square,. We also need to limit the board in width, so that the grid is only 26 cells wide. We specify the width of #board to be 26 * 24px, and use the calc function in CSS to not have to calculate this number manually. The margin: auto means that the board will be centered horizontally on-screen.

#board {
    width: calc(26 * 24px);
    margin: auto;
}

We now have a black grid for the snake game!

View this in a new tab

It is not very interesting yet though, because we don’t have any snake on the game board to control.

Keep track of the game state

Next we need to add the snake to the board. We start by defining the properties the snake has in this game, there are a few:

  • Direction — The snake is moving either left, right, up or down. We can store this as strings 'Up', 'Down', 'Left' and ‘Right’.
  • Position — Defined as a X and Y position on the board. Note that 0, 0 will be top left on the board and 25, 15 will be bottom right.
  • Length — How long is the snake in segments?

Let’s create global variables for these properties.

// Add this after the board definition
var snakeX;
var snakeY;
var snakeDirection;
var snakeLength;

We also need to store the tail of the snake. To keep track of this, we store the information about the tail in the board. So we’ll add a property snake to every cell on the board.

We’ll give it an integer value of 0 to denote that the snake is not on the space (at the start of the game).

// Update this inside the initGame function
var cell = {
    snake: 0
};

Start the game

We also need to give some default values to the snake properties. We’ll do that in a new startGame function that. This function will set all the properties to default values at the start of the game.

The snake will start moving upwards, with a length of 5 segments. It will start in the center of the board (divide width and height by 2), we need to use Math.floor here to round down, since if the board width is 25, 12.5 is not a meaningful position on the grid.

function startGame() {
    // Default position for the snake in the middle of the board.
    snakeX = Math.floor(boardWidth / 2);
    snakeY = Math.floor(boardHeight / 2);
    snakeLength = 5;
    snakeDirection = 'Up';

    // Set the center of the board to contain a snake
    board[snakeY][snakeX].snake = 1;
}

Why do we create new function for this, and not just give default values to the global variables? Because we also need to do the same thing when the player dies to reset the game state.

In other words: initGame will only run once for the entire application, but startGame will run multiple times.

Add a call to startGame from the end initGame (before the final }). With this addition, we’re done with the first part of the tutorial.

Wrapping up

Next time, we will make the snake visible on the board and implement keyboard controls, so that you can steer it around the board using the keyboard.

Continue with part 2 of the tutorial.