Let’s make Snake with JavaScript

Tasty orange squares

We’ve improved our game to the point where we can add more features. On the whole, our game right now is looking pretty boring, the snake just wonders around in never-ending and seemingly infinite limbo where nothing interesting ever happens. So let’s change that! We want to add food items which randomly appear on the map and – when bumped into by the snake – make the snake longer. Let’s break this problem into parts and work on every part individually.

Let’s start by adding some code to the drawGame function:

We are doing the exact same thing for food as we did for snake cells. As you can see, the code is nearly identical. When you see code like this you should always consider simplifying it by writing functions you can reuse. Let’s do just that and add a brand new function immediately above the drawGame function. We’ll call it drawCells:

Now we will change the drawGame function correspondingly:

As a general rule of thumb, try to always avoid redundancy in code. You don’t need to do obsess and lose sleep over it, just keep it in mind as a good general practice. When you spot repeating code, the light-bulbs in your head should start flickering. Cleaning code like this up makes things easier for your future self or someone else who might be working on your code.

The code to draw food is now in place but we still need to figure out a way to create food items to begin with. Our food variable is initialized to an empty list, now let’s find a way to fill it up!

Let’s create a new function, we’ll name it makeFood. Add this code immediately above the updateGame function:

Since we want to place food items on screen at random, we will also need a random number generator. For ease of use, let’s make it a function. Add this code immediately above the makeFood function:

Math.random() generates a random number in range [0, 1). Our function multiplies this number by b and adds a, and then applies Math.floor to the result. This gives us a random number in range [a, b]. Now we can conveniently use this function when generating random coordinates.

Let’s also add a call to makeFood at the end of our updateGame routine. Add the two lines to the bottom:

Now that we’ve got everything set up, let’s play around with the makeFood function. The problem goes as follows. While the game plays out, we want to generate food with random x and y coordinates. At the same time, we don’t want the food to pop up on top of the snake, so let’s make our code exclude all the cells that are currently occupied by the snake. To achieve this, we will loop through all the cells contained in the snake list and see if any of them matches our random coordinates. If we find a match, we’ll simply abort the food generation and exit the function, and let it have another go on the following update – since this function will be continuously called, this won’t pose much of a problem and we can always give it a try on the next go. We also want to limit the number of food items, let’s go with 5 for now.

With that in mind, let’s add some code to the makeFood function:

But we may be forgetting something. What if food spawns on top of another food? This may mess up our game logic further down the line, so let’s try and fix this. Learning from our previous encounters with redundant code, let’s add another function immediately above the makeFood function and call it checkCells:

Here, we look for matches between x and y coordinates and any elements within some arbitrary list of cells that is given to the function. We then return an index of the item it found on hit, or -1 on miss when we find no items. This will come in useful later.

Let’s now edit the makeFood function like so:

Now we need to deal with the act of the snake actually eating food items on screen. If you run the game right now, you will observe that food items do in fact show up one by one, but the snake simply ignores them and goes right through them. We will need to alter the updateGame function so it takes food into account. What we will do is simply check, on each update, whether or not the new head of the snake is touching food. If so, we will dispose of this food and extend the snake by ignoring the final operation of cutting its tail. Let’s code these ideas into updateGame:

The way we coded our checkCells function now turned out to be very handy. We use a single variable ate to give us information on both whether or not we ate a food item or not, and at which index the item is if we did. We then use Array.splice() to remove the item from the list. To grow the snake, we simply skip the next part where we remove the tail of the snake from the list in case we’ve eaten. Tink of it like this: whenever ate is -1, our snake is roaming around in empty space, so we keep removing the tail to make the snake go. But when we encounter food, ate goes to some number greater than -1, in which case we can make the snake grow by not cutting the tail on that single update frame.

We can now test the game and marvel at the snake’s newly gained ability to eat! It should look and play something like this:

Om nom nom nom

As you can observe, our snake grows as the player eats food. However, food seems to reappear immediately after it is eaten. Also we can observe that at the start of the game the food is generated 5 times in a sequence of frames.

Let’s add some code into our resetGame function so that we generate some food items the game begins:

That fixes the problem of food popping up in sequence at the start of the game. When we now run the game, we’re most likely (that is – unless generation fails) to begin the game with 5 food items randomly placed on screen.

To give the game a more slow paced feeling, we will change the code a bit so that food isn’t generated continuously. We need to tell the computer to only create more food items once in a blue moon. Let’s make a small change to the updateGame function:

We now only generate more food whenever a randomly generated number in range [0, 12] equals zero. Or in human terms, on every update to our game, we roll a dice that has 13 sides (13 because we include the zero) and see if it rolls 0, and only then create more food.

Our game should now feel more natural to play as food takes some small amount of time to reappear after it is eaten:

Now that we have what appears to almost be a fully playable game, we need to deal with one more nuisance – collision detection. We will code this up in the following section.