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?
好吧,好吧,您已经准备好开始了吗?
入门 (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 <!DOCTY
PE> declaration . In snak
e.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 <canv
as> . 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"><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 t
he </canvas>, where all our JavaScript code will go.
为了使我们的画布可见,我们可以通过编写一些JavaScript代码为它提供边框。 为此,我们需要在</ canvas> s after t
插入<script><
; / script>标签,所有JavaScript代码都将移至该位置。
If you put the
<scri
pt> tag before the &l
t;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 < 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 calledmain
功能称为advanceSnake
is called which advances the snake -10px to the left.advanceSnake
被调用,它将蛇向左推进-10px。
The player presses the down arrow key
播放器按向下箭头键
changeDirection
is calledchangeDirection
被称为keyPressed === DOWN_KEY && dy !goingUp
evaluates to truekeyPressed === 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 calledchangeDirection
被称为keyPressed === RIGHT_KEY && !goingLeft
evaluates to truekeyPressed === 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 trueconst didCollide = snake[i].x === snake[0].x && snake[i].y === snake[0].y
计算为truedidGameEnd
returns truedidGameEnd
返回truemain
function returns earlymain
功能提前归还- 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.
要修复我们的错误,我们必须确保仅在调用main
和advanceSnake
之后才能更改方向。 我们可以创建一个变量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. ✨
直到下一次。 ✨