Our game is off to a great start - unless you begin the game by hitting the left arrowkey, that is. We assume the player knows which direction the snake starts in. This bug persists regardless of where the snake is in the grid. By telling the snake to move backwards, the player will immidiately die! That makes our game a little less fun.
The first part of our solution is to introduce a map of deniedKeys. We want the game to know that if the player is currently travelling to the right, he cannot move to the left, and so on.
Now, let's change our eventListener to use this denied list, so that only allowed keys get recorded in the lastKey variable.
If you run the game now, you will notice that it sort of has the correct affect. The player is not able to immidiately die by pressing the left arrow anymore, but a subtle bug in the code remains. When moving to the right, try pressing up and left very quickly. Or when moving up, try pressing left and down as fast as you can, and so on. Both of these examples are valid moves. We shouldn't be getting a gameover unless the head of the snake hits another tile that the snake occupies.
The reason this bug is happening is that our keydown event listener is running out-of-band from the update function. Because the game has such a low framerate, it is possible for the player to quickly hit multiple keys before the update function is called, and that can subvert the logic we just wrote to disallow players from making the snake go backwards to accidentally get a gameover.
In order to fix this, we must buffer the input, and only update lastKey in the update function. To begin, let's create a variable to hold our bufferedKey. Then we can simplify our event listener to update the buffer whenever a key is pressed.
We still want all of the logic that was in our event listener. Let's factor it out into a new function so we can use it to update lastKey whenever we want to. Since this code is no longer living in an event listener, we will replace e.key with the bufferedKey variable.
To put the finishing touch on this feature, we need to call the getKey function at the beginning of the update loop.
Great!
Now the game seems much more fluid. There's nothing a player hates more than an unfair game. On the flip side, there's nothing a player loves more than beating his friends. So...
The last feature of Snake that our game is missing is keeping score! In Snake, the player's length is usually the score, although you could get fancy and factor in the amount of time that the player has stayed alive as well.
A challenger approaches
At this point in the course, you have all of the tools that you need to implement keeping track of the score and drawing it on the screen. If you're up for a challenge, you can pause here and try to write the code yourself - you might even like your solution better than mine! My solution will be waiting for you until you're ready...
Let's begin with a distinction between the current score and the highscore. The current score will just be player.length, and the highscore will be set if the current score is greater than the highscore. We could set the highscore once gameOver == true in the update function; however, I prefer to set the highscore in the setup function. This may sound counter-intuitive, but hear me out. When the player is focused while playing, he may not realize that he has beaten the highscore. If the gameover screen shows that the current score is higher than the highscore, it's plainly obvious that the player beat the highscore. Then when a new game starts, the highscore will be updated.
Now that we have the score and highscore, it's time to render them on the screen! Let's position the score in the top-left, and the highscore in the top-right of the screen both during the game and after the game. You can write these lines of code at the very bottom of the render function. In order to keep the score out of the player's way, we'll make it 20 pixels.
Yellow will make the text stand out on both the blue grid and black gameover screen.
In order to render the the current score in the top left, we will set the textAlign to left. Putting the text at 10 pixels from the left will give it a little padding so that it's not jammed up against the left side of the canvas. I'll set the y axis origin at 20 pixels since our font is 20 pixels.
Since we want to put the highscore on the right side of the screen, let's set the textAlign to right. This means that we can conviniently set the x and y parameters of fillText to the same values that we used previously and the text layout will be mirrored on the opposite side of the screen.
Congrats for sticking with it!
Great job creating a classic arcade-style game. If this is your first HTML5 game, I want to appreciate what an achievement you've just made! Take a look at my other tutorials here on Scipress if you want to level up your game programming skills.
Final note
The entire source code is below in case you want to copy-paste it. Here is a link to the codepen if you want to quickly hack on the completed version of this game right in your browser.