阅读 391

检验原生JS功底的时候到了--纯JS实现贪食蛇

前言

以前读书学习C语言的时候,总是想着要完成贪食蛇这款游戏,但是那时候我的编程基础和编程思维还不够扎实和成熟,所以导致怎么折腾都写不出来,但是现在工作了,就想着要完成读书时候的小心愿,就开始动手写了,写着写着,就还真是写出来了,哈哈,看来我也是成长了不少。。。

贪食蛇这款游戏,相信大家一定都不陌生了,童年之作,用编程语言编写出来的贪食蛇,无非就是要实现几个功能:蛇的移动、是否吃到自己、吃到食物、撞墙,只要把需要实现的功能给列出来了,接下来只要一个个去实现就好了,思路清晰还是很重要的,因为都是用原生JS来编写的,所以对JS功底还是有点要求的,我觉得有时间练手的可以尝试一下,好了,废话不多说,进去正题。。。

分析思路

首先我们需要创建一个大画布,开始和暂停按钮,画布是蛇在里面跑的,开始和暂停分别控制开始和停止,这里需要用到HTML和CSS,但我选择了用JS实现,毕竟练手嘛,当然是要往不同的方法去出发。

然后动态创建蛇的节点(一个蛇头两个蛇点)和食物,好了,最基本的HTML和CSS已经用JS来实现了,接下来开始实现需要实现的功能了

  • 蛇的移动
  • 撞墙的判断
  • 是否吃到食物
  • 是否吃到自己

好了,思路分析好了就开始动手了!!

Let't it go ..

场景准备

如上所述,首先要创建场景,画布和开始、暂停按钮

直接上代码:

            let d = document,
                //定时器
                timer = null,
                //禁止连续按键
                disabled = 0,
                //main是画布,open是开始,stop是暂停
                node = {
                    main: d.createElement('div'),
                    open: d.createElement('button'),
                    stop: d.createElement('button'),
                }
            
            //赋予样式
            node.main.style.background = `rgb(104, 163, 210)`;
            node.main.style.width = `${15 * (30+1)}px`;
            node.main.style.height = `${15 * (20+1)}px`;
            node.main.id = 'main';
            node.main.position = `relative`;
            node.open.textContent = `开始`;
            node.stop.textContent = `暂停`;
            //加入document.body
            for( key in node) document.body.appendChild(node[key]);
复制代码

这样就创建好画布和按钮了,完成了基本的场景准备

创建蛇节点

这里我本来打算用函数function Snake(),然后通过构造函数去调用的,但想了想,但是用ES6的class去定义吧!!

直接上代码:

        class Snake {
            constructor() {
                this.init();
            }

            //初始化
            init() {
                //添加蛇身汇总
                this.snakeAll = document.createElement('div');
                this.snakeAll.id = 'all';
                this.snakeAll.style.position = `relative`;
                this.snakeAll.style.top = `${0}px`;
                document.getElementById('main').appendChild(this.snakeAll);

                //蛇节点坐标
                this.Snode = [
                    { x: 3 , y: 1, flag: null},
                    { x: 2 , y: 1, flag: null},
                    { x: 1 , y: 1, flag: null},
                ];

                //默认方向
                this.direction = 'right';
                //蛇节点宽度
                this.width = 15;
                //蛇节点长度
                this.height = 15;
                this.createSnake();

            }
            
        }
复制代码

new Snake()的时候调用构造函数,调用this.init(),对蛇需要用到的参数进行初始化和创建蛇必要的节点

接下来看this.createSnake()代码

             //创建蛇
            createSnake() {
                //首先清除蛇的节点
                this.removeSnake();
                //根据坐标添加节点
                this.Snode.forEach( item => {
                    if(item.x != null) {
                        let div = document.createElement('div');
                        div.style.background = `#734fb3`;
                        div.style.width = `${this.width}px`
                        div.style.height = `${this.width}px`
                        div.style.position = `absolute`;
                        div.style.left = `${item.x * this.width}px`
                        div.style.top = `${item.y * this.height}px`
                        div.style.borderRadius = `${50}%`
                        div.classList.add('snake');
                        item.flag = div;
                        this.snakeAll.appendChild(div);
                    }
                })

                
            }
复制代码

this.createSnake()也是class Snake {}里面的方法,只是我怕代码太长你们看的不耐烦...

this.createSnake()的作用就是根据蛇的默认参数来创建蛇的节点,创建之前需要清除之前存在的蛇的节点

this.removeSnake()代码:

            //删除蛇
            removeSnake() {
                for( let key in this.Snode) {
                    this.Snode[key].flag && this.snakeAll.removeChild(this.Snode[key].flag)
                }
            }
复制代码

好了,创建蛇的节点的逻辑大概就是这样,这里来总结一下:

通过定义Snake的类来编写方法,new Snake()的时候会调用构造函数里面的this.init()来设置默认参数

然后调用this.createSnake()来根据this.Snode[]默认参数来创建蛇的节点

创建之前一定要记住调用this.removeSnake()来清除之前创建的节点,这部分的逻辑大概就是如此。。。

创建食物

创建食物的逻辑比较简单吧,跟创建蛇的逻辑是差不多的

也是定义一个Food的类,然后通过new Food()去调用创建食物

好了,直接上代码

        class Food {
            constructor() {
                this.width = 15;
                this.height = 15;

                this.createFood();
            }

            //创建食物  
            createFood() {
                this.food && this.food.remove();
                this.food = document.createElement('div');
                this.food.id = 'food';
                this.x = Math.floor(Math.random() * 15);
                this.y = Math.floor(Math.random() * 10);
                this.food.style.position = `relative`;
                this.food.style.top = `${this.width * this.y}px`;
                this.food.style.left = `${this.height * this.x}px`;
                this.food.style.width = `${this.width}px`
                this.food.style.height = `${this.height}px`
                this.food.style.background = `#3F51B5`
                this.food.style.borderRadius = `${50}%`
                document.getElementById('all').appendChild(this.food);
            }
        }
复制代码

看了上面创建蛇的逻辑,再看这段创建食物的逻辑应该问题不大吧?哈哈,我相信你看懂了

蛇的移动

接下来开始说蛇的移动了,这段是重点呢,因为蛇的移动包含了撞墙的判断吃到食物的判断吃到自己的判断等功能,是有点麻烦,但是我会分段来说,这样会清晰很多,好了,直接开始...

蛇的移动的原理就是除了蛇头蛇尾的后一个点的坐标等于前一个点的坐标,蛇头的坐标由this.direction这个属性来控制的,然后通过计时器不断执行this.createSnake()就可以实现蛇的移动了

好了,直接上代码

            //运动
            run(food,timer) {
                this.food = food;
                //移动蛇身体
                for(let i=this.Snode.length-1; i>0; i--) {
                    this.Snode[i].x = this.Snode[i-1].x;
                    this.Snode[i].y = this.Snode[i-1].y;
                }
                //移动蛇头
                switch(this.direction){
                    case 'left': this.Snode[0].x -=1;break;
                    case 'right': this.Snode[0].x += 1; break;
                    case 'top': this.Snode[0].y -= 1; break;
                    case 'bottom': this.Snode[0].y += 1;break;
                }
                this.createSnake();
            }
复制代码

这样子就可以实现蛇的移动了,是不是很简单呢

下面开始细分功能,以下的代码都是在run()方法里面的,我把他给分出来这样容易看清楚

判断是否撞墙

判断是否撞墙就是判断蛇的X,Y坐标是否超出画布的最大或最小X、Y坐标

上面的代码说了

画布X的坐标范围是0 - 30

画布Y的坐标范围是0 - 20

        //判断是否撞墙,根据蛇头的X和Y坐标去判断
        if(this.Snode[0].x > 30 || this.Snode[0].x < 0 || this.Snode[0].y > 20 || this.Snode[0].y < 0) {
            alert("哎哟,老兄,看路啊,把我给撞晕了");
            this.snakeAll.remove();
            clearInterval(timer);
            this.init();
            this.food.createFood();
        }
复制代码

撞墙后移动蛇的全部节点,清除定时,重新调用this.init()方法创建,和调用this.food.createFood()方法创建食物

是不是条例很清晰呢?

判断吃到食物

吃到食物的逻辑也是很简单的

当蛇头的坐标XY都等于食物的XY就等于是吃到了食物...

        //吃到食物
        if(this.Snode[0].x == this.food.x && this.Snode[0].y == this.food.y) {
            this.Snode.push({x: null,y: null,flag: null});
            this.food.createFood();
        }
复制代码

this.Snode控制蛇的节点,吃到食物后需要多增加一个节点,直接push()进去就好

然后重新调用方法创建食物就好

判断是否吃到自己

自己吃到自己,这种情况只会在蛇拥有四个节点以上的情况下才会发生

当蛇头的坐标等于自己身体的任意一个坐标的时候,就可以判断为吃到了自己

    //判断是否吃到自己,只有四个点以上才有可能吃到自己
    for(let i=4;i<this.Snode.length;i++) {
        if(this.Snode[0].x == this.Snode[i].x && this.Snode[0].y == this.Snode[i].y) {
            alert("哎哟,老兄,肚子饿也不能吃自己啊");
            this.snakeAll.remove();
            clearInterval(timer);
            this.init();
            this.food.createFood();
        }
    }
复制代码

吃到自己后,也是需要初始化和重新创建食物

好了,看到这里,基本上已经完成了,接下来只需要添加点DOM事件即可

添加事件

添加键盘事件和开始、暂停定时事件

        //添加键盘事件
        document.body.onkeydown = e => {
            let ev = e || window.event;
            if(!disabled) {
                disabled = 1;
                switch(ev.keyCode){
                    case 39:{
                        if(snake.direction != 'left'){
                            snake.direction = 'right';
                            break;
                        }
                    }
                    case 38:{
                        if(snake.direction != 'bottom'){
                            snake.direction = 'top';
                            break;
                        }
                    }
                    case 37:{
                        if(snake.direction != 'right'){
                            snake.direction = 'left';
                            break;
                        }
                    }
                    case 40:{
                        if(snake.direction != 'top'){
                            snake.direction = 'bottom';
                            break;
                        }
                    }
                };  
            } 

            setTimeout( () => {
                disabled = 0;
            },100);
        }

        //开始
        node.open.addEventListener('click', ()=> {
            clearInterval(timer);
            timer = setInterval( () => {
                snake.run(food,timer);
            },100);
        })

        //暂停
        node.stop.addEventListener('click', ()=> {
            clearInterval(timer);
        })
复制代码

好了,代码写到这里基本上就完成了,不知道你们有没有看到最后(伤心脸)!!

完整代码

电脑上重装了,没了git,懒得装,直接贴完整代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <script>

        class Snake {
            constructor() {
                this.init();
            }

            //初始化
            init() {
                //添加蛇身汇总
                this.snakeAll = document.createElement('div');
                this.snakeAll.id = 'all';
                this.snakeAll.style.position = `relative`;
                this.snakeAll.style.top = `${0}px`;
                document.getElementById('main').appendChild(this.snakeAll);

                //蛇节点坐标
                this.Snode = [
                    { x: 3 , y: 1, flag: null},
                    { x: 2 , y: 1, flag: null},
                    { x: 1 , y: 1, flag: null},
                ];

                //默认方向
                this.direction = 'right';
                //蛇节点宽度
                this.width = 15;
                //蛇节点长度
                this.height = 15;
                this.createSnake();

            }

            //创建蛇
            createSnake() {
                //首先清除蛇的节点
                this.removeSnake();
                //根据坐标添加节点
                this.Snode.forEach( item => {
                    if(item.x != null) {
                        let div = document.createElement('div');
                        div.style.background = `#734fb3`;
                        div.style.width = `${this.width}px`
                        div.style.height = `${this.width}px`
                        div.style.position = `absolute`;
                        div.style.left = `${item.x * this.width}px`
                        div.style.top = `${item.y * this.height}px`
                        div.style.borderRadius = `${50}%`
                        div.classList.add('snake');
                        item.flag = div;
                        this.snakeAll.appendChild(div);
                    }
                })

                
            }

            //删除蛇
            removeSnake() {
                for( let key in this.Snode) {
                    this.Snode[key].flag && this.snakeAll.removeChild(this.Snode[key].flag)
                }
            }

            //运动
            run(food,timer) {
                this.food = food;
                //移动蛇身体
                for(let i=this.Snode.length-1; i>0; i--) {
                    this.Snode[i].x = this.Snode[i-1].x;
                    this.Snode[i].y = this.Snode[i-1].y;
                }
                //移动蛇头
                switch(this.direction){
                    case 'left': this.Snode[0].x -=1;break;
                    case 'right': this.Snode[0].x += 1; break;
                    case 'top': this.Snode[0].y -= 1; break;
                    case 'bottom': this.Snode[0].y += 1;break;
                }

                //判断是否撞墙,根据蛇头的X和Y坐标去判断
                if(this.Snode[0].x > 30 || this.Snode[0].x < 0 || this.Snode[0].y > 20 || this.Snode[0].y < 0) {
                    alert("哎哟,老兄,看路啊,把我给撞晕了");
                    this.snakeAll.remove();
                    clearInterval(timer);
                    this.init();
                    this.food.createFood();
                }
                
                //吃到食物
                if(this.Snode[0].x == this.food.x && this.Snode[0].y == this.food.y) {
                    this.Snode.push({x: null,y: null,flag: null});
                    this.food.createFood();
                }

                //判断是否吃到自己,只有四个点以上才有可能吃到自己
                for(let i=4;i<this.Snode.length;i++) {
                    if(this.Snode[0].x == this.Snode[i].x && this.Snode[0].y == this.Snode[i].y) {
                        alert("哎哟,老兄,肚子饿也不能吃自己啊");
                        this.snakeAll.remove();
                        clearInterval(timer);
                    this.init();
                    this.food.createFood();
                    }
                }
                

                this.createSnake();
            }


        }

        class Food {
            constructor() {
                this.width = 15;
                this.height = 15;

                this.createFood();
            }

            //创建食物  
            createFood() {
                this.food && this.food.remove();
                this.food = document.createElement('div');
                this.food.id = 'food';
                this.x = Math.floor(Math.random() * 15);
                this.y = Math.floor(Math.random() * 10);
                this.food.style.position = `relative`;
                this.food.style.top = `${this.width * this.y}px`;
                this.food.style.left = `${this.height * this.x}px`;
                this.food.style.width = `${this.width}px`
                this.food.style.height = `${this.height}px`
                this.food.style.background = `#3F51B5`
                this.food.style.borderRadius = `${50}%`
                document.getElementById('all').appendChild(this.food);
            }
        }

        (()=>{
            let d = document,
                //定时器
                timer = null,
                //禁止连续按键
                disabled = 0,
                //main是画布,open是开始,stop是暂停
                node = {
                    main: d.createElement('div'),
                    open: d.createElement('button'),
                    stop: d.createElement('button'),
                }
            
            //赋予样式
            node.main.style.background = `rgb(104, 163, 210)`;
            node.main.style.width = `${15 * (30+1)}px`;
            node.main.style.height = `${15 * (20+1)}px`;
            node.main.id = 'main';
            node.main.position = `relative`;
            node.open.textContent = `开始`;
            node.stop.textContent = `暂停`;
            //加入document.body
            for( key in node) document.body.appendChild(node[key]);

            let snake = new Snake();
            let food = new Food();

            //添加键盘事件
            document.body.onkeydown = e => {
                let ev = e || window.event;
                if(!disabled) {
                    disabled = 1;
                    switch(ev.keyCode){
                        case 39:{
                            if(snake.direction != 'left'){
                                snake.direction = 'right';
                                break;
                            }
                        }
                        case 38:{
                            if(snake.direction != 'bottom'){
                                snake.direction = 'top';
                                break;
                            }
                        }
                        case 37:{
                            if(snake.direction != 'right'){
                                snake.direction = 'left';
                                break;
                            }
                        }
                        case 40:{
                            if(snake.direction != 'top'){
                                snake.direction = 'bottom';
                                break;
                            }
                        }
                    };  
                } 

                setTimeout( () => {
                    disabled = 0;
                },100);
            }

            //开始
            node.open.addEventListener('click', ()=> {
                clearInterval(timer);
                timer = setInterval( () => {
                    snake.run(food,timer);
                },100);
            })

            //暂停
            node.stop.addEventListener('click', ()=> {
                clearInterval(timer);
            })
        })();
    </script>
</body>
</html>
复制代码

本人是菜鸟,如果觉得我那里写得不好,可以及时指出来,我也很乐意接受高手的指点,哈哈!!

对了 对了 忘了贴效果图

难看是难看了点,但重要的是逻辑思维嘛,哈哈!!

总结

我觉得写下了这个贪食蛇,是对自己的编程实力和思维是一种检测,虽然没有什么实际性的意义(尴尬脸)

不知不觉我也已经工作一年多了,从毕业的跨行成功到现在生活的无奈,有时候我也不知道坚持是为了什么,我也似乎是忘记了自己的初心了,没错,我就是那种很丧很丧的那种人,哈哈!!

最后送大家一句话:人生,无非就是起起落落落落落落,所以要好好活着,哈哈,生活,加油!!