Haven’t translated for a long time, the whole simple practice hand. This is the first time to experience the translation in the crowd into the translation, and the nuggets translation is not quite the same, as if there is no proofreading process… So… If the translation is not good, please forgive me

Reading on mobile can be bad, mainly for code blocks… It is recommended to read on the computer

Original address: https://medium.freecodecamp.org/think-like-a-programmer-how-to-build-snake-using-only-javascript-html-and-css-7b1479c333 9e

The into the address: https://www.zcfy.cc/article/think-like-a-programmer-how-to-build-snake-using-only-javascript-html-css

The code address: https://github.com/supergoat/snake

Demo address: https://snake-cdxejlircg.now.sh/

Hi 👋

Welcome aboard. Today we begin an exciting adventure where we will develop our very own snake game 🐍. Learn how to solve a problem by breaking it down into short steps. By the end of this journey, you’ll have learned something new and have the confidence to explore more on your own.

If you’re new to programming, Check out freeCodeCamp. This is a great learning site… That’s right… It’s totally free. That’s where I got my start.

All right, all right, back to business — ready to go?

You can find the full code here, and you can preview it online here.

Let’s start

Start by creating a new file, “snake. HTML”, which will contain all the code.

Since this is an HTML file, the first thing to do is declare
. Enter the following in the snake. HTML file:

<! DOCTYPE html><html> <head> <title>Snake Game</title> </head> <body> Welcome to Snake! </body></html>

Copy the code

Good. Next, open snake. HTML in your favorite browser. You should be able to see Welcome to Snake! .

Great start! 👊

Create a canvas

To complete the game, we need to use the

tag and draw the image with JavaScript.

Replace the welcome words in snake. HTML with the following code.

<canvas id="gameCanvas" width="300" height="300"><canvas>Copy the code

Id is used to identify the canvas and should always be specified. We’ll use it later to access the canvas. Width and height are the width and height of the canvas, respectively, and also need to be specified. In this case, the canvas is 300 px by 300 px.

The snake. HTML file should now look like this:

<! DOCTYPE html><html> <head> <title>Snake Game</title> </head> <body> <canvas id="gameCanvas" width="300" height="300"></canvas> </body></html>

Copy the code

If you refresh the page, you’ll find it blank. This is because by default, the canvas is empty and has no background. Let’s adjust. 🔧

Adds a background color and border to the canvas

To see the canvas, we can write some JavaScript code to put a border around it. To do this, we need to add the tag after the and write JavaScript code in it.

If you place the

Now let’s write some JavaScript code between the closed tags. The code is as follows:

<! DOCTYPE html><html> <head> <title>Snake Game</title> </head> <body> <canvas id="gameCanvas" width="300" Height ="300"></canvas> <script> /** constant **/ const CANVAS_BORDER_COLOUR = 'black'; const CANVAS_BACKGROUND_COLOUR = "white"; Var gameCanvas = document.getelementById ("gameCanvas"); Var CTX = gamecanvas.getContext ("2d"); // Select the background color of the canvas ctx.fillStyle = CANVAS_BACKGROUND_COLOUR; Strokestyle = CANVAS_BORDER_COLOUR; // Draw a "solid" rectangle to cover the entire canvas ctx.fillrect (0, 0, gamecanvas.width, gamecanvas.height); Ctx.strokerect (0, 0, gamecanvas.width, gamecanvas.height); </script> </body></html>Copy the code

First, get the Canvas element using the id (gameCanvas) specified earlier. We then get the “2D” context of the canvas, which means we will draw the image in 2D space.

Finally, we drew a 300 x 300 white rectangle with a black border. This rectangle covers the entire canvas starting at the top left corner (0,0).

If you reload snake. HTML in your browser, you’ll see a white block with a black border! Well done, now we have a canvas to build our gluttonous snake game on! 👏 For the next challenge!

Let’s use the coordinates to represent the gluttonous snake

In order for the game to work, we need to know where the snake is in the canvas. To do this, we represent the snake with a series of coordinates. So, to draw a horizontal snake in the middle of the canvas (150,150), we could write:

let snake = [
  {x: 150, y: 150},
  {x: 140, y: 150},
  {x: 130, y: 150},
  {x: 120, y: 150},
  {x: 110, y: 150},
];Copy the code

Notice that the y coordinate of all parts of the snake is 150. The x-coordinate of each part is 10 px more than that of the previous part. The first pair of coordinates in the array {x: 150, y: 150} represent the head of the snake, located at the far right of the snake.

Don’t worry, we’ll have a clearer idea of this when we draw snakes next.

Began to draw the snake

To draw a snake, we can write a function to draw a rectangle for each part of the snake.

function drawSnakePart(snakePart) {
  ctx.fillStyle = 'lightgreen';
  ctx.strokestyle = 'darkgreen';
  ctx.fillRect(snakePart.x, snakePart.y, 10, 10);
  ctx.strokeRect(snakePart.x, snakePart.y, 10, 10);
}Copy the code

Next, we use another function to display the snake on the canvas.

function drawSnake() {
  snake.forEach(drawSnakePart);
}Copy the code

At this point, the snake. HTML file should look like this.

<! DOCTYPE html><html> <head> <title>Snake Game</title> </head> <body> <canvas id="gameCanvas" width="300" Height ="300"></canvas> <script> /** constant **/ const CANVAS_BORDER_COLOUR = 'black'; const CANVAS_BACKGROUND_COLOUR = "white"; const SNAKE_COLOUR = 'lightgreen'; const SNAKE_BORDER_COLOUR = 'darkgreen'; let snake = [ {x: 150, y: 150}, {x: 140, y: 150}, {x: 130, y: 150}, {x: 120, y: 150}, {x: 110, y: Var gameCanvas = document.getelementById ("gameCanvas"); Var CTX = gamecanvas.getContext ("2d"); // Select the background color of the canvas ctx.fillStyle = CANVAS_BACKGROUND_COLOUR; Strokestyle = CANVAS_BORDER_COLOUR; // Draw a "solid" rectangle to cover the entire canvas ctx.fillrect (0, 0, gamecanvas.width, gamecanvas.height); Ctx.strokerect (0, 0, gamecanvas.width, gamecanvas.height); drawSnake(); Function drawSnake() {function drawSnake() {function drawSnake() { ForEach (drawSnakePart)} /** * Draw a part of the snake on the canvas * @param {object} snakePart -- the coordinates of the part to draw */ function DrawSnakePart (snakePart) {ctx.fillstyle = SNAKE_COLOUR; drawSnakePart(snakePart) {ctx.fillstyle = SNAKE_COLOUR; Strokestyle = SNAKE_BORDER_COLOUR; ctx.strokestyle = SNAKE_BORDER_COLOUR; // Draw a "solid" rectangle to represent the snake ctx.fillrect (snakePart.x, snakePart.y, 10, 10); StrokeRect (snakepart. x, snakepart. y, 10, 10); } </script> </body></html>

Copy the code

Refresh the page and you’ll see a green snake in the middle of the canvas. 666! 😎

Move the snake sideways

Next we want to get the snake moving. But how do we do that? 🤔

Well, in order for the snake to move to the far right step by step (10 px), we can increase the x coordinate of each part of the snake by 10 px at a time. Similarly, to move the snake to the far left, reduce the x-coordinate of each part of the snake by 10 px at a time.

Dx is the lateral velocity of the snake.

After the snake has moved 10 px to the right, the coordinates will look like this:

Update the state of the snake with the advanceSnake function.

function advanceSnake() {    const head = {x: snake[0].x + dx, y: snake[0].y};
  snake.unshift(head);
  snake.pop();
}Copy the code

First, we drew a new head for the snake. Then use the unshift method to place the new head on the first part of the snake, and use the POP method to remove the last part of the snake. After that, as shown in the image above, all the rest of the snake moves into position.

Boom 💥, you’re getting the hang of it.

Move the snake lengthwise

In order for the snake to move up and down, we can’t just change all the y coordinates of the snake by 10 px. That makes the whole snake move up and down.

Instead, we can adjust the y-coordinate of the snake’s head. Reducing the snake by 10 px will move it down, increasing the snake by 10 px will move it up. This will allow the snake to move properly.

Fortunately, this is easy to do because the advanceSnake function we’ve written is great. In the advanceSnake function, update the head so that the y coordinate varies with dy.

const head = {x: snake[0].x + dx, y: snake[0].y + dy};Copy the code

To verify that the advanceSnake function is correct, we can temporarily call it before the drawSnake function.

AdvanceSnake (); advanceSnake(); // Change the vertical velocity to 10dy = -10; // Step up advanceSnake(); DrawSnake ();Copy the code

Now the code for snake. HTML looks like this:

<! DOCTYPE html><html> <head> <title>Snake Game</title> </head> <body> <canvas id="gameCanvas" width="300" Height ="300"></canvas> <script> /** constant **/ const CANVAS_BORDER_COLOUR = 'black'; const CANVAS_BACKGROUND_COLOUR = "white"; const SNAKE_COLOUR = 'lightgreen'; const SNAKE_BORDER_COLOUR = 'darkgreen'; let snake = [ {x: 150, y: 150}, {x: 140, y: 150}, {x: 130, y: 150}, {x: 120, y: 150}, {x: 110, y: 150}] // let dx = 10; // let dy = 0; Var gameCanvas = document.getelementById ("gameCanvas"); Var CTX = gamecanvas.getContext ("2d"); // Select the background color of the canvas ctx.fillStyle = CANVAS_BACKGROUND_COLOUR; Strokestyle = CANVAS_BORDER_COLOUR; // Draw a "solid" rectangle to cover the entire canvas ctx.fillrect (0, 0, gamecanvas.width, gamecanvas.height); Ctx.strokerect (0, 0, gamecanvas.width, gamecanvas.height); // advanceSnake(); // Change the vertical velocity to 10 dy = -10; // Step up advanceSnake(); DrawSnake (); / function advanceSnake() {const head = {x: snake[0]. X + dx, y: const head = {x: snake[0]. snake[0].y + dy}; snake.unshift(head); snake.pop(); } function drawSnake() {function drawSnake() {function drawSnake() { ForEach (drawSnakePart)} /** * Draw a part of the snake on the canvas * @param {object} snakePart -- the coordinates of the part to draw */ function DrawSnakePart (snakePart) {ctx.fillstyle = SNAKE_COLOUR; drawSnakePart(snakePart) {ctx.fillstyle = SNAKE_COLOUR; Strokestyle = SNAKE_BORDER_COLOUR; ctx.strokestyle = SNAKE_BORDER_COLOUR; // Draw a "solid" rectangle to represent the snake ctx.fillrect (snakePart.x, snakePart.y, 10, 10); StrokeRect (snakepart. x, snakepart. y, 10, 10); } </script> </body></html>

Copy the code

Refresh the page and the snake moves. A: congratulations!

Refactor the code

Before we move on, let’s refactor our existing code to encapsulate the code that draws the canvas in a function. This helps with the next step.

Code refactoring is the process of refactoring existing computer code without changing its external behavior.” — Wikipedia

function clearCanvas() {
  ctx.fillStyle = "white";
  ctx.strokeStyle = "black";
  ctx.fillRect(0, 0, gameCanvas.width, gameCanvas.height);
  ctx.strokeRect(0, 0, gameCanvas.width, gameCanvas.height);
}Copy the code

We are making great strides! 🐾

Make the snake move on its own

Okay, now that we’ve successfully refactoring the code, let’s make the gluttonous Snake move automatically.

We called the advanceSnake function twice earlier to test it for correctness. Once to move the snake to the right and once to move it up.

So if we want the snake to move right five times, we need to call advanceSnake() five times in a row.

clearCanvas(); advanceSnake(); advanceSnake(); advanceSnake(); advanceSnake(); advanceSnake(); drawSnake();

Copy the code

However, as shown above, calling advanceSnake five times in a row causes the snake to jump forward 50 px.

Instead, we want the snake to appear to be advancing step by step.

To do this, we use setTimeout to add a short delay between each call. We also need to ensure that the drawSnake function is called every time the advanceSnake function is called. If we don’t, we won’t be able to see the intermediate steps of the snake’s movement.

setTimeout(function onTick() { clearCanvas(); advanceSnake(); drawSnake(); }, 100); setTimeout(function onTick() { clearCanvas(); advanceSnake(); drawSnake(); }, 100); . drawSnake();Copy the code

Note that we also call the clearCanvas() function in each setTimeout. This is to remove all of the snake’s previous positions so it doesn’t leave a trace.

Still, there are problems with the code. Nothing tells the program that it must wait for a setTimeout to complete before moving to the next setTimeout. That means the snake will still jump 50 px forward after a short delay.

To solve this problem, we must wrap our code in functions and call only one function at a time.

stepOne(); function stepOne() { setTimeout(function onTick() { clearCanvas(); advanceSnake(); drawSnake(); // Call the second function stepTwo(); }, 100) }function stepTwo() { setTimeout(function onTick() { clearCanvas(); advanceSnake(); drawSnake(); // call the third function stepThree(); }}, 100)...Copy the code

How do you keep the snake moving? Instead of creating countless functions that call each other, we can create a single main function and call it recursively.

function main() { setTimeout(function onTick() { clearCanvas(); advanceSnake(); drawSnake(); // Call main() again; }}, 100)Copy the code

Look! The snake will now keep moving to the right. Although once it reaches the end of the canvas, it will continue its endless journey and enter unknown places 😅. We will solve this problem in due course, be patient young friends. 🙏.

Adjust the direction of the snake’s movement

Our next task is to change the direction of the snake’s movement when any arrow key is pressed. Add the following code after the drawSnakePart function.

function changeDirection(event) { const LEFT_KEY = 37; const RIGHT_KEY = 39; const UP_KEY = 38; const DOWN_KEY = 40; const keyPressed = event.keyCode; const goingUp = dy === -10; const goingDown = dy === 10; const goingRight = dx === 10; const goingLeft = dx === -10; if (keyPressed === LEFT_KEY && ! goingRight) { dx = -10; dy = 0; } if (keyPressed === UP_KEY && ! goingDown) { dx = 0; dy = -10; } if (keyPressed === RIGHT_KEY && ! goingLeft) { dx = 10; dy = 0; } if (keyPressed === DOWN_KEY && ! goingDown) { dx = 0; dy = 10; }}Copy the code

There’s nothing special about it. We check that the key pressed matches one of the arrow keys. If matched, we change the vertical and horizontal velocity as described above.

Note that we also check to see if the snake is moving in the opposite direction of the new expected direction. This is to prevent our snake from turning around, for example by pressing the right button when the snake moves to the left.

To add changeDirection to the game code, you can use the addEventListener on the Document to “listen” for keys being pressed. We then call the changeDirection method with the KeyDown event. Then add the following code after the main function.

document.addEventListener("keydown", changeDirection)Copy the code

You should now be able to change the snake’s direction using the four arrow keys. Well done, you fire 🔥!

Now let’s look at how to generate food and grow our snakes.

It makes food for the snake

To generate snake food, we have to generate a random set of coordinates. We can use the auxiliary function randomTen to generate two numbers. One for the x coordinate and one for the y coordinate.

We also have to make sure the food doesn’t overlap with the snake. If it overlaps, we have to generate a new food location.

function randomTen(min, max) { return Math.round((Math.random() * (max-min) + min) / 10) * 10; }function createFood() { foodX = randomTen(0, gameCanvas.width - 10); foodY = randomTen(0, gameCanvas.height - 10); snake.forEach(function isFoodOnSnake(part) { const foodIsOnSnake = part.x == foodX && part.y == foodY if (foodIsOnSnake)  createFood(); }); }Copy the code

Then we need to write a function that draws food on the canvas.

function drawFood() {
 ctx.fillStyle = 'red';
 ctx.strokestyle = 'darkred';
 ctx.fillRect(foodX, foodY, 10, 10);
 ctx.strokeRect(foodX, foodY, 10, 10);
}Copy the code

Finally, we can call createFood before calling main. Don’t forget to update the main function to call the drawFood function.

function main() { setTimeout(function onTick() { clearCanvas(); drawFood() advanceSnake(); drawSnake(); main(); }}, 100)Copy the code

Let the snake grew up

Growing a snake is easy. Update the advanceSnake function to check if the snake’s head has touched the food. If so, we can skip removing the last part of the snake and create a new food location.

function advanceSnake() { const head = {x: snake[0].x + dx, y: snake[0].y}; snake.unshift(head); const didEatFood = snake[0].x === foodX && snake[0].y === foodY; if (didEatFood) { createFood(); } else { snake.pop(); }}Copy the code

Keep track of game scores

To make the game even more fun, we can also add a score that increases when the snake eats food.

After you declare Snake, create a new variable score and set it to 0.

let score = 0;Copy the code

Next, add a new div with id “Score” in front of the canvas to display the score.

<div id="score">0</div>
<canvas id="gameCanvas" width="300" height="300"></canvas>Copy the code

Last updated advanceSnake, which increases and displays scores when snakes eat food.

function advanceSnake() { ... if (didEatFood) { score += 10; document.getElementById('score').innerHTML = score; createFood(); } else { ... }}Copy the code

Shout… It’s not easy, but victory is at hand 😌

End of the game

There is one last part left, which is ending the game 🖐. Create a function for this, didGameEnd, that returns true when the game is over and false otherwise.

function didGameEnd() {    for (let i = 4; i < snake.length; i++) {          const didCollide = snake[i].x === snake[0].x && snake[i].y === snake[0].y          if (didCollide) return true
  }    const hitLeftWall = snake[0].x < 0;    const hitRightWall = snake[0].x > gameCanvas.width - 10;    const hitToptWall = snake[0].y < 0;    const hitBottomWall = snake[0].y > gameCanvas.height - 10;    return       hitLeftWall || 
      hitRightWall || 
      hitToptWall ||
      hitBottomWall
} 
Copy the code

First, we check to see if the head of the snake is touching the rest of the snake, and return true if so.

Notice that we start the loop with the index value of 4. There are two reasons for this: one is that if the index is 0, didCollide immediately decides it’s true and the game is over. Another is that the first three parts of the snake can’t possibly touch each other.

Next we check to see if the snake hit the wall on the canvas, return true if so, false otherwise.

Now, if didEndGame returns true, we can return early in the main function and end the game.

function main() { if (didGameEnd()) return; . }Copy the code

Snake. HTML should now look like this:

<! DOCTYPE html><html> <head> <title>Snake Game</title> </head> <body> <div id="score">0</div> <canvas id="gameCanvas" Width ="300" height="300"></canvas> <script> /** constant **/ const CANVAS_BORDER_COLOUR = 'black'; const CANVAS_BACKGROUND_COLOUR = "white"; const SNAKE_COLOUR = 'lightgreen'; const SNAKE_BORDER_COLOUR = 'darkgreen'; const FOOD_COLOUR = 'red'; const FOOD_BORDER_COLOUR = 'darkred'; let snake = [ {x: 150, y: 150}, {x: 140, y: 150}, {x: 130, y: 150}, {x: 120, y: 150}, {x: 110, y: 150}] // let score = 0; // let dx = 10; // let dy = 0; Var gameCanvas = document.getelementById ("gameCanvas"); Var CTX = gamecanvas.getContext ("2d"); // Select the background color of the canvas ctx.fillStyle = CANVAS_BACKGROUND_COLOUR; Strokestyle = CANVAS_BORDER_COLOUR; // Draw a "solid" rectangle to cover the entire canvas ctx.fillrect (0, 0, gamecanvas.width, gamecanvas.height); Ctx.strokerect (0, 0, gamecanvas.width, gamecanvas.height); // start the game main(); // Create the first food position createFood(); / / press any key, will be called changeDirection document. The addEventListener (" keydown ", changeDirection); function main() { if (didGameEnd()) return; setTimeout(function onTick() { clearCanvas(); drawFood(); advanceSnake(); drawSnake(); // Call main again main(); }, 100)} /** * Set the canvas background color to CANVAS_BACKGROUND_COLOUR * and draw the canvas border */ function clearCanvas() {// Select the canvas background color ctx.fillstyle = CANVAS_BACKGROUND_COLOUR; Strokestyle = CANVAS_BORDER_COLOUR; // Draw a "solid" rectangle to cover the entire canvas ctx.fillrect (0, 0, gamecanvas.width, gamecanvas.height); Ctx.strokerect (0, 0, gamecanvas.width, gamecanvas.height); } /** * return true */ function didGameEnd() {for (let I = 4; i < snake.length; i++) { const didCollide = snake[i].x === snake[0].x && snake[i].y === snake[0].y if (didCollide) return true } const hitLeftWall = snake[0].x < 0; const hitRightWall = snake[0].x > gameCanvas.width - 10; const hitToptWall = snake[0].y < 0; const hitBottomWall = snake[0].y > gameCanvas.height - 10; Return hitLeftWall | | hitRightWall | | hitToptWall | | hitBottomWall} / * * * * / function on the canvas painting food drawFood () { ctx.fillStyle = FOOD_COLOUR; ctx.strokestyle = FOOD_BORDER_COLOUR; ctx.fillRect(foodX, foodY, 10, 10); ctx.strokeRect(foodX, foodY, 10, 10); } /** ** ** */ function advanceSnake() {// Draw a new head const head = {x: snake[0].x + dx, y: snake[0].y + dy}; // Place the new head on the first part of the snake's body. const didEatFood = snake[0].x === foodX && snake[0].y === foodY; If (didEatFood) {// Add score += 10; // Display the score on the screen document.getelementById ('score').innerhtml = score; CreateFood (); } else {// Remove the last part of the snake snake.pop(); }} /** * Given a maximum and minimum value, @param {number} Max @param {number} Max @param {number} Max @param {number} Max @param {number} Max @param {number} Max max) { return Math.round((Math.random() * (max-min) + min) / 10) * 10; */ function createFood() {foodX = randomTen(0, gamecanvas.width -10); FoodY = randomTen(0, gamecanvas.height-10); foodY = randomTen(0, gamecanvas.height-10); // If the newly generated food overlaps the snake's current position, ForEach (function isOnSnake(part) {if (part.x == foodX && part.y == foodY) createFood(); }); } function drawSnake() {function drawSnake() {function drawSnake() { ForEach (drawSnakePart)} /** * Draw a part of the snake on the canvas * @param {object} snakePart -- the coordinates of the part to draw */ function DrawSnakePart (snakePart) {ctx.fillstyle = SNAKE_COLOUR; drawSnakePart(snakePart) {ctx.fillstyle = SNAKE_COLOUR; Strokestyle = SNAKE_BORDER_COLOUR; ctx.strokestyle = SNAKE_BORDER_COLOUR; // Draw a "solid" rectangle to represent the snake ctx.fillrect (snakePart.x, snakePart.y, 10, 10); StrokeRect (snakepart. x, snakepart. y, 10, 10); } /** * Change the speed of the snake's horizontal movement and vertical movement depending on the key pressed * To avoid reversing the snake, the direction of the snake's movement should not change directly to the opposite direction * for example, the current direction is "to the right", Function changeDirection(event) {const LEFT_KEY = 37; / / function changeDirection(event) {const LEFT_KEY = 37; const RIGHT_KEY = 39; const UP_KEY = 38; const DOWN_KEY = 40; const keyPressed = event.keyCode; const goingUp = dy === -10; const goingDown = dy === 10; const goingRight = dx === 10; const goingLeft = dx === -10; if (keyPressed === LEFT_KEY && ! goingRight) { dx = -10; dy = 0; } if (keyPressed === UP_KEY && ! goingDown) { dx = 0; dy = -10; } if (keyPressed === RIGHT_KEY && ! goingLeft) { dx = 10; dy = 0; } if (keyPressed === DOWN_KEY && ! goingUp) { dx = 0; dy = 10; } } </script> </body></html>Copy the code

Now that the snake game is available, you can play it alone or share it with your friends. But before we celebrate, let’s look at one last question. I promise, this is definitely the last one.

Hidden bugs 🐛

If you play the game enough times, you may notice that sometimes the game ends unexpectedly. This is a good example of how bugs can creep into our programs and cause problems 🙄.

When you find a bug, the best way to fix it is to first find a reliable way to reproduce it. That is, finding the precise steps that lead to unexpected behavior. Then, you need to understand why they lead to unexpected behavior, and finally look for solutions.

To reproduce the bug

In our example, the steps to reproduce the bug are as follows:

  • The snake is moving to the left

  • The player presses the down button

  • Player immediately right click (within 100 ms)

  • Game over

Analyze the cause of the bug

Let’s break down the bug process step by step.

The snake is moving to the left

  • The horizontal velocity dx is equal to minus 10

  • Call main

  • Call advanceSnake and move the snake 10 px to the left.

The player presses the down button

  • Call changeDirection

  • keyPressed === DOWN_KEY && ! The value of goingUp is true

  • Dx is changed to 0

  • So dy is going to be plus 10

Player immediately right click (within 100 ms)

  • Call changeDirection

  • keyPressed === RIGHT_KEY && ! The value of goingLeft is true

  • I’m going to change dx to plus 10

  • Dy is going to be 0

Game over

  • Main is called after a delay of 100 ms

  • Call advanceSnake and move the snake 10 px to the right.

  • Const didCollide = snake[I].x == snake[0].x == snake[0].y == snake[0].y == snake[0]

  • DidGameEnd returns true

  • The main function returns early

  • Game over

To solve the bug

After analyzing what happened, we learned that the game ended because the snake turned around.

This is because the dx is set to 0 when the player presses the down key. So keyPressed === RIGHT_KEY &&! The goingLeft value is true, and dx is changed to 10.

It is important to note that within 100 ms, the direction changed. If it exceeds 100 ms, the snake will first move down rather than turn around.

To fix this bug, you must ensure that you can only change the direction of the snake after main and advanceSnake have been called. You can create a variable **changingDirection. ** Set changingDirection to true when calling changeDirection and false when calling advanceSnake.

In changeDirection, if changingDirection is true, it returns early.

function changeDirection(event) { const LEFT_KEY = 37; const RIGHT_KEY = 39; const UP_KEY = 38; const DOWN_KEY = 40; if (changingDirection) return; changingDirection = true; . }function main() { setTimeout(function onTick() { changingDirection = false; . }}, 100)Copy the code

Here is the final version of snake

Note that I also added some styles 🎨 between the

tags. This is to keep the canvas and score in the middle of the screen.

<! DOCTYPE html><html> <head> <title>Snake Game</title> <link href="https://fonts.googleapis.com/css?family=Antic+Slab" rel="stylesheet"> </head> <body> <div id="score">0</div> <canvas id="gameCanvas" width="300" height="300"></canvas> <style> #gameCanvas { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } #score { text-align: center; font-size: 140px; font-family: 'Antic Slab', serif; } </style> </body> <script> const GAME_SPEED = 100; const CANVAS_BORDER_COLOUR = 'black'; const CANVAS_BACKGROUND_COLOUR = "white"; const SNAKE_COLOUR = 'lightgreen'; const SNAKE_BORDER_COLOUR = 'darkgreen'; const FOOD_COLOUR = 'red'; const FOOD_BORDER_COLOUR = 'darkred'; let snake = [ {x: 150, y: 150}, {x: 140, y: 150}, {x: 130, y: 150}, {x: 120, y: 150}, {x: 110, y: 150}] // let score = 0; Let changingDirection = false; // let foodX; // Let foodY; // let dx = 10; // let dy = 0; // Get canvas element const gameCanvas = document.getelementById ("gameCanvas"); Const CTX = gamecanvas.getContext ("2d"); // Return a 2d drawing context const CTX = gamecanvas.getContext ("2d"); // start the game main(); // Create the first food position createFood(); / / press any key, will be called changeDirection document. The addEventListener (" keydown ", changeDirection); If (didGameEnd()) return; if (didGameEnd()) return; setTimeout(function onTick() { changingDirection = false; clearCanvas(); drawFood(); advanceSnake(); drawSnake(); // Call main main() again; }, GAME_SPEED)} /** * Set the canvas's background color to CANVAS_BACKGROUND_COLOUR * and draw the canvas's border */ function clearCanvas() {// Select the canvas's background color ctx.fillstyle  = CANVAS_BACKGROUND_COLOUR; Strokestyle = CANVAS_BORDER_COLOUR; // Draw a "solid" rectangle to cover the entire canvas ctx.fillrect (0, 0, gamecanvas.width, gamecanvas.height); Ctx.strokerect (0, 0, gamecanvas.width, gamecanvas.height); } / / function drawFood() {ctx.fillstyle = FOOD_COLOUR; ctx.strokestyle = FOOD_BORDER_COLOUR; ctx.fillRect(foodX, foodY, 10, 10); ctx.strokeRect(foodX, foodY, 10, 10); } /** ** ** */ function advanceSnake() {// Draw a new head const head = {x: snake[0].x + dx, y: snake[0].y + dy}; // Place the new head on the first part of the snake's body. const didEatFood = snake[0].x === foodX && snake[0].y === foodY; If (didEatFood) {// Add score += 10; // Display the score on the screen document.getelementById ('score').innerhtml = score; CreateFood (); } else {// Remove the last part of the snake snake.pop(); }} / / function didGameEnd() {for (let I = 4; i < snake.length; i++) { if (snake[i].x === snake[0].x && snake[i].y === snake[0].y) return true } const hitLeftWall = snake[0].x < 0; const hitRightWall = snake[0].x > gameCanvas.width - 10; const hitToptWall = snake[0].y < 0; const hitBottomWall = snake[0].y > gameCanvas.height - 10; Return hitLeftWall | | hitRightWall | | hitToptWall | | hitBottomWall} / * * * given a maximum and minimum value, @param {number} Max @param {number} Max @param {number} Max @param {number} Max @param {number} Max @param {number} Max max) { return Math.round((Math.random() * (max-min) + min) / 10) * 10; */ function createFood() {foodX = randomTen(0, gamecanvas.width -10); FoodY = randomTen(0, gamecanvas.height-10); foodY = randomTen(0, gamecanvas.height-10); // If the newly generated food overlaps the snake's current position, ForEach (function isFoodOnSnake(part) {const foodIsoNsnake = part.x == foodX && part.y == foodY; if (foodIsoNsnake) createFood(); }); } function drawSnake() {function drawSnake() {function drawSnake() { ForEach (drawSnakePart)} /** * Draw a part of the snake on the canvas * @param {object} snakePart -- the coordinates of the part to draw */ function DrawSnakePart (snakePart) {ctx.fillstyle = SNAKE_COLOUR; drawSnakePart(snakePart) {ctx.fillstyle = SNAKE_COLOUR; Strokestyle = SNAKE_BORDER_COLOUR; ctx.strokestyle = SNAKE_BORDER_COLOUR; // Draw a "solid" rectangle to represent the snake ctx.fillrect (snakePart.x, snakePart.y, 10, 10); StrokeRect (snakepart. x, snakepart. y, 10, 10); } /** * Change the speed of the snake's horizontal movement and vertical movement depending on the key pressed * To avoid reversing the snake, the direction of the snake's movement should not change directly to the opposite direction * for example, the current direction is "to the right", Function changeDirection(event) {const LEFT_KEY = 37; / / function changeDirection(event) {const LEFT_KEY = 37; const RIGHT_KEY = 39; const UP_KEY = 38; const DOWN_KEY = 40; /** * Avoid snake turning around * for example: * Snake is moving to the right. The player presses the down button and then quickly presses the left button. * If (changingDirection) return */ if (changingDirection) return; changingDirection = true; const keyPressed = event.keyCode; const goingUp = dy === -10; const goingDown = dy === 10; const goingRight = dx === 10; const goingLeft = dx === -10; if (keyPressed === LEFT_KEY && ! goingRight) { dx = -10; dy = 0; } if (keyPressed === UP_KEY && ! goingDown) { dx = 0; dy = -10; } if (keyPressed === RIGHT_KEY && ! goingLeft) { dx = 10; dy = 0; } if (keyPressed === DOWN_KEY && ! goingUp) { dx = 0; dy = 10; } } </script></html>

Copy the code

At the end

A: congratulations! 🎉 👏

We have completed the journey. I hope you enjoy learning with me and have the confidence to move on to the next adventure.

But don’t say goodbye just yet. My next article will focus on how to help you open up the very exciting world of open source.

Open source is a great way to learn lots of new things and meet the big boys. It may not be daring at first, but it’s definitely worth it.

If you want to be notified of my next post, follow me! 📫

I’m so happy to be on this journey with you.

Look forward to seeing you next time. ✨