Let’s make Snake with JavaScript

Two steps forward, one step back

On the surface, things look fine but the devil is – like always – in the details. If you start playing around with the game at this point and you test user input carefully, you may find there’s still something wrong with our game. We can still reverse our snake to go backwards if we trick our input function. Try running the game and very quickly press up and then left or down and then left. The snake runs over itself backwards! You can do this for any direction by simply tapping one allowed direction and one disallowed direction one after another quick succession. The player can use this bug to do what we thought we already fixed:

Don’t frustrate over it, though – this is what programming is all about! Let’s think about what is going on here. Let’s navigate back to our keyDown function again and look at what happens when we press – for example – the left arrow key on the keyboard:

When the user presses, “left”, we set direction to “left”, unless the direction is currently set to “right”. In the same fashion, when the user presses “up”, we set the direction to “up” unless the direction is currently set to “down”. If we were naive about testing, we might end up thinking that this makes reversing direction impossible for the user. But remember that our game is running at a fixed framerate, and a pretty slow one at that. This means that the user has time (1/5th of a second) between updates to press any key they desire and possibly trick our game logic into doing something that we don’t want to allow.

Consider the following scenario:
1. direction is set to RIGHT
2. the user presses UP
3. since the direction is not DOWN, we change the direction to UP
4. the user presses LEFT
5. since the direction is not RIGHT, we change the direction to LEFT

Since the user can perform these actions between two frames of logic, the snake can be coerced into any direction, overriding our carefully coded lock-outs. What can we do to fix this? If we observe our code so far, we notice that we have a single “direction” variable which can be altered by the user by pressing one of the direction keys. This same variable also controls the actual movement of the snake between frames. What we really want is to separate the direction our user picks and the direction our snake is actually heading towards. With that in mind, let’s add another variable to our game, we’ll call it heading.

Add the following line to the list of “game variables”.

Now let’s alter our keyDown function so that it looks for the direction the snake is heading in when deciding whether or not a direction should be applied. Change the code like so:

Next, we’ll take these changes into account in our updateGame function. We will change the function so that it sets heading to whatever value direction holds, once per frame:

Let’s also not forget about initializing our new variable. Change the code in resetGame to initialize both heading and direction variables at once:

With these changes in place, the user should now not be able to wiggle any strange pattern into the keyboard to make the snake go in some direction we don’t want it to go. If you now try the game, you will find you are unable to reverse the direction of the snake using the trick we described previously.

You should at all times be very rigorous with bug hunting. When testing your game, always try to push the limits and never just assume that things will roll out how you envision them when you first begin. Bugs are a part of life and the sooner you start coping with them, the better.

At this point, I encourage you to play around with the game and try to find any new bugs or other problems before continuing on to the next section.

Smooth sailing

If we take a moment to examine our game’s behavior closely at this point, we’ll notice that even with our previous fixes in place, the game behaves in a very robust way, sometimes accepting and other times refusing user commands. For example – as the snake initially moves to the right when you run the game – you press down and right in succession, expecting the snake to zig-zag along the directions you pressed in. However – unless you very carefully time your key inputs to align with the frame rate we are using – the snake only moves downwards and ignores any further commands. This happens because we only consider a single direction per frame. The game is playable, but difficult for the user to actually operate. So how can we improve it and make life easier on the player?

The thing to consider is a queue of user inputs. Instead of just ever keeping track of a single direction as given by the user (or initialized at the start of the game), let’s consider a sequence of directions instead, and then take them out one by one in a first-in, first-out manner while the game plays out.

We’ll need to carefully readjust our code to achieve this. First, let’s make some changes to the resetGame function:

From now on, we will treat direction as a queue. This means we’ll be pushing items into it and then using Array.shift() on it to pick out items one by one from the beginning of the array before processing them.

Now we need to implement some new code and alter the keyDown function once again. We can’t rely on the heading variable anymore like we used to because it only holds the current heading of the snake. For example if the snake is heading right, and the user presses up and then left, we want to be blocking first “left”, then “down” and finally “right”. To achieve this, we will need to determine the last direction the user pushed towards, which will be given by the last element of our queue or if our direction queue happens to be empty, we will default back to what our current heading is. We will store this value into a variable named lastHeading and then use it to determine whether a move into a certain direction is allowed and if so, push that direction to the end of the queue.

Update the keyDown function as follows:

If you are like me and you posses a distaste for redundancy in your code, note that you can also use the ternary condition syntax to rewrite lines 4-8 to something like this:

Finally, we need to change the updateGame function to work correctly with these changes. Edit the function and add code as follows:

In the above code, we make our game change the heading variable once per frame whenever direction contains elements. We achieve this by taking the first element off our queue. When the queue is empty, heading simply stays unchanged and the snake moves along the last direction used.

When we now test the game, we can see that when we input several commands one after another, the snake in our game will happily follow those orders until it runs out of commands, making the flow of our game smoother and allowing the player to better manuever around corners and tight spots. Hooray!

Optionally we can add the following to the keyDown function to limit the number of directions that can be kept in the queue:

It turns out that two directions is all we need in our queue to make the game smoother, as that is about as many maximum key presses you would expect during a single frame lasting 200ms (think about how many times you’re able to press a key on a keyboard in one fifth of a second). Feel free to experiment with your own values.