如何用不到200行代码实现经典小游戏贪吃蛇,附源代码及详细实现思路

不多废话,直接上链接

链接:https://pan.baidu.com/s/1ZKtVNhzR4fIzNZSGWgFKQw?pwd=zglt 
提取码:zglt

有需要的好兄弟们可以直接取用,想要了解一下编程思路的朋友们可以继续往下看,如有任何问题可以在评论区留言。

首先贪吃蛇小游戏主要需要实现一下几个功能:

(1)小蛇不断向前移动

(2)小蛇根据键盘按键改变移动方向

(3)小蛇撞墙或撞到自己后游戏结束

(4)地图内随机生成苹果

(5)小蛇吃到苹果后增加一格

接下来我们逐条实现

扫描二维码关注公众号,回复: 14581066 查看本文章

首先在页面内生成一个div,划出800*800的区域,并使其居中,将其class名设置为back

back的内容如下:

.back {
            border: 2px solid black;
            width: 800px;
            height: 800px;
            margin: 0 auto;
        }

生成效果:

 然后在.back中添加两个属性 display: flex;和 flex-wrap: wrap;

.back {
            border: 2px solid black;
            width: 800px;
            height: 800px;
            margin: 0 auto;
            display: flex;
            flex-wrap: wrap;
        }

这两个属性是弹性盒子的内容,第一个属性设置为flex值将back声明为弹性盒子,第二个属性设置该盒子内容自动换行,设置这两个属性的原因我们接下来讲。

然后我们声明一个css样式名为.box,设置.box的宽高为40,并设置为怪异盒子

.box {
            width: 40px;
            height: 40px;
            box-sizing: border-box;
        }

box-sizing: border-box;用于将元素设置为怪异盒子,怪异盒子的特殊之处在于其总宽高固定,设置边框不会增加其实际宽高。

左为怪异盒子,右为普通盒子,给予20像素边框右者实际大小为140*140

这时页面中并没有叫.box的元素,接下来我们使用JavaScript代码给大背景添加小格子。

首先获取clas名为back的大背景。

var back = document.querySelector(".back");

接下来写一个执行400次的for循环,每次循环都用.creatElement()函数生成一个新的div标签,并将该标签的class名设置为box,然后使用.appendChild方法添加进大背景中:

for (let i = 0; i < 400; i++) { //添加地图格子
        var box = document.createElement("div");
        box.className = "box";
        back.appendChild(box);
    }

由于大背景宽高为800,小格子宽高为40,这样大背景中一共可以塞下20*20=400个小格子,由于我们给父元素设置为了弹性盒子且自动换行,这时当第一行被小格子塞满时多余的小格子就自动填充到下一行中(类似于浮动),这样逐行填充后就生成了一个400格的棋盘。

为了方便演示,我们给小格子添加一个边框,这是最终效果:

 接下来我们开始画蛇,首先用querySelectAll()方法获取大背景中的400个小格子,并命名为box

 var box = document.querySelectorAll(".box"); //获取所有格子

然后给格子添加颜色,需要注意的是,由于游戏开始时蛇头的位置是朝右的,所以蛇头一格并不是box[0],而是box[2],这里我设置蛇头为青色,身体为灰色。

//声明初始蛇样式
    box[0].style.background = "grey";
    box[1].style.background = "grey";
    box[2].style.background = "cyan";

最终效果:

 现在我们有了一条三格长的小蛇,接下来就是如何让小蛇动起来,这里当然需要用到计时器

var timer = setInterval(move, 200);

setInterval()为间隔计时器,内部传入两个参数,第一个为调用的函数名,第二个为间隔时间(单位为毫秒),当前该计时器会每隔200毫秒调用一次函数,即让小蛇一秒走五次,如果觉得太慢也可以把这个值调小,这样小蛇会移动得更快。

然后我们需要写让小蛇动起来的函数move(),不过在此之前,先将小蛇的头和尾位置以及整体位置存储一下。

//声明蛇,并以行和列的形式存储蛇每一格的位置
    var snake = [{hang:0,lie: 0}, {hang:0,lie: 1}, {hang:0,lie: 2}];
    var head = {}; //声明蛇头
    head.hang = snake[2].hang;//获取当前蛇头位置
    head.lie = snake[2].lie;
    var tail = {}; //声明蛇尾
    tail.hang = snake[0].hang;//获取当前蛇尾位置
    tail.lie = snake[0].lie;

接下来我们来写函数move(),首先我们要理解一点,想让小蛇前进一个其实并不需要让小蛇每一段身体都前进,只需要让头部的位置前移一格,让原本头部的位置变为身体,再删除最后一节尾部即可。

 这样我们就有了基本的实现思路,接下来就是如何用代码实现。

首先在移动前,我们需要判断蛇头是否撞墙,若撞墙则游戏直接结束无需再进行移动,判断是否撞墙,即判断新的头部位置行和列数值是否大于-1且小于20,不过向右移动时只需要判断右侧即可。

function move() {
        if (head.lie + 1 < 20) { //判断是否向右撞墙

        } else {
            alert("游戏结束"); //撞墙,游戏结束;
            clearInterval(timer);//清除定时器,让小蛇停止移动
        };
    }

由于头部遇到的情况比较复杂(撞墙,吃苹果,撞自己等),所以这里我们将头部移动单独封装成一个函数moveHead(),不过在调用该函数前,先设置好新头部的位置。

    function move() {
        if (head.lie + 1 < 20) { //判断是否向右撞墙
            head.列 += 1;//向右移动即头部列位置+1
            moveHead();//调用头部移动函数
        } else {
            alert("游戏结束"); //撞墙,游戏结束;
            clearInterval(timer); //清除定时器,让小蛇停止移动
        };
    }

    function moveHead() {}//声明头部移动函数

接下来正式开始移动小蛇,首先获取到新的头部位置在数组中的下标,由于地图每行为20格,那么位置下标=行数值*20+列数值,获取后将新头部格子刷成青色。

    function moveHead() { //声明头部移动函数
        //获取头部位置在数组中的下标,并储存为positon
        var position = head.hang * 20 + head.lie;
        //利用下标在box数组中定位新的头位置,并将其刷成青色
        box[position].style.background = "cyan";
    }

当前效果:

 然后我们需要将原蛇头位置的青色变为灰色,原蛇头位置即当前snake数组中的最后一位,即数组长度-1的位置(数组下标从0开始,所以减一)。

    function moveHead() { //声明头部移动函数
        //获取新头部位置在数组中的下标,并储存为positon
        var position = head.hang * 20 + head.lie;
        //利用下标在box数组中定位新的头位置,并将其刷成青色
        box[position].style.background = "cyan";
        //获取原有头部位置的下标,并储存为positon
        position = snake[snake.length - 1].hang * 20 + snake[snake.length - 1].lie;
        //将原蛇头位置刷成灰色
        box[position].style.background = "grey";
    }

当前效果:

接下来就是删除蛇尾,蛇尾位置即snak[0]存储的位置。

    function moveHead() { //声明头部移动函数
        //获取新头部位置在数组中的下标,并储存为positon
        var position = head.hang * 20 + head.lie;
        //利用下标在box数组中定位新的头位置,并将其刷成青色
        box[position].style.background = "cyan";
        //获取原有头部位置的下标,并储存为positon
        position = snake[snake.length - 1].hang * 20 + snake[snake.length - 1].lie;
        //将原蛇头位置刷成青色
        box[position].style.background = "grey";
        //获取蛇尾位置
        position = snake[0].hang * 20 + snake[0].lie;
        //删除蛇尾颜色
        box[position].style.background = "";
    }

 最终效果:

 这时我们还需要做最后的处理工作,虽然页面中的snake位置已经变化了,但是snake数组中存储的位置并没有改变,所以现在我们要修改snake数组中的内容。

        var newhead = { //处理对象浅复制问题
            "hang": head.hang,
            "lie": head.lie
        };
        snake.push(newhead) //将新的头位置添加进数组末尾
        snake.shift() //删除数组中尾部位置

由于这里涉及到对象浅复制问题,所以每次添加都需要声明一个新对象,感兴趣的朋友们可以自行了解一下。

最终效果:

 这样小蛇的移动就完成了,接下来我们来控制小蛇的移动方向。

首先我们声明一个变量,设置其数值为ArrowRight,为什么是这个值我们接下来讲。

    var ahead = "ArrowRight"; //声明蛇的前进方向

然后我们给页面添加一个监听事件,监听鼠标按下,并传入一个参数e。

var ahead = 39; //声明蛇的前进方向
    document.addEventListener("keydown", (e) => {})//添加键盘按下的监听事件并传入e

这里传入的e可以简单理解为事件本身,其内部存储了触发该事件时的一系列数据,这里我们需要用到其中的一个数据.key,该属性存储了触发事件的按键名称。

打印e.key时分别点击小键盘上下左右四个键时返回的名称:

 这就是我们设置ahead的值为ArrowRight的原因,在JavaScript中不同的按键有不同的名称,我们只需要根据名称就可以利用分支语句决定小蛇前进的方向,同时我们还可以添加一个判断语句,防止小蛇出现180度调头的情况(这里使用了JavaScript的三目运算,感兴趣的朋友们可以自行了解)。

    document.addEventListener("keydown", (e) => { //添加键盘按下的监听事件并传入e
        switch (e.key) { //添加分支语句,设置移动方向
            case "ArrowUp": //判断e.key是否等于该关键字
                ahead != "ArrowDown" ? ahead = e.key : ""; //判断并给ahead赋新值
                break; //结束Switch执行,防止下方的代码影响结果
            case "ArrowRight":
                ahead != "ArrowLeft" ? ahead = e.key : ""; //判断并给ahead赋新值
                break;
            case "ArrowDown":
                ahead != "ArrowUp" ? ahead = e.key : ""; //判断并给ahead赋新值
                break;
            case "ArrowLeft":
                ahead != "ArrowRight" ? ahead = e.key : ""; //判断并给ahead赋新值
                break;
        }
        console.log(e.key);
    })

然后再给前面的move()函数也添加一个Switch分支语句,通过ahead的值判断头部向哪个方向移动,向上移动时头部行位置减一,向下移动时头部行位置加一,向左和向右则为头部列位置减一和加一。

    function move() {
        switch (ahead) { //通过ahead判断移动方向
            case "ArrowUp": //是否向上
                if (head.hang - 1 > -1) {
                    head.hang -= 1; //向上移动即头部行位置-1
                    moveHead();
                } else {
                    alert("游戏结束");
                    clearInterval(timer);
                };
                break;
            case "ArrowDown": //是否向下
                if (head.hang + 1 < 20) {
                    head.hang += 1; //向下移动即头部行位置-1
                    moveHead();
                } else {
                    alert("游戏结束");
                    clearInterval(timer);
                };
                break;
            case "ArrowLeft": //是否向左
                if (head.lie - 1 > -1) {
                    head.lie -= 1; //向左移动即头部列位置-1
                    moveHead();
                } else {
                    alert("游戏结束");
                    clearInterval(timer);
                };
                break;
            case "ArrowRight": //是否向右
                if (head.lie + 1 < 20) {
                    head.lie += 1; //向右移动即头部列位置+1
                    moveHead();
                } else {
                    alert("游戏结束");
                    clearInterval(timer);
                };
                break;
        }
    }

由于目前我们可以通过按键来改变ahead的值,而move()函数的移动方向由ahead决定,所以我们就间接实现了通过按键控制小蛇的移动方向,效果如下:

 下一步就是声明苹果了,首先我们创建函数addApple(),并声明对象apple存储苹果的位置

    var apple = {};

    function addapple() {}

接下来我们需要用到一个数学方法Math.random(),该方法的作用是声明一个0到1之间的随机浮点数,由于我们的地图大小为20*20格,所以我们将生成的数字乘20并取整,这样就得到了一个0到20之间的随机整数(向下取整所以不含20)。

function addapple() { //随机生成苹果函数
        apple.hang = parseInt(Math.random() * 20); //随机生成行
        apple.lie = parseInt(Math.random() * 20); //随机生成列
        var position = apple.lie + apple.hang * 20 //通过行和列随机生成苹果
        box[position].style.background = "red"; //在随机确定的位置刷红色
    }

这时我们调用函数,苹果就会出现在地图中

 不过由于目前苹果的位置完全随机,所以有可能会直接生成在小蛇身上,这是我们需要避免的问题,即当随机生成的苹果位置有颜色时重新生成,这里我们可以通过使用一个简单的递归来解决该问题。

var apple = {}; //声明对象存储苹果位置

    function addapple() { //随机生成苹果函数
        apple.hang = parseInt(Math.random() * 20); //随机生成行
        apple.lie = parseInt(Math.random() * 20); //随机生成列
        var position = apple.lie + apple.hang * 20 //通过行和列随机生成苹果
        if (box[position].style.background != "") { //判断随机生成的苹果位置是否有颜色
            addapple(); //若有颜色重新调用该函数生成
        } else {
            box[position].style.background = "red"; //若无颜色则刷红
        }
    }

这时当生成的随机位置有颜色时该函数就会重新被调用,生成一个新的苹果位置,若没有颜色则结束递归,并在该位置刷红色。

然后就是最后一步,当小蛇吃到苹果时变长一格,同时生成新的苹果位置,其实这一功能很好实现,我们在移动小蛇时会删除小蛇的尾部,现在只需要让小蛇头部碰到苹果时尾部不删除即可增加一格长度,所以我们需要对movehead()函数进行修改:

    function moveHead() {
        //获取新头部位置在数组中的下标,并储存为positon
        var position = head.hang * 20 + head.lie;
        if (box[position].style.background == "red") { //判断新的头部位置是否为红色
            snake.unshift({
                "hang": snake[0].hang,
                "lie": snake[0].lie
            }); //若为红色则复制一份蛇尾,删除时便会保留蛇尾
            addapple(); //调用函数,生成新苹果
        }
        box[position].style.background = "cyan";
        position = snake[snake.length - 1].hang * 20 + snake[snake.length - 1].lie;
        box[position].style.background = "grey";
        position = snake[0].hang * 20 + snake[0].lie;
        box[position].style.background = "";
        var newhead = {
            "hang": head.hang,
            "lie": head.lie
        };
        snake.push(newhead)
        snake.shift() //删除数组中尾部位置
    }

同时这里我们还可以添加一个判断,若新蛇头位置为灰色,则说明小蛇撞到了自己,这时可以直接结束游戏:

    function moveHead() {
        //获取新头部位置在数组中的下标,并储存为positon
        var position = head.hang * 20 + head.lie;
        if (box[position].style.background == "red") { //判断新的头部位置是否为红色
            snake.unshift({
                "hang": snake[0].hang,
                "lie": snake[0].lie
            }); //若为红色则复制一份蛇尾,删除时便会保留蛇尾
            addapple(); //调用函数,生成新苹果
        } else if (box[position].style.background == "grey") {//判断新的头部位置是否为灰色
            alert("游戏结束");//若为灰色则直接结束游戏
            clearInterval(timer);
        }
        box[position].style.background = "cyan";
        position = snake[snake.length - 1].hang * 20 + snake[snake.length - 1].lie;
        box[position].style.background = "grey";
        position = snake[0].hang * 20 + snake[0].lie;
        box[position].style.background = "";
        var newhead = {
            "hang": head.hang,
            "lie": head.lie
        };
        snake.push(newhead)
        snake.shift() //删除数组中尾部位置
    }

到这里一款可以正常游玩的贪吃蛇小游戏就算基本完工了,删除注释的话总代码量可能都不到150行,虽然有很多细节有待优化;

那么本期的内容就到这里,如有任何问题欢迎在评论区留言,up都会尽量解答。

----------------------------------------------分割线-----------------------------------------------

后续我又对代码进行了一点优化,首先是添加了分数和规则

规则不难写,分数的话只需要在全局声明一个变量number 来存储分数,每次撞到苹果后该数值加一即可。

var p = document.querySelector("p");//获取第一行,分数行
var number = 0; //存储当前分数

function moveHead() { //声明头部移动函数
        .....
        if (box[position].style.background == "red") { //判断新的头部位置是否为红色
            snake.unshift({
                "hang": snake[0].hang,
                "lie": snake[0].lie
            }); //若为红色则复制一份蛇尾,删除时便会保留蛇尾
            addapple(); //调用函数,生成新苹果

            number++; //number加一分
            p.innerHTML = number + "分"; //改变p标签内容

        } else if (box[position].style.background == "grey") { //判断新的头部位置是否为灰色
            alert("游戏结束"); //若为灰色则直接结束游戏
            clearInterval(timer);
        }
        .....
    }

然后是暂停功能,只需要在addEventListener监听事件的Switch分支中添加一个空格分支,单击空格时弹出一个窗口即可暂停,当浏览器显示窗口时计时器是停止运行的。

document.addEventListener("keydown", (e) => { //添加键盘按下的监听事件并传入e
        switch (e.key) { //添加分支语句,设置移动方向
            .....
            case " ":
                alert("暂停中。。。");
        }
        console.log(e.key);
    });

最后我把蛇和苹果的颜色放到了代码最上方,这样可以直接修改所有代码中的颜色。

猜你喜欢

转载自blog.csdn.net/weixin_47040861/article/details/127707643