所以我有一个蛇的游戏,是基于Kenny Yip编码教程(https://youtu.be/baBq5GAL0_U)。代码如下所示。我的restartGame函数不能很好地工作,因为它不能在第一次调用时正确更新棋盘。第二次调用时效果很好。我试过使用clearInterval,但是之后游戏完全死机了。
//board
const blockSize = 25;
const rows = 20;
const cols = 20;
const board = document.getElementById("board");
const ctx = board.getContext("2d");
const borderClr = 'black';
const boardClr = 'green';
//snake
let snakeX = 5 * blockSize;
let snakeY = 5 * blockSize;
let snakeBody = [
[snakeX - blockSize, snakeY],
[snakeX - 2 * blockSize, snakeY],
];
const snakeClr = 'blue';
// starting velocity
let velocityX = 0;
let velocityY = 0;
// current direction (only used to prevent changing direction multiple times with in one "tick")
let currentDirection = null;
//food
let radius = blockSize / 2;
let foodX;
let foodY;
const foodClr = "red";
let gameOver = false;
//score
let score = 0;
let gameSpeed = 100;
let intervalID;
// This function executes after the window has loaded
window.onload = function() {
// Initialize the game board and set its dimensions
board;
board.height = rows * blockSize;
board.width = cols * blockSize;
// Initialize the canvas
ctx;
// Place the food on the board
placeFood();
// Listen for user input to change the direction of the snake
document.addEventListener("keyup", changeDirection);
// Set the update function to execute every 100 milliseconds
intervalID = setInterval(update, gameSpeed);
alert("Use arrow keys to move the snake.");
}
// This function updates the game state every time it is called
function update() {
// If the game is over, stop updating
if (gameOver) {
return;
}
// Update the score display
scoreTxt.textContent = score;
// Clear the canvas and draw the board and food
ctx.fillStyle= boardClr;
ctx.fillRect(0, 0, board.width, board.height);
ctx.beginPath();
ctx.arc( foodX + blockSize/2, foodY + blockSize/2, radius, 2 * Math.PI, 0, false);
ctx.fillStyle = foodClr;
ctx.fill();
ctx.strokeStyle = borderClr;
ctx.stroke();
// If the snake eats the food, increase the score and add a new segment to the snake
if (snakeX == foodX && snakeY == foodY) {
score += 1;
snakeBody.push([foodX, foodY]);
placeFood();
}
let oldSnakeX = snakeX;
let oldSnakeY = snakeY;
// Update the positions of the segments in the snake's body
if(velocityX != 0 || velocityY !=0){
for (let i = snakeBody.length-1; i > 0; i--) {
snakeBody[i] = snakeBody[i-1];
}
if (snakeBody.length) {
snakeBody[0] = [oldSnakeX, oldSnakeY];
}
}
// update snake position based on velocity
snakeX += velocityX * blockSize;
snakeY += velocityY * blockSize;
// update velocity based on current direction
if (currentDirection == "up" && velocityY != 1) {
velocityX = 0;
velocityY = -1;
}
else if (currentDirection == "down" && velocityY != -1) {
velocityX = 0;
velocityY = 1;
}
else if (currentDirection == "left" && velocityX != 1) {
velocityX = -1;
velocityY = 0;
}
else if (currentDirection == "right" && velocityX != -1) {
velocityX = 1;
velocityY = 0;
}
// Draw the snake and its body segments
ctx.fillStyle= snakeClr;
ctx.strokeStyle = borderClr;
ctx.fillRect(snakeX, snakeY, blockSize, blockSize);
ctx.strokeRect(snakeX, snakeY, blockSize, blockSize);
for (let i = 0; i < snakeBody.length; i++) {
ctx.fillRect(snakeBody[i][0], snakeBody[i][1], blockSize, blockSize);
ctx.strokeRect(snakeBody[i][0], snakeBody[i][1], blockSize, blockSize)
}
// If the snake goes out of bounds, end the game
if (snakeX < 0 || snakeX >= cols*blockSize || snakeY < 0 || snakeY >= rows*blockSize) {
gameOver = true;
displayGameover();
}
// If the snake collides with its body, end the game
for (let i = 0; i < snakeBody.length; i++) {
if (snakeX == snakeBody[i][0] && snakeY == snakeBody[i][1]) {
gameOver = true;
displayGameover();
}
}
// This function displays the "Game Over" message
function displayGameover(){
ctx.font = "75px Courier New";
ctx.fillStyle = foodClr;
ctx.textAlign = "center";
ctx.fillText("Game Over!", board.height / 2, board.width / 2);
}
}
// This function changes the direction of the snake based on user input
function changeDirection(e) {
if (e.code == "ArrowLeft" && currentDirection == null){
return;
}
if (e.code == "ArrowUp" && currentDirection != "down") {
currentDirection = "up";
}
else if (e.code == "ArrowDown" && currentDirection != "up") {
currentDirection = "down";
}
else if (e.code == "ArrowLeft" && currentDirection != "right") {
currentDirection = "left";
}
else if (e.code == "ArrowRight" && currentDirection != "left") {
currentDirection = "right";
}
}
// This function places the food in a random position on the game board
function placeFood() {
let foodPosValid = false; // Set foodPosValid to false initially
while (!foodPosValid) { // Loop until a valid position is found for the food
// Choose a random x and y position for the food
foodX = Math.floor(Math.random() * cols) * blockSize;
foodY = Math.floor(Math.random() * rows) * blockSize;
foodPosValid = true; // Assume the position is valid
// Check if the food position is already occupied by the snake
for (let i = 0; i < snakeBody.length; i++) {
// If it is, set foodPosValid to false and break out of the loop
if (snakeBody[i][0] == foodX && snakeBody[i][1] == foodY){
foodPosValid = false;
break;
}
}
}
}
// This function restarts the game when the user clicks the button
restart.addEventListener("click", restartGame);
function restartGame(){// Reset the score and snake variables, place food in a random position, set gameOver to false
score = 0;
snakeBody = [
[snakeX - blockSize, snakeY],
[snakeX - 2 * blockSize, snakeY],
];
snakeX = 5 * blockSize;
snakeY = 5 * blockSize;
velocityX = 0;
velocityY = 0;
currentDirection = null;
placeFood();
gameOver = false;
};
// This function toggles between the dark and light themes of the weebsite
function themeSwap(){
document.documentElement.classList.toggle("darkmode");
document.querySelectorAll(".inverted").forEach((result) => {
result.classList.toggle("invert");
});
};
const sitetheme = document.querySelector("#theme");
sitetheme.addEventListener("click", themeSwap);
在添加这些变量声明后,我没有看到restartGame()
函数中的任何错误:
const board = document.getElementById("board");
const scoreTxt = document.getElementById("score");
const restart = document.getElementById("restart");
和HTML看起来像这样:
<h1>Snack Game</h1>
<canvas id="board"></canvas>
<p>Score: <span id="score">0</span></p>
<button id="restart">Restart</button>
<button id="theme">Theme</button>
完整代码:
//board
const blockSize = 25;
const rows = 20;
const cols = 20;
const board = document.getElementById("board"); // new
const scoreTxt = document.getElementById("score"); // new
const restart = document.getElementById("restart"); // new
const ctx = board.getContext("2d");
const borderClr = 'black';
const boardClr = 'green';
//snake
let snakeX = 5 * blockSize;
let snakeY = 5 * blockSize;
let snakeBody = [
[snakeX - blockSize, snakeY],
[snakeX - 2 * blockSize, snakeY],
];
const snakeClr = 'blue';
// starting velocity
let velocityX = 0;
let velocityY = 0;
// current direction (only used to prevent changing direction multiple times with in one "tick")
let currentDirection = null;
//food
let radius = blockSize / 2;
let foodX;
let foodY;
const foodClr = "red";
let gameOver = false;
//score
let score = 0;
let gameSpeed = 100;
let intervalID;
// This function executes after the window has loaded
window.onload = function() {
// Initialize the game board and set its dimensions
board;
board.height = rows * blockSize;
board.width = cols * blockSize;
// Initialize the canvas
ctx;
// Place the food on the board
placeFood();
// Listen for user input to change the direction of the snake
document.addEventListener("keyup", changeDirection);
// Set the update function to execute every 100 milliseconds
intervalID = setInterval(update, gameSpeed);
alert("Use arrow keys to move the snake.");
}
// This function updates the game state every time it is called
function update() {
// If the game is over, stop updating
if (gameOver) {
return;
}
// Update the score display
scoreTxt.textContent = score;
// Clear the canvas and draw the board and food
ctx.fillStyle= boardClr;
ctx.fillRect(0, 0, board.width, board.height);
ctx.beginPath();
ctx.arc( foodX + blockSize/2, foodY + blockSize/2, radius, 2 * Math.PI, 0, false);
ctx.fillStyle = foodClr;
ctx.fill();
ctx.strokeStyle = borderClr;
ctx.stroke();
// If the snake eats the food, increase the score and add a new segment to the snake
if (snakeX == foodX && snakeY == foodY) {
score += 1;
snakeBody.push([foodX, foodY]);
placeFood();
}
let oldSnakeX = snakeX;
let oldSnakeY = snakeY;
// Update the positions of the segments in the snake's body
if(velocityX != 0 || velocityY !=0){
for (let i = snakeBody.length-1; i > 0; i--) {
snakeBody[i] = snakeBody[i-1];
}
if (snakeBody.length) {
snakeBody[0] = [oldSnakeX, oldSnakeY];
}
}
// update snake position based on velocity
snakeX += velocityX * blockSize;
snakeY += velocityY * blockSize;
// update velocity based on current direction
if (currentDirection == "up" && velocityY != 1) {
velocityX = 0;
velocityY = -1;
}
else if (currentDirection == "down" && velocityY != -1) {
velocityX = 0;
velocityY = 1;
}
else if (currentDirection == "left" && velocityX != 1) {
velocityX = -1;
velocityY = 0;
}
else if (currentDirection == "right" && velocityX != -1) {
velocityX = 1;
velocityY = 0;
}
// Draw the snake and its body segments
ctx.fillStyle= snakeClr;
ctx.strokeStyle = borderClr;
ctx.fillRect(snakeX, snakeY, blockSize, blockSize);
ctx.strokeRect(snakeX, snakeY, blockSize, blockSize);
for (let i = 0; i < snakeBody.length; i++) {
ctx.fillRect(snakeBody[i][0], snakeBody[i][1], blockSize, blockSize);
ctx.strokeRect(snakeBody[i][0], snakeBody[i][1], blockSize, blockSize)
}
// If the snake goes out of bounds, end the game
if (snakeX < 0 || snakeX >= cols*blockSize || snakeY < 0 || snakeY >= rows*blockSize) {
gameOver = true;
displayGameover();
}
// If the snake collides with its body, end the game
for (let i = 0; i < snakeBody.length; i++) {
if (snakeX == snakeBody[i][0] && snakeY == snakeBody[i][1]) {
gameOver = true;
displayGameover();
}
}
// This function displays the "Game Over" message
function displayGameover(){
ctx.font = "75px Courier New";
ctx.fillStyle = foodClr;
ctx.textAlign = "center";
ctx.fillText("Game Over!", board.height / 2, board.width / 2);
}
}
// This function changes the direction of the snake based on user input
function changeDirection(e) {
if (e.code == "ArrowLeft" && currentDirection == null){
return;
}
if (e.code == "ArrowUp" && currentDirection != "down") {
currentDirection = "up";
}
else if (e.code == "ArrowDown" && currentDirection != "up") {
currentDirection = "down";
}
else if (e.code == "ArrowLeft" && currentDirection != "right") {
currentDirection = "left";
}
else if (e.code == "ArrowRight" && currentDirection != "left") {
currentDirection = "right";
}
}
// This function places the food in a random position on the game board
function placeFood() {
let foodPosValid = false; // Set foodPosValid to false initially
while (!foodPosValid) { // Loop until a valid position is found for the food
// Choose a random x and y position for the food
foodX = Math.floor(Math.random() * cols) * blockSize;
foodY = Math.floor(Math.random() * rows) * blockSize;
foodPosValid = true; // Assume the position is valid
// Check if the food position is already occupied by the snake
for (let i = 0; i < snakeBody.length; i++) {
// If it is, set foodPosValid to false and break out of the loop
if (snakeBody[i][0] == foodX && snakeBody[i][1] == foodY){
foodPosValid = false;
break;
}
}
}
}
// This function restarts the game when the user clicks the button
restart.addEventListener("click", restartGame);
function restartGame(){// Reset the score and snake variables, place food in a random position, set gameOver to false
score = 0;
snakeBody = [
[snakeX - blockSize, snakeY],
[snakeX - 2 * blockSize, snakeY],
];
snakeX = 5 * blockSize;
snakeY = 5 * blockSize;
velocityX = 0;
velocityY = 0;
currentDirection = null;
placeFood();
gameOver = false;
};
// This function toggles between the dark and light themes of the weebsite
function themeSwap(){
document.documentElement.classList.toggle("darkmode");
document.querySelectorAll(".inverted").forEach((result) => {
result.classList.toggle("invert");
});
};
const sitetheme = document.querySelector("#theme");
sitetheme.addEventListener("click", themeSwap);
<h1>Snack Game</h1>
<canvas id="board"></canvas>
<p>Score: <span id="score">0</span></p>
<button id="restart">Restart</button>
<button id="theme">Theme</button>