JavaScript实现贪吃蛇小游戏

2022/3/20 11:27:41

本文主要是介绍JavaScript实现贪吃蛇小游戏,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

效果图:

 需求:

        1、贪吃蛇的基本功能

        2、统计得分

        3、开始与暂停

        4、选择难度等级

        5、设置快捷键

                5.1、通过IJKL,WSAD也能实现方向的切换

                5.2、 通过“P” 表示暂停,“C”表示开始或继续,"R"表示重新开始

实现步骤思路:

        1、画一个蛇的移动区域

        2、画一条蛇

        3、画食物

        4、让蛇动起来

        5、设定游戏规则

        6、设置难度等级

        7、设置开始与暂停

        8、设置游戏结束后续操作

        9、实现人机交互页面

具体实现代码如下:

1、画蛇的活动区域
        首先我们画蛇的活动区域,我们采用JavaScript的canvas进行绘制,我们用一个400 × 400 的区域作为蛇的活动区域

<canvas id="canvas" width="400" height="400"></canvas>

同时通过CSS设置一个边界线

#canvas {
    border: 1px solid #000000; /* 设置边框线 */
}

2、画蛇和食物

效果如下:

 

        在画蛇前我们需要想下蛇的数据结构,在这里我们采取最简单的队列表示蛇,队首表示蛇头位置,队尾表示蛇尾位置,我们将之前画的400 × 400 区域划分为400个20 × 20 的方块,用这些方块组成蛇,那么蛇所在方块的位置的取值范围就是0~399

举个例子:

var snake=[42,41,40];

        上述代码表示蛇所在的位置为42,41,40三个方块,其中蛇头为42,蛇尾为40,对于食物,我们可以用一个变量food存储食物的位置即可,食物的取值范围为0~399,且不包括蛇的部分,由于游戏中需要随机产生食物,随机函数实现如下:

// 产生min~max的随机整数,用于随机产生食物的位置
function random(min, max) {
    const num = Math.floor(Math.random() * (max - min)) + min;
    return num;
}

        当食物被蛇吃掉后就需要重新刷新食物,由于食物不能出现在蛇所在的位置,我们用一个while循环,当食物的位置不在蛇的数组中则跳出循环

while (snake.indexOf((food = random(0, 400))) >= 0); // 重新刷新食物,注意食物应不在蛇内部

接下来通过canvas进行绘制,首先在JS中获取canvas组件

const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");

然后写绘制函数用于绘制方格,绘制方格的时候注意我们预留1px作为边框,即我们所画的方格的边长为18,我们实际填充的是18 × 18 的方块,方块的x、y坐标(方块的左上角)的计算也需要注意加上1px

注:canvas的原点坐标在左上角,往右为x轴正方向,往下为y轴正方向

// 用于绘制蛇或者是食物代表的方块,seat为方块位置,取值为0~399,color为颜色
function draw(seat, color) {
    ctx.fillStyle = color; // 填充颜色
    // fillRect的四个参数分别表示要绘制方块的x坐标,y坐标,长,宽,这里为了美观留了1px用于边框
    ctx.fillRect(
        (seat % 20) * 20 + 1,
        Math.floor(seat / 20) * 20 + 1,
        18,
        18
    );
}

让蛇动起来
        首先要规定蛇运动的方向,我们用一个变量direction来表示蛇运动的方向,其取值范围为{1,-1,20,-20},1 表示向右,-1 表示向左,20 表示向下,-20 表示向上,运动时只需要将蛇头的位置加上direction就可以表示新蛇头的位置,这样我们就可以表示蛇的运动了。

那么如何让蛇动起来呢,对于蛇的每次移动,我们需要完成下面几个基本操作:

1、将蛇运动的下一个位置变成新蛇头
        1.1、将下一个位置加入蛇队列
        1.2、绘制下一个方块为浅蓝色
2、把旧蛇头变成蛇身
        2.1、绘制旧蛇头为浅灰色
3、删除旧蛇尾
        3.1、将旧蛇尾弹出蛇队列
        3.2、绘制旧蛇尾位置为白色
        当蛇吃掉食物时(蛇的下一个位置为食物所在位置)则需更新食物的位置,并绘制新食物为黄色,此时则不需要删除旧蛇尾,这样可以实现蛇吃完食物后长度的增加功能

我们需要写一个函数实现上述操作,并且要不断地调用这个函数,从而实现页面的刷新,即我们所说的动态效果

n = snake[0] + direction; // 找到新蛇头坐标
snake.unshift(n); // 添加新蛇头
draw(n, "#1a8dcc"); // 绘制新蛇头为浅蓝色
draw(snake[1], "#cececc"); // 将原来的蛇头(浅蓝色)变成蛇身(浅灰色)
if (n == food) {
    while (snake.indexOf((food = random(0, 400))) >= 0); // 重新刷新食物,注意食物应不在蛇内部
    draw(food, "Yellow"); // 绘制食物
} else {
    draw(snake.pop(), "White"); // 将原来的蛇尾绘制成白色
}

        接下来,我们需要实现通过键盘控制蛇的运动

        我们需要获取键盘的key值,然后通过一个监听函数去监听键盘按下的操作,我们这里通过上下左右键(还拓展了WSAD键和IJKL键控制上下左右方向),同时设置一个变量n表示下一步的方向

// 用于绑定键盘上下左右事件,上下左右方向键,代表上下左右方向
document.onkeydown = function (event) {
    const keycode = event.keyCode;
    if (keycode <= 40) {
        // 上 38 下 40 左 37 右 39
        n = [-1, -20, 1, 20][keycode - 37] || direction; // 若keycode为其他值,即表示按了没用的键,则方向不变
    } else if (keycode <= 76 && keycode >= 73) {
        // i 73 j 74 k 75 l 76
        n = [-20, -1, 20, 1][keycode - 73] || direction;
    } else {
        switch (keycode) {
            case 87: //w
                n = -20;
                break;
            case 83: //s
                n = 20;
                break;
            case 65: //a
                n = -1;
                break;
            case 68: //d
                n = 1;
                break;
            default:
                n = direction;
        }
    }
    direction = snake[1] - snake[0] == n ? direction : n; // 若方向与原方向相反,则方向不变
};

设定游戏规则

贪吃蛇的最基础的游戏规则如下:

  1. 蛇如果撞到墙或者蛇的身体或尾巴则游戏结束
  2. 蛇如果吃掉食物则蛇的长度会增加(上一步已经实现)且得分会增加

先实现第一个,具体如下:

注:下面的一段代码中的n即为新蛇头的位置

// 判断蛇头是否撞到自己或者是否超出边界
if (
    snake.indexOf(n, 1) > 0 ||
    n < 0 ||
    n > 399 ||
    (direction == 1 && n % 20 == 0) ||
    (direction == -1 && n % 20 == 19)
) {
    game_over();
}

        接下来我们实现得分统计,对于得分的计算我们只需要设置一个变量score,用于统计得分,然后每吃一个食物,该变量加一,然后将得分信息更新到网页相应位置

score = score + 1;
score_cal.innerText = "目前得分: " + score; // 更新得分

设置难度等级

我们在网页上设置一个单选框,用于设置难度等级

<form action="" id="mode_form">
    难度等级: 
    <input type="radio" name="mode" id="simply" value="simply" checked />
    <label for="simply">简单</label>
    <input type="radio" name="mode" id="middle" value="middle" />
    <label for="middle">中级</label>
    <input type="radio" name="mode" id="hard" value="hard" />
    <label for="hard">困难</label>
</form>
效果如下:

 

那么后台具体如何设置难度等级的功能呢?我们采取调用蛇运动的函数的时间间隔来代替难度,时间间隔越小则难度越大,我们分三级:简单、中级、困难,创建一个时间间隔变量time_internal,然后用一个函数获取单选框的取值,并将相应模式的时间间隔赋值给time_internal

// 用刷新间隔代表蛇的速度,刷新间隔越长,则蛇的速度越慢
const simply_mode = 200;
const middle_mode = 100;
const hard_mode = 50;

var time_internal = simply_mode; // 刷新时间间隔,用于调整蛇的速度,默认为简单模式

// 同步难度等级
function syncMode() {
    var mode_value = "";
    for (var i = 0; i < mode_item.length; i++) {
        if (mode_item[i].checked) {
            mode_value = mode_item[i].value;
        }
    }
    switch (mode_value) {
        case "simply":
            time_internal = simply_mode;
            break;
        case "middle":
            time_internal = middle_mode;
            break;
        case "hard":
            time_internal = hard_mode;
            break;
    }
}

        最后只需要在蛇每次移动前调用一次上述函数syncMode()就可以实现难度切换,至于蛇的速度的具体调节且看下面如何讲解

设置开始与暂停
        如何实现蛇的移动动态效果,如何暂停,如何继续,速度如何调节,这就涉及到JavaScript的动画的部分其实现动态效果的方法为设置一个立即执行函数!function() {}();,然后在该函数中使用setTimeout(arguments.callee, 150);,每隔0.15秒调用此函数,从而实现了网页的不断刷新,也就是所谓的动态效果。在requestAnimationFrame方法可以实现动画效果,实现难度调节,蛇的速度

var requestID; // 用于标记请求ID与取消动画
function updateProgress() { 
	// do something...
    requestID = requestAnimationFrame(updateProgress); // 调用后在函数中反复调用该函数
} 
id = requestAnimationFrame(updateProgress); // 第一次调用(即开始动画)

cancelAnimationFrame(requestID); // 取消动画

        requestAnimationFrame()已经解决了浏览器不知道 JavaScript 动画何时开始的问题, 以及最佳间隔是多少的问题。······

        传给 requestAnimationFrame()的函数实际上可以接收一个参数,此参数是一个 DOMHighResTimeStamp 的实例(比如 performance.now()返回的值),表示下次重绘的时间。这一点非常重要: requestAnimationFrame()实际上把重绘任务安排在了未来一个已知的时间点上,而且通过这个参数 告诉了开发者。基于这个参数,就可以更好地决定如何调优动画了。

requestAnimationFrame()返回一个请求 ID,可以用于通过另一个 方法 cancelAnimationFrame()来取消重绘任务

如何控制时间间隔的方法:

        配合使用一个计时器来限制重绘操作执行的频率。这样,计时器可以限制实际的操作执行间隔,而 requestAnimationFrame 控制在浏览器的哪个渲染周期中执行。下面的例子可以将回调限制为不超过 50 毫秒执行一次

具体实现:

let enabled = true; 
function expensiveOperation() { 
	console.log('Invoked at', Date.now()); 
} 
window.addEventListener('scroll', () => { 
 if (enabled) { 
     enabled = false; 
     requestAnimationFrame(expensiveOperation); 
     setTimeout(() => enabled = true, 50); 
 } 
}); 

根据上面的内容,可以设置一个控制函数,用于控制隔一定的时间调用一次蛇运动的函数,实现如下

// 控制游戏的刷新频率,每隔time_internal时间间隔刷新一次
function game_control(){
    if(enabled){
        enabled = false;
        requestAnimationFrame(run_game);
        setTimeout(() => enabled = true, time_internal);
    }
    run_id = requestAnimationFrame(game_control);
}

// 启动或继续游戏
function run_game() {
    syncMode(); // 同步难度等级
    n = snake[0] + direction; // 找到新蛇头坐标
    snake.unshift(n); // 添加新蛇头
    // 判断蛇头是否撞到自己或者是否超出边界
    if (
        snake.indexOf(n, 1) > 0 ||
        n < 0 ||
        n > 399 ||
        (direction == 1 && n % 20 == 0) ||
        (direction == -1 && n % 20 == 19)
    ) {
        game_over();
    }
    draw(n, "#1a8dcc"); // 绘制新蛇头为浅蓝色
    draw(snake[1], "#cececc"); // 将原来的蛇头(浅蓝色)变成蛇身(浅灰色)
    if (n == food) {
        score = score + 1;
        score_cal.innerText = "目前得分: " + score; // 更新得分
        while (snake.indexOf((food = random(0, 400))) >= 0); // 重新刷新食物,注意食物应不在蛇内部
        draw(food, "Yellow"); // 绘制食物
    } else {
        draw(snake.pop(), "White"); // 将原来的蛇尾绘制成白色
    }
    // setTimeout(arguments.callee, time_internal); //之前的方案,无法实现暂停和游戏的继续
}

至于暂停只需要在特定的位置调用cancelAnimationFrame(run_id);就可以了

设置游戏结束后续操作

        游戏结束后出现一个“弹窗”,显示最终得分和是否再来一把

效果如下:

 

        我们实现网页的弹窗,通过调研发现JavaScript的弹窗可以通过alert()的方法实现,不过在网页上直接弹窗感觉不太美观,而且影响体验,于是我想了一下,可以采用一个div标签实现伪弹窗,在需要显示的时候设置其display属性为block,不需要显示的时候设置其display属性为none,就类似于Photoshop里面的图层概念,这样我们就可以在平常的时候设置其display属性为none触发game over时设置其display属性为block,实现如下:

<div id="game_over">
    <h3 id="game_over_text" align="center">游戏结束!</h3>
    <h3 id="game_over_score" align="center">您的最终得分为: 0分</h3>
    <button id="once_again">再来一把</button>
    <button id="cancel">取消</button>
</div>

CSS样式如下: 

#game_over {
    display: none; /* 设置game over 窗口不可见 */
    position: fixed;
    top: 190px;
    left: 65px;
    width: 280px;
    height: 160px;
    background-color: aliceblue;
    border-radius: 5px;
    border: 1px solid #000; /* 设置边框线 */
}
#once_again {
    position: relative;
    left: 20px;
}
#cancel {
    position: relative;
    left: 50px;
}

我们需要实现game over的后续操作:暂停动画,显示得分,显示“弹窗”

function game_over(){
    cancelAnimationFrame(run_id);
    game_over_score.innerText = "您的最终得分为: " + score + "分";
    game_over_div.style.display = "block";
}

实现人机交互页面
        接下来的部分就是提高用户体验的部分,具体实现下列功能/操作

游戏说明
        人机交互按钮:开始/继续,暂停,重新开始
快捷键
        由于在游戏过程中通过鼠标移动到暂停键暂停,时间上太慢,可能造成游戏终止,故应该设置开始/继续(C)、暂停(P)、重新开始(R)的快捷键
        有些电脑键盘的上下左右键较小,操作起来不太方便,可以添加WSAD或者IJKL扩展,用于控制上下左右方向
效果如下:

 

界面代码下文末,这里讲解下绑定按键点击事件与绑定快捷键

        首先看下绑定按键点击事件,点击”开始/继续“只需要调用requestAnimationFrame(game_control);,点击”暂停“只需要调用cancelAnimationFrame(run_id);

// 绑定开始按钮点击事件
start_btn.onclick = function () {
    run_id = requestAnimationFrame(game_control);
};

// 绑定暂停按钮点击事件
pause_btn.onclick = function () {
    cancelAnimationFrame(run_id);
};

        点击“重新开始”的话,则需要先暂停动画,然后删除画面上的蛇和食物,初始化所有设置,然后再调用requestAnimationFrame(game_control);开始游戏

注:初始化时需要初始化得分与难度等级,这里解释下为什么要将第一个食物设置为蛇头下一个位置,因为这样的话蛇会自动先吃一个食物,继而可以通过“开始 / 继续” 一个按钮实现开始和继续操作,同时run_game()函数中的食物绘制是在蛇吃到食物之后,保证第一个食物顺利绘制,这样的话score就需要初始化为-1
// 用于初始化游戏各项参数
function init_game() {
    snake = [41, 40]; 
    direction = 1; 
    food = 42;
    score = -1; 
    time_internal = simply_mode;
    enabled = true;
    score_cal.innerText = "目前得分: 0分"; // 更新得分
    mode_item[0].checked = true; // 重置难度等级为简单
}

// 绑定重新开始按钮点击事件
restart_btn.onclick = function () {
    cancelAnimationFrame(run_id);
    // 将原有的食物和蛇的方块都绘制成白色
    for(var i = 0; i < snake.length; i++){
        draw(snake[i], "White");
    }
    draw(food, "White");
    // 初始化游戏各项参数
    init_game();
    run_id = requestAnimationFrame(game_control);			
};

我们绑定game over中的两个按键”再来一把“和”取消“,”再来一把“只需要完成“重新开始”里面的事件即可,”取消“只需要完成”重新开始“点击操作中除了开始游戏的部分,即除了run_id = requestAnimationFrame(game_control);这两个按钮都需要设置”弹窗“的display属性为none

具体实现如下:

// 绑定游戏结束时的取消按钮点击事件
cancel_btn.onclick = function () {
    for(var i = 0; i < snake.length; i++){
        draw(snake[i], "White");
    }
    draw(food, "White");
    init_game();
    game_over_div.style.display = "none";
}

// 绑定游戏结束时的再来一把按钮点击事件
once_again_btn.onclick = function () {
    for(var i = 0; i < snake.length; i++){
        draw(snake[i], "White");
    }
    draw(food, "White");
    init_game();
    game_over_div.style.display = "none";
    run_id = requestAnimationFrame(game_control);
}

如何设置快捷键,快捷键只需要用JavaScript模拟点击对应的按钮即可,实现如下:

// 同时绑定R 重启,P 暂停,C 继续
document.onkeydown = function (event) {
    const keycode = event.keyCode;
    if(keycode == 82){
        // R 重启
        restart_btn.onclick();
    } else if(keycode == 80){
        // P 暂停
        pause_btn.onclick();
    } else if(keycode == 67){
        // C 继续
        start_btn.onclick();
    } 
};

项目源码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <title>贪吃蛇小游戏</title>
    <style>
        button {
            width: 100px;
            height: 40px;
            font-weight: bold;
        }

        #game_title {
            margin-left: 95px;
        }

        #canvas {
            border: 1px solid #000000; /* 设置边框线 */
        }

        #score {
            font-weight: bold;
        }

        #mode_form {
            font-weight: bold;
        }

        #game_over {
            display: none; /* 设置game over 窗口不可见 */
            position: fixed;
            top: 190px;
            left: 65px;
            width: 280px;
            height: 160px;
            background-color: aliceblue;
            border-radius: 5px;
            border: 1px solid #000; /* 设置边框线 */
        }

        #once_again {
            position: relative;
            left: 20px;
        }

        #cancel {
            position: relative;
            left: 50px;
        }
    </style>
</head>
<body>
<h1 id="game_title">贪吃蛇小游戏</h1>
<canvas id="canvas" width="400" height="400"></canvas>
<div id="game_over">
    <h3 id="game_over_text" align="center">游戏结束!</h3>
    <h3 id="game_over_score" align="center">您的最终得分为: 0分</h3>
    <button id="once_again">再来一把</button>
    <button id="cancel">取消</button>
</div>
<br>
<div id="game_info">
    <p><b>游戏说明:</b></p>
    <p>
        <b>1</b>. 用键盘上下左右键(或者IJKL键,或者WSAD键)控制蛇的方向,寻找吃的东西
        <br><b>2</b>. 每吃一口就能得到一定的积分,同时蛇的身子会越吃越长
        <br><b>3</b>. 不能碰墙,不能咬到自己的身体,更不能咬自己的尾巴
        <br><b>4</b>. 在下方单选框中选择难度等级,点击"<b>开始 / 继续</b>"即开始游戏,点击"<b>暂停</b>"则暂停游戏,
        <br>&nbsp;&nbsp;&nbsp;&nbsp;再点击"<b>开始 / 继续</b>"继续游戏,点击"重新开始"则重新开始游戏
        <br><b>5</b>. <b>快捷键</b>: "<b>C</b>"表示开始或继续,"<b>P</b>"表示暂停,"<b>R</b>"表示重新开始
    </p>
</div>

<p id="score">目前得分: 0分</p>
<form action="" id="mode_form">
    难度等级:
    <input type="radio" name="mode" id="simply" value="simply" checked/>
    <label for="simply">简单</label>
    <input type="radio" name="mode" id="middle" value="middle"/>
    <label for="middle">中级</label>
    <input type="radio" name="mode" id="hard" value="hard"/>
    <label for="hard">困难</label>
</form>
<br/>
<button id="startButton">开始 / 继续</button>
<button id="pauseButton">暂停</button>
<button id="restartButton">重新开始</button>

<script>
    const canvas = document.getElementById("canvas");
    const ctx = canvas.getContext("2d");

    const start_btn = document.getElementById("startButton");
    const pause_btn = document.getElementById("pauseButton");
    const restart_btn = document.getElementById("restartButton");
    const once_again_btn = document.getElementById("once_again");
    const cancel_btn = document.getElementById("cancel");
    const game_over_div = document.getElementById("game_over");
    const game_over_score = document.getElementById("game_over_score");

    const score_cal = document.getElementById("score");
    const mode_item = document.getElementsByName("mode");

    // 用刷新间隔代表蛇的速度,刷新间隔越长,则蛇的速度越慢
    const simply_mode = 200;
    const middle_mode = 100;
    const hard_mode = 50;

    //注意要改为var const是不会修改的
    let snake = [41, 40]; // 蛇身体队列
    let direction = 1; // 方向:1为向右,-1为向左,20为向下,-20为向上
    let food = 42; // 食物位置,取值为0~399
    let n; // 蛇的下一步的方向(由键盘和蛇的原方向决定)
    let score = -1; // 得分
    let time_internal = simply_mode; // 刷新时间间隔,用于调整蛇的速度,默认为简单模式

    let enabled = true; // 用于控制是否刷新,实现通过一定频率刷新
    let run_id; // 请求ID,用于暂停功能

    // 产生min~max的随机整数,用于随机产生食物的位置
    function random(min, max) {
        return Math.floor(Math.random() * (max - min)) + min;
    }

    // 用于绘制蛇或者是食物代表的方块,seat为方块位置,取值为0~399,color为颜色
    function draw(seat, color) {
        ctx.fillStyle = color; // 填充颜色
        // fillRect的四个参数分别表示要绘制方块的x坐标,y坐标,长,宽,这里为了美观留了1px用于边框
        ctx.fillRect(
            (seat % 20) * 20 + 1,
            Math.floor(seat / 20) * 20 + 1,
            18,
            18
        );
    }

    // 同步难度等级
    function syncMode() {
        let mode_value = "";
        for (let i = 0; i < mode_item.length; i++) {
            if (mode_item[i].checked) {
                mode_value = mode_item[i].value;//原来是mode_item.value
            }
        }
        switch (mode_value) {
            case "simply":
                time_internal = simply_mode;
                break;
            case "middle":
                time_internal = middle_mode;
                break;
            case "hard":
                time_internal = hard_mode;
                break;
        }
    }

    // 用于绑定键盘上下左右事件,我设置了wsad,或者ijkl,或者上下左右方向键,代表上下左右方向
    // 同时绑定R 重启,P 暂停,C 继续,注意:若是这几个键则不需要更新direction的值,操作结束后直接返回即可
    document.onkeydown = function (event) {
        const keycode = event.keyCode;
        if (keycode === 82) {
            // R 重启
            restart_btn.onclick();
            return;
        } else if (keycode === 80) {
            // P 暂停
            pause_btn.onclick();
            return;
        } else if (keycode === 67) {
            // C 继续
            start_btn.onclick();
            return;
        } else if (keycode <= 40) {
            // 上 38 下 40 左 37 右 39
            n = [-1, -20, 1, 20][keycode - 37] || direction; // 若keycode为其他值,则方向不变
        } else if (keycode <= 76 && keycode >= 73) {
            // i 73 j 74 k 75 l 76
            n = [-20, -1, 20, 1][keycode - 73] || direction;
        } else {
            switch (keycode) {
                case 87: //w
                    n = -20;
                    break;
                case 83: //s
                    n = 20;
                    break;
                case 65: //a
                    n = -1;
                    break;
                case 68: //d
                    n = 1;
                    break;
                default:
                    n = direction;
            }
        }
        direction = snake[1] - snake[0] === n ? direction : n; // 若方向与原方向相反,则方向不变
    };

    // 用于初始化游戏各项参数
    function init_game() {
        snake = [41, 40];
        direction = 1;
        food = 42;
        score = -1;
        time_internal = simply_mode;
        enabled = true;
        score_cal.innerText = "目前得分: 0分"; // 更新得分
        mode_item[0].checked = true; // 重置难度等级为简单
    }

    function game_over() {
        cancelAnimationFrame(run_id);
        game_over_score.innerText = "您的最终得分为: " + score + "分";
        game_over_div.style.display = "block";
    }

    // 启动或继续游戏
    function run_game() {
        syncMode(); // 同步难度等级
        n = snake[0] + direction; // 找到新蛇头坐标
        snake.unshift(n); // 添加新蛇头
        // 判断蛇头是否撞到自己或者是否超出边界
        if (
            snake.indexOf(n, 1) > 0 ||
            n < 0 ||
            n > 399 ||
            (direction === 1 && n % 20 === 0) ||
            (direction === -1 && n % 20 === 19)
        ) {
            game_over();
        }
        draw(n, "#1a8dcc"); // 绘制新蛇头为浅蓝色
        draw(snake[1], "#cececc"); // 将原来的蛇头(浅蓝色)变成蛇身(浅灰色)
        if (n === food) {
            score = score + 1;
            score_cal.innerText = "目前得分: " + score; // 更新得分
            while (snake.indexOf((food = random(0, 400))) >= 0) ; // 重新刷新食物,注意食物应不在蛇内部
            draw(food, "Yellow"); // 绘制食物
        } else {
            draw(snake.pop(), "White"); // 将原来的蛇尾绘制成白色
        }
        // setTimeout(arguments.callee, time_internal); //之前的方案,无法实现暂停和游戏的继续
    }

    // 控制游戏的刷新频率,每隔time_internal时间间隔刷新一次
    function game_control() {
        if (enabled) {
            enabled = false;
            requestAnimationFrame(run_game);
            setTimeout(() => enabled = true, time_internal);
        }
        run_id = requestAnimationFrame(game_control);
    }

    // 绑定开始按钮点击事件
    start_btn["onclick"] = function () {
        run_id = requestAnimationFrame(game_control);
    };

    // 绑定暂停按钮点击事件
    pause_btn.onclick = function () {
        cancelAnimationFrame(run_id);
    };

    // 绑定重新开始按钮点击事件
    restart_btn.onclick = function () {
        cancelAnimationFrame(run_id);
        // 将原有的食物和蛇的方块都绘制成白色
        for (let i = 0; i < snake.length; i++) {
            draw(snake[i], "White");
        }
        draw(food, "White");
        // 初始化游戏各项参数
        init_game();
        run_id = requestAnimationFrame(game_control);
    };

    // 绑定游戏结束时的取消按钮点击事件
    cancel_btn.onclick = function () {
        for (let i = 0; i < snake.length; i++) {
            draw(snake[i], "White");
        }
        draw(food, "White");
        init_game();
        game_over_div.style.display = "none";
    }

    // 绑定游戏结束时的再来一把按钮点击事件
    once_again_btn.onclick = function () {
        for (let i = 0; i < snake.length; i++) {
            draw(snake[i], "White");
        }
        draw(food, "White");
        init_game();
        game_over_div.style.display = "none";
        run_id = requestAnimationFrame(game_control);
    }
</script>
</body>
</html>



这篇关于JavaScript实现贪吃蛇小游戏的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程