You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JavaScript贪吃蛇游戏重启后保留上次按键输入问题求助

贪吃蛇游戏重启后自动移动的问题修复

我是JavaScript新手,开发贪吃蛇游戏时遇到一个问题:点击画布上的重启按钮后,蛇会自动沿游戏结束前最后一次按键的方向移动,但首次启动游戏时需要按下任意按键才开始移动。这个问题是在修复快速按键导致蛇反向撞自身的事件监听问题后出现的,相关代码如下:

HTML代码

<!-- the snake game section -->
<div class="snake-game" data-hover="Press any key to start">
    <div class="score">
        Score: <p id="score-number">0</p>
    </div>
<canvas id="game-board">    
</canvas>
</div>

JavaScript代码

const gameBoard = document.getElementById("game-board");
const context = gameBoard.getContext("2d");

let rows = 20;
let cols = 20;

let gridBoard = 20; // per row 30 grids
let gameOver = false;
let snakeSize = 1; // initializing snake's body size

let x = new Array(900);
let y = new Array(900);

gameBoard.height = rows * gridBoard;
gameBoard.width = cols * gridBoard;


for (let i = 0; i < snakeSize; i++){
    x[i] = 40 - i*20
    y[i] = 40 //the head of the snake
}


// to come up with a random position for the apple

let eatenApple = true;
let appleX;
let appleY;

let score = 0;
let scoreNumber = document.getElementById("score-number");

const path = new Path2D(); // for the reset button

function mainGame(){

    //when the snake eats the apple and grows
    eatApple();

    //movement validation and check the collision
    movementValidation();

    // keep record of head&body in canvas for movement
    move(); // or change the snake position 

    //to display the snake's head, body, and the apple
    display();

    setTimeout(mainGame, 150);

}

function display(){
    context.clearRect(0,0, gameBoard.width, gameBoard.height);
    if(gameOver){
        isGameOver();
        } else {
            //apple
            if(eatenApple){
                appleRandomPlace();
                eatenApple = false;
            }

            //the apple's head
            context.beginPath()
            context.fillStyle = "red";
            context.fillRect(appleX, appleY, gridBoard / 1, gridBoard / 1);
            context.closePath();


            for (let i = 0; i < snakeSize; i++){
            //the snake's head
            if(i === 0){
                const snake_head = new Path2D();
                context.fillStyle = "#86626E";
                snake_head.rect(x[0], y[0], gridBoard / 1, gridBoard / 1);
                context.fill(snake_head);
                
            
            } else {
                //the snake's body
                const snake_body = new Path2D();
                context.fillStyle = "#201335";
                context.strokeStyle = "#fff";
                context.lineWidth = 3;
                snake_body.rect(x[i], y[i], gridBoard / 1, gridBoard / 1);
                context.fill(snake_body);
                context.stroke(snake_body);
            }
        }
    }
}


function isGameOver(){
    //game over text
    context.fillStyle = "black";
    context.textBaseline = "middle";
    context.textAlign = "center";
    context.font = "35px Adonay";
    context.fillText("game over", gameBoard.width / 2, gameBoard.height / 2);

    // the reset button and text
    path.rect(150,240,100,40); //x, y, width, height
    path.closePath();
    context.fillStyle = "black";
    context.textBaseline = "middle";
    context.textAlign = "center";
    context.font = "20px Adonay";
    context.strokeStyle = "#0B1D51";    
    context.lineWidth = 1;
    context.fillText("restart", 200, 261, 111, 40);
    context.stroke(path);
}


function eatApple(){
    if(x[0] === appleX && y[0] === appleY){
        snakeSize++
        eatenApple = true;
        score += 1;
        scoreNumber.innerText = score;
    }

}

function appleRandomPlace(){
    appleX = Math.floor(Math.random() * (cols - 5)) * gridBoard + 20;
    appleY = Math.floor(Math.random() * (rows - 5)) * gridBoard + 20;

}

let direction;
let nextDirection;

document.addEventListener("keydown", function(event){

    if(event.keyCode === 38 && direction !== "down") {
        nextDirection = "up";
    }

    else if(event.keyCode === 40 && direction !== "up"){
        nextDirection = "down";
    }

    else if(event.keyCode === 37 && direction !== "right"){
        nextDirection = "left";
    }

    else if(event.keyCode === 39 && direction !== "left"){
        nextDirection = "right";
    }

});


function movementValidation(){

    // snake's collision with the walls
    if(y[0] >= gameBoard.height / 1.1) {
        gameOver = true;

    } else if(y[0] < 1) {
        gameOver = true;

    } else if(x[0] >= gameBoard.width / 1.1) {
        gameOver = true;

    } else if(x[0] < 1) {
        gameOver = true;
    }

    //x[0] and y[0] represent the head of the snake

    // snake eats its own body
    for (let i = 1; i < snakeSize; i+=1){
        if(x[0] === x[i] && y[0] === y[i]){
            gameOver = true;
        }
    }
}

function move(){
    // for snake's body
    for (let i = snakeSize; i > 0; i--){
        x[i] = x[(i-1)];
        y[i] = y[(i-1)];
    }

    // for snake's head

            if(nextDirection === "left"){
                x[0] -= gridBoard;
            }
        
            else if(nextDirection === "right"){
                x[0] += gridBoard;
            }
        
            else if(nextDirection === "up"){
                y[0] -= gridBoard;
            }
        
            else if(nextDirection === "down"){
                y[0] += gridBoard;
            }

            direction = nextDirection;

        }
    


        function buttonReset(gameBoard, event){
            const rect = gameBoard.getBoundingClientRect();
            const x = event.clientX - rect.left;
            const y = event.clientY - rect.top;
            return {x:x, y:y};
        }


document.addEventListener("click", function (e){
    const x_y = buttonReset(gameBoard, e);
    if(context.isPointInPath(path, x_y.x, x_y.y)){

    gameOver = false;
    snakeSize = 1; // initializing snake's body size
   
    x = new Array(900);
    y = new Array(900);
   
   gameBoard.height = rows * gridBoard;
   gameBoard.width = cols * gridBoard;
   
   for (let i = 0; i < snakeSize; i++){
       x[i] = 40 - i*10
       y[i] = 40 //the head of the snake
   }
   
    eatenApple = false;
    appleX;
    appleY;
   
    score = 0;
    scoreNumber.innerText = score;
    
    direction;
    nextDirection;

    display();
    move();
    eatApple();
    movementValidation();

}

}, false);

mainGame();

问题原因与修复方案

原因

重启游戏时,direction和nextDirection变量未被重置,仍保留上一局的方向值,导致蛇重启后直接按之前的方向移动;同时重启代码中蛇的初始位置计算有误,且苹果生成逻辑存在问题。

修复步骤

  1. 重置方向变量:在重启按钮的点击事件中,显式将direction和nextDirection设为undefined,清除上一局的方向记录,确保重启后必须按键才会移动。
  2. 修正蛇初始位置:将重启时蛇的x坐标计算改为和首次启动一致的40 - i*20,避免位置偏移。
  3. 修复苹果生成逻辑:重启时将eatenApple设为true,确保重新生成苹果。
  4. 优化move函数:添加判断,只有当nextDirection存在时才移动蛇头,增强鲁棒性。

修改后的重启事件处理函数:

document.addEventListener("click", function (e){
    const x_y = buttonReset(gameBoard, e);
    if(context.isPointInPath(path, x_y.x, x_y.y)){
        gameOver = false;
        snakeSize = 1;
       
        x = new Array(900);
        y = new Array(900);
       
        // 修正初始位置,和首次启动保持一致
        for (let i = 0; i < snakeSize; i++){
            x[i] = 40 - i*20;
            y[i] = 40;
        }
       
        eatenApple = true; // 重启后重新生成苹果
        score = 0;
        scoreNumber.innerText = score;
        
        // 关键:重置方向变量
        direction = undefined;
        nextDirection = undefined;

        display();
    }
}, false);

修改后的move函数:

function move(){
    // 蛇身跟随移动
    for (let i = snakeSize; i > 0; i--){
        x[i] = x[(i-1)];
        y[i] = y[(i-1)];
    }

    // 只有存在方向指令时才移动蛇头
    if(nextDirection){
        if(nextDirection === "left"){
            x[0] -= gridBoard;
        } else if(nextDirection === "right"){
            x[0] += gridBoard;
        } else if(nextDirection === "up"){
            y[0] -= gridBoard;
        } else if(nextDirection === "down"){
            y[0] += gridBoard;
        }
        direction = nextDirection;
    }
}

额外优化

删除重启代码中重复设置gameBoard.height和gameBoard.width的语句,这部分初始化时已完成,无需重复执行。

内容的提问来源于stack exchange,提问作者meii18

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 10:01:18