首页前端开发JavaScript实例分享之JavaScript实现贪吃蛇小游戏

实例分享之JavaScript实现贪吃蛇小游戏

时间2024-01-31 04:10:02发布访客分类JavaScript浏览885
导读:收集整理的这篇文章主要介绍了实例分享之JavaScript实现贪吃蛇小游戏,觉得挺不错的,现在分享给大家,也给大家做个参考。本篇文章给大家带来了利用JavaScript实现贪吃蛇小游戏的实例,希望对大家有帮助。JavaScript实现贪吃蛇...
收集整理的这篇文章主要介绍了实例分享之JavaScript实现贪吃蛇小游戏,觉得挺不错的,现在分享给大家,也给大家做个参考。本篇文章给大家带来了利用JavaScript实现贪吃蛇小游戏的实例,希望对大家有帮助。

JavaScript实现贪吃蛇小游戏

功能概述

本程序实现了如下功能:

  1. 贪吃蛇的基本功能

  2. 统计得分

  3. 开始与暂停

  4. 选择难度等级

  5. 设置快捷键

    5.1 通过ijkl,wsad也能实现方向的切换

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

实现过程

最开始的实现原理其实是参考的csdn的一位大神,他用JavaScript20行就实现了贪吃蛇的基本功能,难等可贵的是还没有bug,链接在此

要实现贪吃蛇大概有以下几个步骤:

  • 画一个蛇的移动区域

  • 画一条蛇

  • 画食物

  • 让蛇动起来

  • 设定游戏规则

  • 设置难度等级

  • 设置开始与暂停

  • 设置游戏结束后续操作

  • 实现人机交互页面

注:下面的过程讲解部分只是讲述了部分原理与实现,建议一边看最后的完整代码,一边看下面的讲解,更容易理解每一部分的原理与实现

画蛇的活动区域

首先我们画蛇的活动区域,我们采用JavaScript的canvas进行绘制

我们用一个400 × 400 400\times 400400×400的区域作为蛇的活动区域

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

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

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

画蛇和食物

效果如下:

在画蛇前我们需要想下蛇的数据结构,在这里我们采取最简单的队列表示蛇

  • 队首表示蛇头位置,队尾表示蛇尾位置

  • 我们将之前画的 400 × 400 400\times 400 @H_956_126@400×400区域划分为400个 20 × 20 20\times 20 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 18\times 1818×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. 将蛇运动的下一个位置变成新蛇头
    • 将下一个位置加入蛇队列
    • 绘制下一个方块为浅蓝色
  2. 把旧蛇头变成蛇身
    • 绘制旧蛇头为浅灰色
  3. 删除旧蛇尾
    • 将旧蛇尾弹出蛇队列
    • 绘制旧蛇尾位置为白色

当蛇吃掉食物时(蛇的下一个位置为食物所在位置)则需更新食物的位置,并绘制新食物为黄色,此时则不需要删除旧蛇尾,这样可以实现蛇吃完食物后长度的增加功能

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

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的动画的部分了,建议看下《JavaScript高级程序设计(第4版)》第18章的部分,讲的很详细。

在最初的“20行JavaScript实现贪吃蛇”中并没有实现开始与暂停,其实现动态效果的方法为设置一个立即执行函数!function() { } (); ,然后在该函数中使用setTimeout(arguments.callee, 150); ,每隔0.15秒调用此函数,从而实现了网页的不断刷新,也就是所谓的动态效果。

后来,我通过web课程老师的案例(弹球游戏)中了解到requestAnimationFrame方法可以实现动画效果,于是我便百度查询,最后在翻书《JavaScript高级程序设计(第4版)》第18章动画与Canvas图形中得到启发–如何实现开始与取消,如何自定义时间间隔(实现难度调节,蛇的速度)

书中给出的开始动画与取消动画的方法如下:

注:为了便于理解,自己修改过原方法

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

p 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>
    /p>
    

其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_p.style.display = "block";
}
    

实现人机交互页面

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

  1. 游戏说明
  2. 人机交互按钮:开始/继续,暂停,重新开始
  3. 快捷键
    • 由于在游戏过程中通过鼠标移动到暂停键暂停,时间上太慢,可能造成游戏终止,故应该设置开始/继续(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_p.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_p.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();
    }
 }
    ;
    

问题、调试与解决

注: 此部分为本人在实现过程中出现的bug、调试过程以及解决方法,感兴趣的可以看看,不感兴趣的也可以跳过此部分,直接看文末的完整代码

问题1:点击暂停和开始,游戏正常开始,按P也可以实现暂停,按C则画面出现蛇所在的方格乱跳,无法正常开始,但是按C的操作中只模拟了”开始 / 继续“按钮的点击?

效果如下:

调试过程:因为蛇头的位置是由direction控制的,故想到设置断点,同时监测这个变量的值的变化,发现这个值在按完P和C时被更新成很大的数,进而去找direction在哪里被更新,发现点击P或C后还需要执行下面这一行代码,而实际上是不需要的

direction = snake[1] - snake[0] == n ? direction : n;
     // 若方向与原方向相反,则方向不变

解决方法:只需要执行完对应的模拟鼠标点击相应按钮事件之后就直接return就可以了

原代码与修改后的代码如下:

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;
 // 若方向与原方向相反,则方向不变}
    ;
    

问题2:调整难度等级后,蛇的速度并没有发生改变,但是通过console.log()发现确实调用了同步难度模式的函数?

调试过程:在同步难度等级的函数中设置console.log()方法,输出time_internal变量,同时设断点调试,发现time_internal变量不发生变化,mode_value变量始终为undefined,最后发现应该是值传递时的错误mode_value = mode_item.value;

解决方法:修改值传递的方法,加上索引,改为mode_value = mode_item[i].value;

原代码和修改后的代码如下:

// 同步难度等级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;
//原来是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;
    }
}
    

完整代码

!DOCTYPE htML>
    html lang="en">
      head>
        meta charset="UTF-8" />
        meta http-equiv="X-UA-Compatible" content="IE=Edge" />
        meta name="viewport" content="width=device-width, initial-scale=1.0" />
        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>
    	p 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>
    	/p>
    	br>
    	p 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>
    	/p>
            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_p = 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是不会修改的		var snake = [41, 40];
     // 蛇身体队列		var direction = 1;
     // 方向:1为向右,-1为向左,20为向下,-20为向上		var food = 42;
     // 食物位置,取值为0~399		var n;
     // 蛇的下一步的方向(由键盘和蛇的原方向决定)		var score = -1;
     // 得分		var time_internal = simply_mode;
     // 刷新时间间隔,用于调整蛇的速度,默认为简单模式		let enabled = true;
     // 用于控制是否刷新,实现通过一定频率刷新		let run_id;
 // 请求ID,用于暂停功能		// 产生min~max的随机整数,用于随机产生食物的位置		function random(min, max) {
    			const num = Math.floor(Math.random() * (max - min)) + min;
    			return num;
		}
		// 用于绘制蛇或者是食物代表的方块,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() {
    			var mode_value = "";
    			for (var 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_p.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(var 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(var i = 0;
     i  snake.length;
 i++){
    				draw(snake[i], "White");
			}
    			draw(food, "White");
    			init_game();
    			game_over_p.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_p.style.display = "none";
    			run_id = requestAnimationFrame(game_control);
		}
        /script>
      /body>
    /html>
    

【相关推荐:javascript学习教程

以上就是实例分享之JavaScript实现贪吃蛇小游戏的详细内容,更多请关注其它相关文章!

声明:本文内容由网友自发贡献,本站不承担相应法律责任。对本内容有异议或投诉,请联系2913721942#qq.com核实处理,我们将尽快回复您,谢谢合作!

上一篇: 聊聊一些Node.js 缓冲区(Buffer...下一篇:探索下浏览器和 Node.js 为什么会...猜你在找的JavaScript相关文章 html font标签如何设置字体大小?html font标签属性用法介绍2022-05-16vue3+TypeScript+vue-router的使用方法2022-04-16vue3获取当前路由地址2022-04-16如何利用React实现图片识别App2022-04-16JavaScript展开运算符和剩余运算符的区别详解2022-04-16微信小程序中使用vant框架的具体步骤2022-04-16Vue elementUI表单嵌套表格并对每行进行校验详解2022-04-16如何利用Typescript封装本地存储2022-04-16微信小程序中wxs文件的一些妙用分享2022-04-16JavaScript的Set数据结构详解2022-04-16 其他相关热搜词更多phpjavapython程序员loadpost-format-gallery

若转载请注明出处: 实例分享之JavaScript实现贪吃蛇小游戏
本文地址: https://pptw.com/jishu/593462.html
聊聊JavaScript中的7种位运算符,看看在实战中如何妙用? 指针常量与常量指针举例说明

游客 回复需填写必要信息