像程序员一样思考:如何仅使用JavaScript,HTML和CSS来构建Snake

by Panayiotis Nicolaou

通过Panayiotis Nicolaou

像程序员一样思考:如何仅使用JavaScript,HTML和CSS来构建Snake (Think like a programmer: How to build Snake using only JavaScript, HTML & CSS)

Hello there ?

你好 ?

Welcome on board. Today we will embark on an exciting adventure, where we will be making our very own snake game ?. You’ll learn how to work through a problem by breaking it down into smaller simpler steps. By the end of this journey, you will have learned some new things, and you’ll feel confident to explore more on your own.

欢迎登船。 今天我们将踏上激动人心的冒险之旅,在那我们将制作自己的蛇游戏?。 您将通过分解成较小的简单步骤来学习如何解决问题。 到本旅程结束时,您将学到一些新东西,并且您会充满信心地自己进行更多探索。

If you are new to programming, I recommend checking out freeCodeCamp. It’s a great place to learn for…you guessed it…free. That’s how I got started ?

如果您不熟悉编程,建议您检出freeCodeCamp 。 这是一个学习的好地方……您猜对了……免费。 那就是我开始的方式?

Okay, okay enough messin’ around — are you ready to start?

好吧,好吧,您已经准备好开始了吗?

You can find the final code here and a live demo here.

你可以找到最终的代码在这里和现场演示这里

入门 (Getting Started)

Let’s begin by creating a file “snake.html” that will contain all our code.

让我们从创建一个包含所有代码的文件“ snake.html”开始。

Since this is an HTML file, the first thing we need is the <!DOCTYPE> declaration . In snake.html type the following:

由于这是一个HTML文件,因此我们首先需要的是<!DOCTY PE>声明. In snak . In snak e.html中,键入以下内容:

Great, now go ahead and open snake.html in your preferred browser. You should be able to see Welcome to Snake!

太好了,现在继续在您喜欢的浏览器中打开snake.html 。 您应该可以看到Welcome to Snake!

We are off to a good start ?

我们有一个良好的开端吗?

创建画布 (Creating the Canvas)

To be able to create our game, we have to make use of HTML <canvas> . This is what is used to draw graphics using JavaScript.

为了能够创建我们的游戏,我们必须使用HTML <canv as>。 这就是使用JavaScript绘制图形的方法。

Replace the welcome message in snake.html with the following:

用以下内容替换snake.html的欢迎消息:

<canvas id="gameCanvas" width="300" height="300">&lt;canvas>

The id is what identifies the canvas and should always be specified. We will use it to access the canvas later. The width and height are the dimensions of the canvas, and should also be specified. In this case, 300 x 300 pixels.

id是标识画布的内容,应始终指定。 我们稍后将使用它来访问画布。 宽度和高度是画布的尺寸,也应指定。 在这种情况下,为300 x 300像素。

Your snake.html file should now look like this.

您的snake.html文件现在应如下所示。

If you refresh your browser page where you previously opened snake.html you will now see a blank page. This is because, by default, the canvas is empty and has no background. Lets fix that. ?

如果您在以前打开snake.html浏览器页面上刷新,现在将看到一个空白页面。 这是因为,默认情况下,画布是空的并且没有背景。 让我们修复它。 ?

为画布赋予背景色和边框 (Give the canvas a background colour and a border)

To make our canvas visible, we can give it a border by writing some JavaScript code. To do that, we need to insert <script><;/script> tags after the </canvas>, where all our JavaScript code will go.

为了使我们的画布可见,我们可以通过编写一些JavaScript代码为它提供边框。 为此,我们需要在</ canvas> s after t插入<script>< ; / script>标签,所有JavaScript代码都将移至该位置。

If you put the <script> tag before the &lt;canvas> your code won’t work, as the HTML will not be loaded.

如果将<scri pt>标记e the &l t; canvas>之前,则代码将无法工作,因为将不会加载HTML。

We can now write some JavaScript code, between the enclosing<script><;/script> tags. Update your code as below.

现在,我们可以在封闭的<script>< ; / script>标记之间编写一些JavaScript代码。 如下更新代码。

First we get the canvas element using the id (gameCanvas) we specified earlier. We then get the canvas “2d” context, which means we will be drawing into 2D space.

首先,我们使用前面指定的ID(gameCanvas)获得canvas元素。 然后,我们获得画布的“ 2d”上下文,这意味着我们将绘制到2D空间中。

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

最后,我们绘制了一个带有黑色边框的300 x 300白色矩形。 从左上角(0,0)开始,它覆盖了整个画布。

If you reload snake.html in your browser, you should see a white box with a black border! Good job, we have a canvas that we can use to create our snake game! ? On to the next challenge!

如果在浏览器中重新加载snake.html ,应该会看到一个带有黑色边框的白色框! 做得好,我们有一块画布可以用来创建我们的蛇游戏! ? 继续下一个挑战!

代表我们的蛇 (Representing our snake)

For our snake game to work, we need to know the location of the snake on the canvas. To do that, we can represent the snake as an array of coordinates. Thus, to create a horizontal snake in the middle of the canvas (150, 150) we can write the following:

为了使我们的蛇游戏正常工作,我们需要知道蛇在画布上的位置。 为此,我们可以将蛇表示为坐标数组。 因此,要在画布中间(150、150)创建一条水平蛇,我们可以编写以下内容:

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

Notice that the y coordinate for all parts is always 150. The x coordinate of each part is -10px (to the left) of the previous part. The first pair of coordinates in the array {x: 150, y: 150} represents the head at the very right of the snake.

请注意,所有零件的y坐标始终为150。每个零件的x坐标为上一个零件的-10px(向左)。 数组{x: 150, y: 150}的第一对坐标表示蛇的最右边的头部。

This will become clearer when we draw the snake in the next section.

在下一节中绘制蛇时,这一点将变得更加清楚。

创建和绘制我们的蛇 (Creating and drawing our snake)

To display the snake on the canvas, we can write a function to draw a rectangle for each pair of coordinates.

为了在画布上显示蛇,我们可以编写一个函数为每对坐标绘制一个矩形。

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);}

Next we can create another function that prints the parts on the canvas.

接下来,我们可以创建另一个在画布上打印零件的函数。

function drawSnake() {  snake.forEach(drawSnakePart);}

Our snake.html file should now look like this:

我们的snake.html文件现在应如下所示:

If you refresh your browser page now you will see a green snake in the middle of the canvas. Awesome! ?

如果现在刷新浏览器页面,您将在画布中间看到一条绿色的蛇。 太棒了! ?

使蛇能够水平移动 (Enabling the snake to move horizontally)

Next we want to give the snake the ability to move. But how do we do that? ?

接下来,我们要赋予蛇移动的能力。 但是我们该怎么做呢? ?

Well, to make the snake move one step (10px) to the right, we can increase the x-coordinate of every part of the snake by 10px (dx = +10px). To make the snake move to the left, we can decrease the x-coordinate of every part of the snake by 10px (dx = -10).

好,要使蛇向右移动一个步长(10px),我们可以将蛇的每个部分的x坐标增加10px(dx = + 10px)。 为了使蛇向左移动,我们可以将蛇的每个部分的x坐标减小10px(dx = -10)。

dx is the horizontal velocity of the snake.

dx是蛇的水平速度。

Creating a snake that has moved 10px to the right should then look like this

然后创建一条向右移动10px的蛇

Create a function called advanceSnake that we will use to update the snake.

创建一个名为advanceSnake的函数,我们将使用它来更新蛇。

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

First we create a new head for the snake. We then add the new head to the beginning of snake using unshift and remove the last element of snake using pop. This way all the other snake parts shift into place as shown above.

首先,我们为蛇创建一个新的头部。 然后,我们使用unshift将新的头部添加到的开头,并使用pop删除的最后一个元素。 这样,所有其他蛇形部分都移到了上面,如图所示。

Boom ?, you are getting the hang of this.

Boom?,您已经掌握了这个技巧。

使蛇垂直移动 (Enabling the snake to move vertically)

To move our snake up and down, we can’t alter all y-coordinates by 10px. That would shift the whole snake up and down.

要上下移动蛇,我们不能将所有y坐标更改10px。 那将使整个蛇上下移动。

Instead we can alter the y-coordinate of the head. Decreasing it by 10px to move the snake down, and increasing it by 10px to move the snake up. This will make the snake move correctly.

相反,我们可以更改头部的y坐标。 将其减小10像素可将蛇向下移动,将其增大10像素可将蛇向上移动。 这将使蛇正确移动。

Luckily, because of the way we wrote the advanceSnake function, this is very easy to do. Inside advanceSnake, update the head to also increase the y-coordinate of the head by dy.

幸运的是,由于我们编写了advanceSnake函数的方式,这很容易做到。 在advanceSnake ,更新头部,也将dy的头部的y坐标增加。

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

To test how our advanceSnake function works, we can temporarily call it before the drawSnake function.

为了测试advanceSnake函数的工作方式,我们可以在drawSnake函数之前临时调用它。

// Move on step to the rightadvanceSnake()
// Change vertical velocity to 0dx = 0;// Change horizontal velocity to 10dy = -10;
// Move one step upadvanceSnake();
// Draw snake on the canvasdrawSnake();

This is how our snake.html file looks so far.

到目前为止,这就是snake.html文件的外观。

Refreshing the browser, we can see that our snake has moved. Success!

刷新浏览器,我们可以看到我们的蛇已经移动了。 成功!

重构我们的代码 (Refactoring our code)

Before we move on, let’s do some refactoring and move the code that draws the canvas inside a function. This will help us in the next section.

在继续之前,让我们进行一些重构,并移动在函数内绘制画布的代码。 这将在下一节中为我们提供帮助。

“Code refactoring is the process of restructuring existing computer code, without changing its external behaviour.” -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);}

We are making great strides! ?

我们正在大步前进! ?

让我们的蛇自动移动 (Making our snake move automatically)

Okay, now that we have successfully refactored our code, we can make our snake move automatically.

好的,既然我们已经成功地重构了代码,我们就可以使我们的蛇自动移动了。

Earlier, to test that our advanceSnake function worked, we called it twice. Once to make the snake move to the right, and once to make the snake move up.

之前,为了测试我们的advanceSnake函数是否有效,我们对其进行了两次调用。 一次使蛇向右移动,一次使蛇向上移动。

Thus if we wanted to make the snake move five steps to the right we would call advanceSnake() five times in a row.

因此,如果我们想让蛇向右移动五步,我们将advanceSnake()五次调用advanceSnake()

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

But, calling it five times in a row as shown above, will make the snake jump 50px forward.

但是,如上图所示连续调用五次,将使蛇向前跳跃50px。

Instead we want to make the snake appear to be moving forward step by step.

相反,我们希望使蛇看起来正在逐步向前移动。

To do that, we can add a slight delay between each call, using setTimeout. We also need to make sure to call drawSnake every time we call advanceSnake. If we don’t, we won’t be able to see the intermediate steps that show the snake moving.

为此,我们可以使用setTimeout在每次调用之间添加一点延迟。 我们还需要确保每次调用drawSnake时都调用advanceSnake 。 如果不这样做,我们将看不到显示蛇在移动的中间步骤。

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

Notice how we also call clearCanvas() inside each setTimeout. This is to remove all the previous positions of the snake that would leave a trail behind.

注意我们如何在每个setTimeout调用clearCanvas() 。 这是为了删除蛇的所有先前位置,这些位置会留下痕迹。

Although, there is a problem with the above code. There is nothing here to tell the program that it has to wait for setTimeout before it moves to the next setTimeout. This means that the snake will still jump 50px forward but after a slight delay.

虽然,上面的代码有问题。 这里没有什么可以告诉程序它必须等待setTimeout才能移到下一个setTimeout 。 这意味着蛇仍然会向前跳50px,但稍有延迟

To fix that, we have to wrap our code inside functions, calling 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)}
...

How do we make our snake keep moving? Instead of creating an infinite number of functions that call each other, we can instead create one function main and call it over and over again.

我们如何使蛇不断运动? 与其创建无限个彼此调用的函数,不如创建一个main函数并一遍又一遍地调用它。

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

Voilà! We now have a snake that will keep moving to the right. Although, once it reaches the end of the canvas, it continues its infinite journey into the unknown ?. We will fix that in due time, patience young padawan. ?.

瞧! 我们现在有了一条蛇,它将继续向右移动。 虽然,一旦到达画布的尽头,它将继续其无限的旅程,进入未知的世界。 我们会在适当的时候解决这个问题,请耐心的年轻朋友。 ?

改变蛇的方向 (Changing the snake’s direction)

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

我们的下一个任务是在按下箭头键之一时更改蛇的方向。 在drawSnakePart函数之后添加以下代码。

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;  }}

There is nothing tricky going on here. We check if the key pressed matches one of the arrow keys. If it does, we change the vertical and horizontal velocity as described earlier.

这里没有棘手的事情。 我们检查按下的键是否与箭头键之一匹配。 如果是这样,我们将如前所述更改垂直和水平速度。

Notice that we also check if the snake is moving in the opposite direction of the new intended direction. This is to prevent our snake from reversing, for example when you press the right arrow key when the snake is moving to the left.

请注意,我们还检查了蛇是否正在沿着新的预期方向相反的方向移动。 这是为了防止我们的蛇反转,例如,当蛇向左移动时按右箭头键

To connect changeDirection to our game, we can use addEventListener on the document to ‘listen’ for when a key is pressed. Then we can call changeDirection with the keydown event. Add the following code after the main function.

要将changeDirection连接到我们的游戏,我们可以在文档上使用addEventListener来“监听”按键的按下时间。 然后,我们可以使用keydown事件调用changeDirection 。 在main函数之后添加以下代码。

document.addEventListener("keydown", changeDirection)

You should now be able to change the snake’s direction using the four arrow keys. Great work, you are on fire?!

现在,您应该能够使用四个箭头键更改蛇的方向。 辛苦了,你着火了?!

Next lets see how we can generate food and grow our snake.

接下来,让我们看看我们如何产生食物并种植蛇。

为蛇生食 (Generating food for the snake)

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

对于我们的蛇食,我们必须生成一组随机坐标。 我们可以使用辅助函数randomTen产生两个数字。 一个用于x坐标,一个用于y坐标。

We also have to make sure that the food is not located where the snake currently is. If it is, 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();  });}

We then have to create a function to draw the food on the canvas.

然后,我们必须创建一个函数来在画布上绘制食物。

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

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

最后,我们可以在调用main之前调用createFood 。 别忘了也更新main以使用drawFood函数。

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

成长的蛇 (Growing the snake)

Growing our snake is simple. We can update our advanceSnake function to check if the head of the snake is touching the food. If it is we can skip removing the last part of the snake and create a new food location.

种植我们的蛇很简单。 我们可以更新advanceSnake函数,以检查蛇的头部是否接触到食物。 如果是这样,我们可以跳过移除蛇的最后部分并创建新的食物位置。

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();  }}
跟踪分数 (Keeping track of the score)

To make the game more enjoyable for the player, we can also add a score that increases when the snake eats food.

为了使游戏更有趣,我们还可以添加一条分数,该分数在蛇吃食物时会增加。

Create a new variable score and set it to 0 after the snake declaration.

创建一个新的变量分数,并在snake声明后将其设置为0。

let score = 0;

Next add a new div with an id “score” before the canvas. We can use this to display the score.

接下来,在画布之前添加一个ID为“ score”的新div。 我们可以用它来显示分数。

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

Finally update advanceSnake to increase and display the score when the snake eats the food.

最后更新advanceSnake以增加并在蛇吃食物时显示分数。

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

Pheew, that was quite a lot, but we have come a long way ?

Pheew,虽然很多,但我们已经走了很长一段路?

结束游戏 (End the game)

There is one final piece left, and that is to end the game ?. To do that we can create a function didGameEnd that returns true when the game has ended or false otherwise.

剩下的最后一块是结束游戏? 要做到这一点,我们可以创建一个函数d idGameEnd回返牛逼芸香当比赛已经结束或F ALSE其他。

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 &lt; 0;  const hitBottomWall = snake[0].y > gameCanvas.height - 10;
return hitLeftWall ||          hitRightWall ||          hitToptWall ||         hitBottomWall}

First we check if the snake’s head touches another part of the snake and return true if it does.

首先,我们检查蛇的头部是否接触到蛇的另一部分,如果确实接触到,则返回true

Notice that we start our loop from index 4. There are two reasons for that. The first is that didCollide would immediately evaluate to true if the index was 0, so the game would end. The second is that, it is impossible for the first three parts to touch each other.

请注意,我们从索引4开始循环。有两个原因。 首先是,如果索引为0, didCollide将立即评估为true,因此游戏将结束。 第二个是前三个部分不可能互相接触。

Next we check if the snake hit any of the canvas walls and return true if it did, otherwise we return false.

接下来,我们检查蛇是否击中了画布的任何墙壁,如果确实击中,则返回true ,否则返回false

Now we can return early in our main function if didEndGame returns true, thus ending the game.

现在,如果didEndGame返回true,我们可以在main函数中didEndGame返回,从而结束游戏。

function main() {  if (didGameEnd()) return;
...}

Our snake.html should now look like this:

我们的snake.html现在应如下所示:

You now have a functioning snake game that you can play and share with your friends. But before celebrating lets look at one final problem. This will be the last one, I promise.

现在,您有了一个可以正常运行的蛇游戏,可以与朋友一起玩并分享。 但是在庆祝之前让我们看一个最后的问题。 我保证这将是最后一个。

鬼nea的虫子? (Sneaky bugs ?)

If you play the game enough times, you might notice that sometimes the game ends unexpectedly. This is a very good example on how bugs can sneak into our programs and cause trouble ?.

如果您玩游戏足够的时间,您可能会注意到有时游戏意外结束。 这是一个很好的例子,说明错误如何潜入我们的程序并引起麻烦?

When a bug occurs, the best way to solve it is to first have a reliable way of reproducing it. That is, come up with the precise steps that lead to the unexpected behaviour. Then you need to understand why they cause the unexpected behaviour and then come up with a solution.

发生错误时,解决错误的最佳方法是首先拥有可靠的方法来再现它。 也就是说,提出导致意外行为的精确步骤。 然后,您需要了解为什么它们会导致意外行为,然后提出解决方案。

重现错误 (Reproducing the bug)

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

在我们的例子中,重现该错误的步骤如下:

  • The snake is moving to the left

    蛇向左移动
  • The player presses the down arrow key

    播放器按向下箭头键
  • The player immediately presses the right arrow key (before 100ms have lapsed)

    播放器立即按向右箭头键(在100毫秒过去之前)
  • The game ends

    游戏结束
了解错误 (Making sense of the bug)

Let’s break down what happens step by step.

让我们按步骤分解发生的情况。

Snake is moving to the left

蛇向左移动

  • Horizontal velocity, dx is equal to -10

    水平速度dx等于-10
  • main function is called

    main功能称为

  • advanceSnake is called which advances the snake -10px to the left.

    advanceSnake被调用,它将蛇向左推进-10px。

The player presses the down arrow key

播放器按向下箭头键

  • changeDirection is called

    changeDirection被称为

  • keyPressed === DOWN_KEY && dy !goingUp evaluates to true

    keyPressed === DOWN_KEY && dy !goingUp评估为true

  • dx changes to 0

    dx更改为0
  • dy changes to +10

    dy变为+10

Player immediately presses the right arrow (before 100ms have lapsed)

播放器立即按向右箭头(在100毫秒过去之前)

  • changeDirection is called

    changeDirection被称为

  • keyPressed === RIGHT_KEY && !goingLeft evaluates to true

    keyPressed === RIGHT_KEY && !goingLeft计算为true

  • dx changes to +10

    dx变为+10
  • dy changes to 0

    dy更改为0

The game ends

游戏结束

  • main function is called after 100ms have lapsed.

    经过100毫秒后调用main函数

  • advanceSnake is called which advances the snake 10px to the right.

    advanceSnake ,将蛇向右推进10px。

  • const didCollide = snake[i].x === snake[0].x && snake[i].y === snake[0].y evaluates to true

    const didCollide = snake[i].x === snake[0].x && snake[i].y === snake[0].y计算为true

  • didGameEnd returns true

    didGameEnd返回true

  • main function returns early

    main功能提前归还

  • The game ends

    游戏结束
修正错误 (Fixing the bug)

After studying what happened, we learn that the game ended because the snake reversed.

在研究了发生的情况之后,我们得知游戏因蛇反转而结束了。

That is because when the player pressed the down arrow, dx was set to 0. Thus keyPressed === RIGHT_KEY && !goingLeft evaluated to true, and dx changed to 10.

这是因为当玩家按下向下箭头时,dx设置为0。因此keyPressed === RIGHT_KEY && !goingLeft评估为true,而dx更改为10。

It is important to note that the change in direction occurred before 100ms had lapsed. If 100ms lapsed, then the snake would have first taken a step down and would not have reversed.

重要的是要注意,方向变化发生 100毫秒之前。 如果经过了100毫秒,那么这条蛇将首先走下坡路并且不会扭转。

To fix our bug, we have to make sure that we can only change direction after main and advanceSnake have been called. We can create a variable changingDirection. This will be set to true when changeDirection is called, and to false when advanceSnake is called.

要修复我们的错误,我们必须确保仅在调用mainadvanceSnake之后才能更改方向。 我们可以创建一个变量changingDirection。 这将被设置为true时changeDirection被调用,以假时advanceSnake被调用。

Inside our changeDirection function, we can return early if changingDirection is true.

里面我们changeDirection功能,我们可以提前返回如果changingDirection是真实的。

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)}

Here is our final version of snake.html

这是我们蛇的最终版本

Notice I also added some styles ? between the &lt;style><;/style> tags. That is to make the canvas and score appear in the middle of the screen.

注意,我还添加了一些样式? 在& lt;style>< ; / style>标记之间。 那就是使画布和乐谱出现在屏幕中间。

结论 (Conclusion)

Congratulations!! ??

恭喜!! ??

We have reached the end of our journey. I hope you enjoyed learning with me and now feel confident to continue on to your next adventure.

我们已经走到了旅程的尽头。 希望您喜欢和我一起学习,现在有信心继续下一个冒险。

But it doesn’t have to end here. My next article will focus on helping you get started with the very exciting world of open source.

但这不必到此结束。 我的下一篇文章将重点介绍如何帮助您入门非常令人兴奋的开源世界。

Open source is a great way to learn a lot of new things and get to know amazing people . It is very rewarding but can can be scary at first ?.

开源是学习很多新事物并结识很棒的人的好方法。 这是非常有益的,但是起初可能会很吓人?

To get a notification when my next article is out, you can follow me! ?

要在下一篇文章发表时得到通知,可以关注我! ?

It was a pleasure to be on this journey with you.

很高兴与您一起旅途。

Till next time. ✨

直到下一次。 ✨

翻译自: https://www.freecodecamp.org/news/think-like-a-programmer-how-to-build-snake-using-only-javascript-html-and-css-7b1479c3339e/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值