纯JS实现贪吃蛇游戏 —— 可能是全网代码最优雅的实现

简介: 在网上看了许多的贪吃蛇这类游戏,效果基本还可以,功能也实现了,不过看代码大都是冗余或杂乱不堪的,难以维护。所以花了点时间,对整个游戏重构了一下,也算是站在各位前辈的肩膀上做的优化,希望对大家有帮助。

说在前面


在网上看了许多的贪吃蛇这类游戏,效果基本还可以,功能也实现了,不过看代码大都是冗余或杂乱不堪的,难以维护。

所以花了点时间,对整个游戏重构了一下,也算是站在各位前辈的肩膀上做的优化,希望对大家有帮助。

效果图.gif

1.png

功能描述



生成一条蛇,可以上下左右移动,目标只有一个:吃食物。吃到一个食物蛇的身体增加一节,然后生成下一个食物,撞到地图就GG,game over。


设计思路



1. 整体实现采用原生JS,使用ES6的Class类构造,完美的诠释了面向对象编程的编程思想。


2. js 主体文件分成 food.js(食物类),snake.js(蛇类), game.js(游戏入口文件),util.js(工具函数)。讲道理,地图也需要用到一个map.js(地图类),由于这里的地图过分简单,所以不搞也罢。


3. 设计思路图解:2.png


1. 工具类设计 ( util.js )


目标功能点:

  • 生成随机坐标


2.png

export function getRandom(a, b){
    let max = Math.max(a, b);
    let min = Math.min(a, b);
    return parseInt(Math.random() * (max - min)) + min;
}


2. 食物类设计(food.js)


目标功能点:

  • 初始化食物(宽,高,颜色等)
  • 在地图上随机生成
  • 管理食物(删除)
import { getRandom } from './util.js';
// 食物类
class Food {
    // 初始化
    constructor({x = 0, y = 0, width = 20, height = 20, color = 'green'} = {}){
        // 结构赋值 参数默认值
        // let options = {x = 0, y = 0, width = 20, height = 20, color = 'green'} || {};
        // 存储食物
        this.elements = [];
        // 坐标
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
        this.color = color;
    }
    render(map){
        this.remove(); // 删除之前创建的食物
        // 随机设置x,y的值
        this.x = getRandom(0, map.offsetWidth / this.width - 10) * this.width;
        this.y = getRandom(0, map.offsetHeight / this.height - 1) * this.height;
        console.log(this.x, this.y);
        // 创建食物 dom
        let div = document.createElement('div');
        map.appendChild(div);
        this.elements.push(div);
        // 设置div的样式
        div.style.position = 'absolute';
        div.style.left = this.x + 'px';
        div.style.top = this.y + 'px';
        div.style.width = this.width + 'px';
        div.style.height = this.height + 'px';
        div.style.backgroundColor = this.color;
    }
    remove() {
        // 从后往前
        for(let i = this.elements.length -1; i >= 0; i--){
            this.elements[i].parentNode.removeChild(this.elements[i]); // 删除div
            this.elements.splice(i, 1); // 删除数组中的元素
        }
    }
}
export default Food;


3. 蛇类(snake.js)


目标功能点:

  • 初始化蛇(宽,高,颜色、长度等)
  • 在地图上初始定位
  • 蛇的移动与管理(吃一个食物生成一个新的蛇对象)
  • 判断是否吃到食物(蛇头的坐标与食物坐标重合)
// 蛇类
class Snake {
    constructor({ width = 20, height = 20, direction = 'right'  } = {}){
          // 存储蛇
          this.elements = [];
          this.width = width;
          this.height = height;
          this.direction = direction;
          // 蛇的身体 初始三节
          this.body = [
              {x: 3, y: 2, color: 'red'},
              {x: 2, y: 2, color: 'blue'},
              {x: 1, y: 2, color: 'blue'},
          ];
    }
    render(map){
        this.remove(); // 删除之前创建的蛇
        for(let i = 0, len = this.body.length; i <  len; i++ ){
            let object = this.body[i];
            let div = document.createElement('div');
            map.appendChild(div);
            this.elements.push(div);
             // 设置样式
             div.style.position = 'absolute';
             div.style.width = this.width + 'px';
             div.style.height = this.height + 'px';
             div.style.left = object.x * this.width + 'px';
             div.style.top = object.y * this.height + 'px';
             div.style.backgroundColor = object.color;
        }
    }
    move(food, map){
        // 控制蛇的移动 (当前蛇节 移动到上一个蛇节)
        for(let i = this.body.length - 1; i > 0; i--){
            this.body[i].x = this.body[i - 1].x; 
            this.body[i].y = this.body[i - 1].y; 
        }
        // 蛇头
        let head = this.body[0];
        // 蛇头的行进方向
        switch(this.direction) {
            case 'right':
                head.x += 1;
                break;
            case 'left':
                head.x -= 1;
                break;
            case 'top':
                head.y -= 1;
                break;
            case 'bottom':
                head.y += 1;
                break;
        }
        // 蛇吃食物
        // 判断蛇头的位置是否与食物的位置重合
        let  headX = head.x * this.width;
        let  headY = head.y * this.height;
        if(headX === food.x && headY === food.y){
            let last = this.body[this.body.length -1 ];
            this.body.push({
                x: last.x,
                y: last.y,
                color: last.color
            });
            // 重新生成一个食物
            food.render(map);
        }
    }
    remove() {
        for (let i = this.elements.length - 1; i >= 0; i--) {
            // 删除div
            this.elements[i].parentNode.removeChild(this.elements[i]);
            // 删除数组中的元素
            this.elements.splice(i, 1);
        }
    }
}
export default Snake;


4. 游戏入口文件(game.js)


目标功能点:

  • 实例化蛇与食物
  • 让蛇动起来
  • 绑定按键,控制方向
  • 开始游戏
  • 当蛇撞到地图边缘GG,显示 game over!
import Food from "./food.js";
import Snake from "./snake.js";
// 游戏的入口文件
class Game {
    constructor() {
        // 创建食物和蛇的实例
        this.food = new Food();
        this.snake = new Snake();
        this.map = map;
        // 定时器
        this.timerId = null;
    }
    start() {
        // 食物和蛇 渲染到地图上
        this.food.render(this.map);
        this.snake.render(this.map);
        this.runSnake(); 
        this.bindKey();
    }
    // 让蛇动起来
    runSnake() {
         this.timerId = setInterval( () => {
            // 要获取游戏对象中的蛇属性
            this.snake.move(this.food, this.map);
            // 2.2  当蛇遇到边界游戏结束
            var maxX = this.map.offsetWidth / this.snake.width;
            var maxY = this.map.offsetHeight / this.snake.height;
            var headX = this.snake.body[0].x;
            var headY = this.snake.body[0].y;
            if (headX < 0 || headX >= maxX || headY < 0|| headY >= maxY) {
                console.log('Game Over');
                clearInterval(this.timerId);
                return
            }
            this.snake.render(this.map);  // 根据body 的数据 重新渲染蛇在页面位置
        }, 150);
    }
    // 绑定键盘事件 控制蛇的方向
    bindKey() {
        document.addEventListener('keydown',  (e) => {
            switch (e.keyCode) {
                case 37:
                    this.snake.direction = 'left';
                    break;
                case 38:
                    this.snake.direction = 'top';
                    break;
                case 39:
                    this.snake.direction = 'right';
                    break;
                case 40:
                    this.snake.direction = 'bottom';
                    break;
            }
        });
    }
}
export default Game;

5. 调用(index.html)


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="map" style="width:80%;height:400px;border: 1px solid orange;"></div>
    <script type="module">
        import Game from './game.js';
        // 全局的地图 map
        let map = document.getElementById('map');
        let game = new Game(map);
        // 调用开始方法
        game.start();
    </script>
</body>
</html>

FAQ:

由于整个项目采用ES6的模块设计,所以需要启动一个本地服务才可以跑,单独点开index.html,是没得用的。

目录
相关文章
|
3天前
|
存储 JavaScript 前端开发
在NodeJS中使用npm包进行JS代码的混淆加密
总的来说,使用“javascript-obfuscator”包可以帮助我们在Node.js中轻松地混淆JavaScript代码。通过合理的配置,我们可以使混淆后的代码更难以理解,从而提高代码的保密性。
38 9
|
24天前
|
前端开发 JavaScript
【Javascript系列】Terser除了压缩代码之外,还有优化代码的功能
Terser 是一款广泛应用于前端开发的 JavaScript 解析器和压缩工具,常被视为 Uglify-es 的替代品。它不仅能高效压缩代码体积,还能优化代码逻辑,提升可靠性。例如,在调试中发现,Terser 压缩后的代码对删除功能确认框逻辑进行了优化。常用参数包括 `compress`(启用压缩)、`mangle`(变量名混淆)和 `output`(输出配置)。更多高级用法可参考官方文档。
106 11
|
1月前
|
JavaScript 前端开发 算法
JavaScript 中通过Array.sort() 实现多字段排序、排序稳定性、随机排序洗牌算法、优化排序性能,JS中排序算法的使用详解(附实际应用代码)
Array.sort() 是一个功能强大的方法,通过自定义的比较函数,可以处理各种复杂的排序逻辑。无论是简单的数字排序,还是多字段、嵌套对象、分组排序等高级应用,Array.sort() 都能胜任。同时,通过性能优化技巧(如映射排序)和结合其他数组方法(如 reduce),Array.sort() 可以用来实现高效的数据处理逻辑。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
1月前
|
JavaScript 前端开发 API
JavaScript中通过array.map()实现数据转换、创建派生数组、异步数据流处理、复杂API请求、DOM操作、搜索和过滤等,array.map()的使用详解(附实际应用代码)
array.map()可以用来数据转换、创建派生数组、应用函数、链式调用、异步数据流处理、复杂API请求梳理、提供DOM操作、用来搜索和过滤等,比for好用太多了,主要是写法简单,并且非常直观,并且能提升代码的可读性,也就提升了Long Term代码的可维护性。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
2月前
|
人工智能 数据可视化 机器人
【通义灵码】三句话生成P5.js粒子特效代码,人人都可以做交互式数字艺术
我发掘出的通义灵码AI程序员新玩法:三句话生成P5.js粒子特效代码,人人都可以做交互式数字艺术
134 6
|
3月前
|
人工智能 程序员 UED
【01】完成新年倒计时页面-蛇年新年快乐倒计时领取礼物放烟花html代码优雅草科技央千澈写采用html5+div+CSS+JavaScript-优雅草卓伊凡-做一条关于新年的代码分享给你们-为了C站的分拼一下子
【01】完成新年倒计时页面-蛇年新年快乐倒计时领取礼物放烟花html代码优雅草科技央千澈写采用html5+div+CSS+JavaScript-优雅草卓伊凡-做一条关于新年的代码分享给你们-为了C站的分拼一下子
174 21
【01】完成新年倒计时页面-蛇年新年快乐倒计时领取礼物放烟花html代码优雅草科技央千澈写采用html5+div+CSS+JavaScript-优雅草卓伊凡-做一条关于新年的代码分享给你们-为了C站的分拼一下子
|
2月前
|
人工智能 数据可视化 架构师
三句话生成 P5.js 粒子特效代码,人人都可以做交互式数字艺术
短短几分钟,两个完全不懂P5.js的人类,和通义灵码AI程序员一起,共同完成了有真实物理引擎和碰撞检测的3D仿真动画。人类扮演的角色更像产品经理和架构师,提出开发需求和迭代修改方案,而AI的作用更像码农,任劳任怨,熟练用各种编程语言完成技术底层的脏活累活。这只是AI编程的冰山一角,未来,每一个艺术家都能快速做出自己的创意原型,每一个数学老师都能轻松做出自己的教学动画。
|
3月前
|
前端开发 JavaScript
【02】v1.0.1更新增加倒计时完成后的放烟花页面-优化播放器-优化结构目录-蛇年新年快乐倒计时领取礼物放烟花html代码优雅草科技央千澈写采用html5+div+CSS+JavaScript-优雅草卓伊凡-做一条关于新年的代码分享给你们-为了C站的分拼一下子
【02】v1.0.1更新增加倒计时完成后的放烟花页面-优化播放器-优化结构目录-蛇年新年快乐倒计时领取礼物放烟花html代码优雅草科技央千澈写采用html5+div+CSS+JavaScript-优雅草卓伊凡-做一条关于新年的代码分享给你们-为了C站的分拼一下子
84 14
【02】v1.0.1更新增加倒计时完成后的放烟花页面-优化播放器-优化结构目录-蛇年新年快乐倒计时领取礼物放烟花html代码优雅草科技央千澈写采用html5+div+CSS+JavaScript-优雅草卓伊凡-做一条关于新年的代码分享给你们-为了C站的分拼一下子
|
2月前
|
自然语言处理 前端开发 JavaScript
20 个 JavaScript 简化技巧,让你的代码更上一层楼!
JavaScript 既灵活又强大,掌握以下20个技巧可助你编写更简洁高效的代码
|
1月前
|
移动开发 运维 供应链
通过array.some()实现权限检查、表单验证、库存管理、内容审查和数据处理;js数组元素检查的方法,some()的使用详解,array.some与array.every的区别(附实际应用代码)
array.some()可以用来权限检查、表单验证、库存管理、内容审查和数据处理等数据校验工作,核心在于利用其短路机制,速度更快,节约性能。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~