JS高级特性简单实现—贪吃蛇

搭建页面

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <link rel="stylesheet" href="css/index.css">
</head>
<body>
  <div class="map" id="map"></div>
  <!-- 引入多个js文件 -->
  <script src="js/tools.js"></script>
  <script src="js/food.js"></script>
  <script src="js/snake.js"></script>
  <script src="js/game.js"></script>
  <script src="js/main.js"></script>
</body>
</html>

index.css

* {
margin:0;
padding:0;
}
.map {
   position:relative;
   width:800px;
   height:600px;
   background-color:lightgray;
}

分析对象

  • 游戏对象
  • 蛇对象
  • 食物对象

食物对象

1.创建构造函数Food,设置属性

  • x
  • y
  • width
  • height
  • color
  1. 通过原型设置方法
    • render 随机创建一个食物对象,并且输出到map上

3.通过自调用函数,进行封装,通过window暴露Food对象

// 缩小定义的构造函数的作用域
// 匿名函数,自调用函数.IIFE,关住作用域
(function(){
    var ps = "absolute";
    // 创建食物的构造函数
function Food(option){
    // 避免传入的参数的数据类型不对,或者没有传参
    option = option instanceof Object ? option : {};
    // 传入的数据可能是类似于数组等对象,需要进一步进行判断
    this.width = option.width || 20;
    this.height = option.height || 20;
    this.x = option.x || 20;
    this.y = option.y || 20;
    this.color = option.color || "green";
    // 增加一个属性,存储将来这个对象渲染出来的所有的div元素
    this.elements = [];
}
// 渲染一个元素到页面之上,需要添加到原型对象的方法中
// 全局变量设置不变的属性,更加优化
Food.prototype.render = function(map){
    // 创建一个新的div元素
    var ele = document.createElement("div");
    // 每次设置样式之前,随机获取一个x和y
    this.x = Tools.getRandom(0,map.clientWidth / this.width - 1) * this.width;
    this.y = Tools.getRandom(0,map.clientHeight / this.height - 1) * this.height;
    // 给生成的元素添加对应的样式
    ele.style.width = this.width + "px";
    ele.style.height = this.height + "px";
    ele.style.left = this.x + "px";
    ele.style.top = this.y + "px";
    ele.style.background = this.color;
    ele.style.position = ps;
    // 让新元素添加到指定的父级中
    map.appendChild(ele);
    // 将新元素添加到数组中,方便后期调用删除
    this.elements.push(ele);
};
// 删除一个食物div元素的方法
Food.prototype.remove = function(map,i){
    // 通过一些方法,获取删除食物的下标
    //将元素从html结构中删除
    map.removeChild(this.elements[i]);
    // 将元素从数组中删除
    this.elements.splice(i,1);
};
// 利用windows对象,暴露Food函数,可以给外部使用
window.Food = Food;
})();

// 测试
// 获取父级地图元素
var map = document.getElementById("map");   
var food = new Food();
food.render(map);
// 在外面调用到这个Food构造函数

蛇对象

  • 创建Snake构造函数,设置属性
    1.width:蛇节宽度,默认20
    2.height:蛇节高度,默认20
    3.body:数组,蛇的头部和身体,第一个位置是蛇头
    4.direction:蛇运动的方向,默认right,可以是left,bottom,top
  • 通过原型设置方法
    • render:随机创建一个蛇对象,输出到map
  • 通过自调用函数,封装,通过window暴露Snake
// 使用自调用函数关住作用域
(function(){
    // 全局变量
    ps = "absolute";
    // 创建蛇的构造函数
    function Snake(option){
        // 避免传入的参数的数据类型不对,或者没有传参
        option = option instanceof Object ? option : {};
        // 给对象添加属性
        //蛇节的宽高
        this.width = option.width || 20;
        this.height = option.height || 20;
        // 设置蛇身数据
        this.body = [
            {x : 3,y: 2,color:"red"},
            {x : 2,y: 2,color:"blue"},
            {x : 1,y: 2,color:"blue"},
        ];
        // 设置蛇的移动方向
        this.direction = "right";
        // 添加一个元素的数组,存储所有的渲染的元素
        this.elements = [];
    }
    // 将元素渲染到页面上的方法
    Snake.prototype.render = function(map){
        // 生成对应个数的div元素
        // 遍历body数组
        for(var i =0,length = this.body.length; i < length;i++){
            // 根据数组的每一项的数据生成一个新的div元素
            var piece = this.body[i];
            // 创建元素
            var ele = document.createElement("div");
            // 添加样式
            ele.style.width = this.width + "px";
            ele.style.height = this.height + "px";
            ele.style.left = piece.x * this.width + "px";
            ele.style.top = piece.y * this.height + "px";
            ele.style.position = ps;
            ele.style.background = piece.color;
            // 渲染到指定的父级内部
            map.appendChild(ele);
            // 添加的新元素存到数组里
            this.elements.push(ele);
        }
    };
    // 添加蛇的运动方法
    Snake.prototype.move = function(){
        // 1.蛇身的每一节都要变成上一节的位置
        // 循环要从最后一项开始,防止前面的数据放生变化
        for(var 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;
        }
        // 存储蛇头的数据
        var head = this.body[0];
        // 2.蛇头要根据方向发生位置变化
        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;
        }
    };
    // 删除上一次渲染的蛇的所有div元素
    Snake.prototype.remove = function (map) {
        // 遍历删除所有的元素
        for(var i = this.elements.length-1; i >=0 ;i--){
            map.removeChild(this.elements[i]);
        }
        // 数组也要清空
        this.elements = [];
    };
    window.Snake = Snake;
})();

// 测试
// var map = document.getElementById("map");
// var snake = new Snake();
// snake.render(map);

游戏对象

  • 创建 Game 的构造函数,并设置属性
    1.food
    2.snake
    3.map
  • 通过原型设置方法
    • start 开始游戏(绘制所有游戏对象,渲染食物对象和蛇对象)
  • 通过自调用函数,进行封装,通过 window 暴露 Game 对象
// 自调用函数封闭作用域
(function(){
    // 定义一个全局变量,存储this
    var that;
    function Game(map){
        // 设置三个属性,蛇,食物,地图
        this.food = new Food();
        this.snake = new Snake();
        this.map = map;
        that = this;
    }
    // 添加一个游戏开始方法,方法内初始化蛇和食物
    Game.prototype.start = function(){
        // 添加蛇和食物到地图上
        this.food.render(this.map);
        this.food.render(this.map);
        this.food.render(this.map);
        this.snake.render(this.map);
        // 让游戏逻辑开始
        // 让蛇动起来
        runSnake();
        // 上下左右箭头控制蛇的运动方向
        bindKey();
    }
    // 封装一个私有函数,这个函数只能在模块内部进行调用
    function runSnake(){
        // 开启一个定时器,让蛇连续运动
        var timer = setInterval(function(){
            // 定时器函数内部的this指向的是window,不能直接使用this。
            that.snake.move();
            // 删掉上一次的蛇
            that.snake.remove(that.map);
            // 渲染新的蛇
            that.snake.render(that.map);
            // 记录一下最大的位置
            var maxX = that.map.offsetWidth / that.snake.width;
            var maxY = that.map.offsetHeight / that.snake.height;
            // 找到当前蛇头的位置
            var headX = that.snake.body[0].x;
            var headY = that.snake.body[0].y;
            // 每一次这个蛇走到新的位置,都要判断一下是否吃到食物了,让自己增加一节
            // 记录一下食物的坐标
            // var foodX = that.food.x;
            // var foodY = that.food.y;
            // 获取一下蛇头的具体坐标值(px)
            var hX = headX * that.snake.width;
            var hY = headY * that.snake.height;
            // 判断蛇头和食物是否重叠
            // 将食物数组的每一个都要进行对比,谁被吃掉,删除自己,渲染一个新的元素
            for(var i = 0;i<that.food.elements.length;i++){
                if(that.food.elements[i].offsetLeft === hX && that.food.elements[i].offsetTop === hY){
                    // 吃到食物
                    // 让食物删除,渲染一个新的食物
                    that.food.remove(that.map,i);
                    that.food.render(that.map);
                    // 添加一个新的蛇节
                    var last = that.snake.body[that.snake.body.length - 1];
                    that.snake.body.push({
                        x: last.x,
                        y: last.y,
                        color: last.color
                    });
                }
            }
            // 每次移动都要判断是否出了地图,游戏是否结束
            // 进行判断
            if(headX < 0 || headX >= maxX || headY < 0 || headY >= maxY){
                // 停止定时器,弹出提醒,游戏结束
                clearInterval(timer);
                alert("Game Over");
            }
        },150)
    };
    // 封装一个私有函数控制上下左右按键更改的方向
    function bindKey() {
        // 给整个文档绑定键盘按下事件
        document.onkeydown = function(e){
            // console.log(e.keyCode);
            // 键盘的编码
            // 37是left,38是top,39是right,40是bottom
            switch(e.keyCode){
                case 37:
                    that.snake.direction = "left";
                    break;
                case 38:
                    that.snake.direction = "top";
                    break;
                case 39:
                    that.snake.direction = "right";
                    break;
                case 40:
                    that.snake.direction = "bottom";
                    break;
            }
        };
    }
    // 将构造函数通过window暴露
    window.Game = Game;
})();

// // 测试
// var map = document.getElementById("map");
// var game = new Game(map);
// game.start();

main主执行代码

项目结构

main主执行代码,该文件引用必须要放在所有js文件引用之后

// 使用自调用函数,关住作用域
(function(){
    var map = document.getElementById("map");
    var game = new Game(map);
    game.start();
})();

减少浏览器http请求

将所有的js代码整合到一个js文件里,然后就只发送一次请求,进行优化


自调用函数之后一定要加分号,不然会有程序错误

JS代码压缩

https://tool.oschina.net/jscompress

压缩之后代码运行速度会有提高,同时,压缩后的文件名一般为xxx.min.js

自调用函数的参数

  • window
  • undefined
    两个参数

    作用:1.window,将全局变为局部变量,避免了跳出本层作用域,将来进行代码压缩的时候,如果没有进行传参,速度会稍微减慢一点
    作用:2.undefined,IE8及以下浏览器的undefined是可变的,会被更改
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 227,428评论 6 531
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 98,024评论 3 413
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 175,285评论 0 373
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 62,548评论 1 307
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 71,328评论 6 404
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 54,878评论 1 321
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 42,971评论 3 439
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 42,098评论 0 286
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 48,616评论 1 331
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 40,554评论 3 354
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 42,725评论 1 369
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 38,243评论 5 355
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 43,971评论 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,361评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 35,613评论 1 280
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 51,339评论 3 390
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 47,695评论 2 370

推荐阅读更多精彩内容