Setup the game¶
Right now, we are setting up our game by calling functions in the global scope. Let's refactor our setup into a function called setup so that we will easily be able to reset the game. This function should go above update and render. setup is the last lifecyle method that we will implement in this tutorial.
function setup() {
gameOver = false;
lastKey = undefined;
player = [
{
x: 4,
y: 5
},
{
x: 3,
y: 5
},
{
x: 2,
y: 5
},
{
x: 1,
y: 5
}
];
createApple();
}
Call the setup function at the bottom of the file, just before the setInterval.
// Setup the game before starting the game loop
setup();
// This will call the function main at 10fps and start the game
setInterval(main, 1000 / 10);
Now that our setup function is working, it's time to clean up the code that we left dangling in the global scope. Remove the extraneous call to createApple that's sitting outside of the setup function. You can also remove the initialization of player that's in the global scope by changing let player = ... to let player;
Game over - revisited¶
We've already programmed our gameover state, but let's add some pizazz by rendering a gameover screen. To render font on the canvas in JavaScript, we need to specify the font, the fillStyle, and the textAlign. These functions all have defaults, but we will want to change them. After setting those properties, use fillText to draw the text onscreen. Let's tell the player that the game is over and that he can hit space to restart. In order to center the text onscreen, we can set textAlign to center and divide the width and height of our canvas by 2 to get the x and y coordinates of the canvas' centerpoint.
ctx.font = "50px serif";
ctx.fillStyle = "red";
ctx.textAlign = "center";
ctx.fillText("GAME OVER", width / 2, height / 2);
ctx.font = "25px serif";
ctx.fillText("Press Space to Restart", width / 2, height / 2 + 50);
Note
We should only draw this gameover screen if the game is indeed over. Let's check the gameOver state in the render function.
function render() {
// Draw background
ctx.fillStyle = "black";
ctx.fillRect(0, 0, width, height);
if(!gameOver) {
// Draw grid
for(var i = 0; i < tilesX; ++i) {
for(var j = 0; j < tilesY; ++j) {
// Set the default tile color
ctx.fillStyle = "blue";
// Detect if the player is in this tile
for(var k = 0; k < player.length; ++k) {
if(player[k].x == i && player[k].y == j) {
ctx.fillStyle = "green";
break;
}
}
drawTile(i,j);
}
}
// Draw apple
ctx.fillStyle = "red";
drawTile(apple.x, apple.y);
}
else {
// Draw GameOver screen
ctx.font = "50px serif";
ctx.fillStyle = "red";
ctx.textAlign = "center";
ctx.fillText("GAME OVER", width / 2, height / 2);
ctx.font = "25px serif";
ctx.fillText("Press Space to Restart", width / 2, height / 2 + 50);
}
}
Press space to restart¶
It's time to make good on our promise. Let's update the logic in update to allow the player to restart the game by pressing space. Instead of calling return if gameOver is true, we should check if lastKey is the spacebar. If the player hits the spacebar while the game is over, we want to restart the game again by calling setup. Whether or not we call setup, we stil want to return from the update loop before running any of the update logic.
function update() {
if(lastKey == undefined)
return;
if(gameOver) {
if(lastKey == " ") {
setup();
}
return;
}
//...
}
Remember how we whitelisted the characters that were allowed to be pressed in the event listener? We need to allow the space key to be pressed only when the game is over.
document.addEventListener("keydown", function (e) {
if(e.key == "ArrowLeft" || e.key == "ArrowRight" || e.key == "ArrowUp" || e.key == "ArrowDown" ||
gameOver && e.key == " ")
{
lastKey = e.key;
}
});
Success
Our snake game is replayable! But don't throw in the towel just yet. In the last chapter, we will add some polish to our game.
