当前位置: 首页 > news >正文

项目二:《贪吃蛇》

思路:

  1. 蛇分为两个部分,蛇头和蛇身体的形成
  2. 蛇的运动,通过键盘控制蛇的走向
  3. 游戏结束的条件,撞墙和吃到自己
  4. 食物的产生,注意食物产生的位置和蛇的位置
  5. 蛇吃到食物的过程,吃到后身体变长并产生新的食物

一、效果图

二、贪吃蛇功能分析

1、游戏区域

        游戏画布由 30 行 30 列且宽高皆为 20 px 的格子组成,蛇和食物功能的实现实际上都是在这些格子中进行的。

    var cw = 20, // 一个格子的宽
        ch = 20, // 格子的高
        tr = 30,
        td = 30;
    // 实例化
    var snake = null,
        food = null,
        game = null;

    // 格子的构造函数
    function Cell(x, y, classname) {
        this.x = x * cw;
        this.y = y * ch;
        this.class = classname;
        // 创建并添加标签
        this.viewContent = document.createElement('div');
        this.viewContent.className = this.class;
        this.parent = document.querySelector('.snakeBox');
    }
    // prototype 属性:为一个特定类声明通用的变量或者函数
    Cell.prototype.create = function() { // 创建并添加格子 dom
        this.viewContent.style.position = 'absolute';
        this.viewContent.style.width = cw + 'px';
        this.viewContent.style.height = ch + 'px';
        this.viewContent.style.left = this.x + 'px';
        this.viewContent.style.top = this.y + 'px';
        this.parent.appendChild(this.viewContent); // 添加到页面
    }
    Cell.prototype.remove = function() { // 删除格子
        this.parent.removeChild(this.viewContent);
    }

2、开始/暂停游戏功能
        用户点击页面中的 start 按钮后即可进行新游戏,点击页面即可暂停游戏,再点击出现的暂停按钮即可继续游戏。代码如下:

    // 调用中介类 开始游戏
    game = new Game();
    var startBtn = document.querySelector('.startBtn button');
    startBtn.addEventListener('click', function() {
        startBtn.parentNode.style.display = 'none';
        game.init(); // 初始化游戏
    });
    // 暂停游戏
    var snakeBox = document.querySelector('.snakeBox');
    var pauseBtn = document.querySelector('.pauseBtn button');
    snakeBox.addEventListener('click', function() {
        game.pause();
        pauseBtn.parentNode.style.display = 'block';
    });
    pauseBtn.addEventListener('click', function() {
        game.start();
        pauseBtn.parentNode.style.display = 'none';
    });
    // 游戏开始
    Game.prototype.start = function() {
        this.timer = setInterval(function() {
            snake.getNextPos();
        }, 200);
    }
    // 游戏暂停
    Game.prototype.pause = function() {
        clearInterval(this.timer);
    }

3、贪吃蛇
1.蛇的组成
        蛇分为蛇头和蛇身两个部分,通过数组来存储蛇的位置,点击 start 按钮后,蛇的方向默认往右移动。

    // 蛇的构造函数
    function Snake() {
        this.head = null; //蛇头
        this.tail = null; // 蛇尾
        this.pos = []; // 存储蛇身上的每一个格子的位置
        this.directionNum = { // 存储用对象来表示的蛇头朝向
            left: {
                x: -1,
                y: 0,
                rotate: 180 // 通过旋转角度调整蛇头方向
            },
            right: {
                x: 1,
                y: 0,
                rotate: 0
            },
            up: {
                x: 0,
                y: -1,
                rotate: -90
            },
            down: {
                x: 0,
                y: 1,
                rotate: 90
            }
        }
    }
    // 初始化蛇
    Snake.prototype.init = function() {
        // 创建蛇头
        var snakeHead = new Cell(2, 0, 'snakeHead');
        snakeHead.create();
        this.head = snakeHead; // 存储蛇头信息
        this.pos.push([2, 0]) // 存储蛇头位置
        // 创建蛇身体
        var snakeBody1 = new Cell(1, 0, 'snakeBody');
        snakeBody1.create();
        this.pos.push([1, 0]) // 存储蛇身1位置
        var snakeBody2 = new Cell(0, 0, 'snakeBody');
        snakeBody2.create();
        this.tail = snakeBody2; // 存储蛇尾信息
        this.pos.push([0, 0]) // 存储蛇身1位置
        // 形成链表关系
        snakeHead.last = null;
        snakeBody1.last = snakeHead;
        snakeBody2.last = snakeBody1;
        snakeHead.next = snakeBody1;
        snakeBody1.next = snakeBody2;
        snakeBody2.next = null;
        // 给蛇添加一个 direction 属性,用来表示蛇走的方向
        this.direction = this.directionNum.right; // 默认往右走
    }

2.蛇的运动功能
        通过键盘上的 W、A、S、D 和上下左右方位键来控制蛇的移动方向。

    // 控制蛇的运动方向
    Game.prototype.init = function() {
        // 初始化节点
        snake.init();
        createFood();
        document.addEventListener('keydown', function(ev) {
            if (ev.keyCode == 37 && snake.direction != snake.directionNum.right) {
                snake.direction = snake.directionNum.left;
            } else if (ev.keyCode == 38 && snake.direction != snake.directionNum.down) {
                snake.direction = snake.directionNum.up;
            } else if (ev.keyCode == 39 && snake.direction != snake.directionNum.left) {
                snake.direction = snake.directionNum.right;
            } else if (ev.keyCode == 40 && snake.direction != snake.directionNum.up) {
                snake.direction = snake.directionNum.down;
            }

            if (ev.keyCode == 65 && snake.direction != snake.directionNum.right) {
                snake.direction = snake.directionNum.left;
            } else if (ev.keyCode == 87 && snake.direction != snake.directionNum.down) {
                snake.direction = snake.directionNum.up;
            } else if (ev.keyCode == 68 && snake.direction != snake.directionNum.left) {
                snake.direction = snake.directionNum.right;
            } else if (ev.keyCode == 83 && snake.direction != snake.directionNum.up) {
                snake.direction = snake.directionNum.down;
            }
        });
        this.start();
    }

4、食物
1.食物的产生

        通过数组存储随机生成的食物的坐标并判断是否与蛇的身体重合。

    // 创建食物
    function createFood() {
        // 食物的随机坐标
        var x = null;
        var y = null;
        var include = true; // 循环跳出的条件,true 表示食物坐标在蛇身上,false:表示不在
        while (include) {
            x = Math.round(Math.random() * (td - 1));
            y = Math.round(Math.random() * (tr - 1));
            snake.pos.forEach(function(value) {
                if (x != value[0] && y != value[1]) {
                    include = false; // 坐标不在蛇身上
                }
            })
        }
        // 生成食物
        food = new Cell(x, y, 'food');
        food.pos = [x, y]; // 存储食物的坐标,用于判断是否与蛇头相撞
        var foodDom = document.querySelector('.food');
        if (foodDom) {
            foodDom.style.left = x * cw + 'px';
            foodDom.style.top = y * ch + 'px';
        } else {
            food.create();
        }
    }

2.吃食物的过程
        当页面任意位置出现食物,用户控制蛇移动到食物周围,蛇头碰到食物时则吃掉此食物,页面上会在任意位置出现另一个食物,蛇再去吃掉。

    Snake.prototype.getNextPos = function() {
        // 蛇头的下一个点的坐标
        var nextPos = [
            this.head.x / cw + this.direction.x,
            this.head.y / ch + this.direction.y
        ]
        // 吃掉食物
        if (food && food.pos[0] == nextPos[0] && food.pos[1] == nextPos[1]) {
            // 条件成立则蛇头的下一个点是食物
            this.strategies.eat.call(this);
            return;
        }
    };
    Snake.prototype.strategies = {
        eat: function() {
            this.strategies.move.call(this, true);
            createFood();
            game.score++;
        },
    }

5、游戏结束

        吃到自己的身体或者撞到墙则游戏结束。

    // 获取蛇头的下一个位置对应的元素,根据元素做不同的事情
    Snake.prototype.getNextPos = function() {
        // 蛇头的下一个点的坐标
        var nextPos = [
            this.head.x / cw + this.direction.x,
            this.head.y / ch + this.direction.y
        ]

        // 吃到自己,游戏结束
        var selfCollied = false; // 判断是否撞到
        this.pos.forEach(function(value) {
            if (value[0] == nextPos[0] && value[1] == nextPos[1]) {
                selfCollied = true;
            }
        });
        if (selfCollied) {
            // 使用 call 方法,能调用父类的实例
            this.strategies.die.call(this); // this 指向实例
            return;
        }

        // 撞到墙,游戏结束
        if (nextPos[0] < 0 || nextPos[1] < 0 || nextPos[0] > td - 1 || nextPos[1] > tr - 1) {
            this.strategies.die.call(this);
            return;
        }

        // 吃掉食物
        if (food && food.pos[0] == nextPos[0] && food.pos[1] == nextPos[1]) {
            // 条件成立则蛇头的下一个点是食物
            this.strategies.eat.call(this);
            return;
        }

        // 不是上面的三种情况,继续走
        this.strategies.move.call(this);
    };

    // 处理碰撞后要做的事
    Snake.prototype.strategies = {
        move: function(format) { // 该参数用于决定是否删除蛇尾
            // 在旧蛇头的位置 创建新身体
            var newBody = new Cell(this.head.x / cw,
                this.head.y / ch, 'snakeBody');
            // 更新链表关系
            newBody.next = this.head.next;
            newBody.next.last = newBody;
            newBody.last = null;
            // 把旧蛇头从原来的位置删除
            this.head.remove();
            newBody.create();

            // 创建新的蛇头即蛇头下一个移动的点
            var newHead = new Cell(this.head.x / cw + this.direction.x,
                this.head.y / ch + this.direction.y, 'snakeHead');
            // 更新链表关系
            newHead.next = newBody;
            newHead.last = null;
            newBody.last = newHead;
            // 更新蛇头方向
            newHead.viewContent.style.transform = 'rotate(' + this.direction.rotate + 'deg)';
            newHead.create();

            // 更新蛇身上每一个格子的坐标
            this.pos.splice(0, 0, [this.head.x / cw + this.direction.x,
                this.head.y / ch + this.direction.y
            ]);
            // 更新 this.head 即蛇头的位置
            this.head = newHead;

            if (!format) { // false: 需要删除蛇尾(处理吃之外的操作)
                this.tail.remove();
                this.tail = this.tail.last;
                this.pos.pop();
            }
        },

        eat: function() {
            this.strategies.move.call(this, true);
            createFood();
            game.score++;
        },

        die: function() {
            game.over();
        }
    }
    // 游戏结束
    Game.prototype.over = function() {
        clearInterval(this.timer);
        alert('游戏结束,你本次的得分为:' + this.score);
        // 游戏回到最初始的状态
        var snakeBox = document.querySelector('.snakeBox');
        snakeBox.innerHTML = '';
        snake = new Snake();
        game = new Game();
        var startBtnWrap = document.querySelector('.startBtn');
        startBtnWrap.style.display = 'block';
    }

小结

        我认为写贪吃蛇最重要的就是要在写之前必需先理清自己的思路明确代码的逻辑结构

视频学习链接:web前端实战项目系列,JavaScript面相对象开发贪吃蛇(全套教程)

相关文章:

  • 企业运维容器之 docker仓库
  • 快速排序sort 第k个数
  • uniapp开发微信小程序Error in onLoad hook: “SyntaxError: Unexpected end of JSON input“
  • MySQL当前链接状态查询
  • 打破平台限制,小程序如何在硬件设备上运行?
  • ORA-01017(:用户名/口令无效; 登录被拒绝)Oracle新建用户并授权
  • PostgreSQL的学习心得和知识总结(九十九)|语法级自上而下完美实现达梦数据库的 TOP语法功能 的实现方案
  • Mybatis-Plus批量插入应该怎么用
  • (Note)C++中的继承方式
  • qemu gutest network configuration
  • 【25】 冒险和预测(四):今天下雨了,明天还会下雨么?
  • CREO:CREO软件之工程图【插入页面】、【装配图出工程图】、【将视图转为绘制图元】、【工程图输入到CAD中去修改】的简介及其使用方法(图文教程)之详细攻略
  • 强大且超实用的论文阅读工具——ReadPaper
  • 完整解析快速排序
  • 评估与监控CI/CD流水线
  • 【RocksDB】TransactionDB源码分析
  • CentOS学习笔记 - 12. Nginx搭建Centos7.5远程repo
  • classpath对获取配置文件的影响
  • eclipse的离线汉化
  • Java 23种设计模式 之单例模式 7种实现方式
  • JS学习笔记——闭包
  • leetcode-27. Remove Element
  • MySQL QA
  • node-sass 安装卡在 node scripts/install.js 解决办法
  • 从零开始学习部署
  • 从伪并行的 Python 多线程说起
  • 它承受着该等级不该有的简单, leetcode 564 寻找最近的回文数
  • 看到一个关于网页设计的文章分享过来!大家看看!
  • Python 之网络式编程
  • ​总结MySQL 的一些知识点:MySQL 选择数据库​
  • #100天计划# 2013年9月29日
  • $(function(){})与(function($){....})(jQuery)的区别
  • (C语言)逆序输出字符串
  • (二十三)Flask之高频面试点
  • (四)模仿学习-完成后台管理页面查询
  • (转)使用VMware vSphere标准交换机设置网络连接
  • .gitignore文件设置了忽略但不生效
  • .net 无限分类
  • .NET开源项目介绍及资源推荐:数据持久层
  • .NET委托:一个关于C#的睡前故事
  • .Net下的签名与混淆
  • .NET中的十进制浮点类型,徐汇区网站设计
  • .vollhavhelp-V-XXXXXXXX勒索病毒的最新威胁:如何恢复您的数据?
  • @PreAuthorize注解
  • [ C++ ] STL---仿函数与priority_queue
  • [ CTF ] WriteUp- 2022年第三届“网鼎杯”网络安全大赛(朱雀组)
  • [ 蓝桥杯Web真题 ]-布局切换
  • [04]Web前端进阶—JS伪数组
  • [2021ICPC济南 L] Strange Series (Bell 数 多项式exp)
  • [BUAA软工]第一次博客作业---阅读《构建之法》
  • [BZOJ 3680]吊打XXX(模拟退火)
  • [C]整形提升(转载)
  • [CSS] 点击事件触发的动画
  • [C语言][C++][时间复杂度详解分析]二分查找——杨氏矩阵查找数字详解!!!
  • [GN] 后端接口已经写好 初次布局前端需要的操作(例)