dan

dan

groovin'

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.