dan

dan

groovin'

Recording player input

Now that we are rendering the player, our goal for this chapter is to use the arrow keys to control the player's movement across the grid. The first thing we need is an input mechanism to record which direction the player wants to move in.

The premise of the game Snake is that the snake never stops moving, even when the player is not inputting a direction. This means that we need a variable to record the lastKey a player has pressed. Luckily, it's very easy to see what keys the user is pressing in the browser. We will listen to all keydown events made on the entire page, and record which key has been pressed in the lastKey variable.

// Keyboard controls
let lastKey;
 
document.addEventListener("keydown", function (e) {
		lastKey = e.key;
});

Note

You can use this tool to see the key value of any key on the keyboard.

Updating the snake's position

That update function has been sitting empty this whole time - but no more! The first thing we must consider is the start state of the game. We want the snake to be stationary until the player presses a key to tell the snake which direction to travel. Let's immediately cancel the update if no key has been pressed, yet.

function update() {
	if(lastKey == undefined) {
		return;
	}
}

Question

At this point, we should think about how to update the snake, since each tile is an element in the player array. If you press the up key, for example, how could we tell the head of the snake to move upward one block, and how could we get the body of the snake to follow behind?

Solution

Although there are many solutions to this problem, some are more difficult than others. The easiest method is to remove the tail of the snake, and add an element to the front of the snake. That way, even if the snake is 20 tiles long, we only have to update 2 tiles, which is much more efficient than updating the position of 20 elements in the snake!

  1. Remove the last element in the array to delete the tail of the snake

    player.pop();

  2. Copy the first element in the array (the head of the snake)

    let head = Object.assign({}, player[0]);

    This is a confusing line of code. We are copying the head of the snake at player[0] into an empty object {} and returning it to the variable head. If we use head = player[0], we would be referencing player[0] instead of copying it.

  3. Increment or decrement the x or y coordinate based upon lastKey

    if(lastKey == "ArrowLeft") {
      --head.x;
    }
    else if(lastKey == "ArrowRight") {
      ++head.x;
    }
    else if(lastKey == "ArrowUp") {
      --head.y;
    }
    else if(lastKey == "ArrowDown") {
      ++head.y;
    }
  4. Add the element to the beginning of the array

    player.unshift(head);

Now your update function should look like this

function update() {
  if(lastKey == undefined) {
    return;
  }
 
  // Remove the last element in the array
  player.pop();
 
  // This is a confusing line of code. We are copying the head of the snake at player[0]
  // into an empty object {} and returning it to the variable `head`.
  // If we use `head = player[0]`, we would be referencing `player[0]` instead of copying it.
  let head = Object.assign({}, player[0]);
 
  if(lastKey == "ArrowLeft") {
    --head.x;
  }
  else if(lastKey == "ArrowRight") {
    ++head.x;
  }
  else if(lastKey == "ArrowUp") {
    --head.y;
  }
  else if(lastKey == "ArrowDown") {
    ++head.y;
  }
 
  // Add the head of the snake to the beginning of the array
  player.unshift(head);
}

Unfinished business

If you press any key that isn't one of the arrowkeys, you'll notice that we have a bug in our game. Let's fix it by only allowing the lastKey to be updated if the player presses an arrowkey.

document.addEventListener("keydown", function (e) {
	if(e.key == "ArrowLeft" || e.key == "ArrowRight" || e.key == "ArrowUp" || e.key == "ArrowDown") {
		lastKey = e.key;
	}
});

Great job!

We're starting to have a real game! But it's not very fun if your snake can disappear offscreen. In the next chapter we'll fix that by implementing a mechanism that's common in arcade games: screen wrap.