zoukankan      html  css  js  c++  java
  • Javascript写了一个2048的游戏

      去年2048很火, 本来我也没玩过, 同事说如果用JS写2048 只要100多行代码;

      PS(iWeb峰会暨攻城师嘉年华2015嘉年华要来啦, 在文章结尾有具体的地址和时间);

      今天试了一下, 逻辑也不复杂, 主要是数据构造函数上的数据的各种操作, 然后通过重新渲染DOM实现界面的更新, 整体不复杂, JS,css,和HTML合起来就300多行;

      界面的生成使用了underscore.jstemplate方法, 使用了jQuery,主要是DOM的选择操作以及动画效果,事件的绑定只做了PC端的兼容,只绑定了keydown事件;

      把代码放到github-page上, 通过点击这里查看 实例: 打开2048实例

      效果图如下:

      

      所有的代码分为两大块,Data, View;

      Data是构造函数, 会把数据构造出来, 数据会继承原型上的一些方法;

      View是根据Data的实例生成视图,并绑定事件等, 我直接把事件认为是controller了,和View放在了一起, 没必要分开;

      Data的结构如下:

            /**
             * @desc 构造函数初始化
             * */
            init : function
            /**
             * @desc 生成了默认的数据地图
             * @param void
             * */
            generateData : function
            /**
             * @desc 随机一个block填充到数据里面
             * @return void
             * */
            generationBlock : function
            /**
             * @desc 获取随机数 2 或者是 4
             * @return 2 || 4;
             * */
            getRandom : function
            /**
             * @desc 获取data里面数据内容为空的位置
             * @return {x:number, y:number}
             * */
            getPosition : function
            /**
             * @desc 把数据里第y排, 第x列的设置, 默认为0, 也可以传值;
             * @param x, y
             * */
            set : function
            /**
             * @desc 在二维数组的区间中水平方向是否全部为0
             * @desc i明确了二维数组的位置, k为开始位置, j为结束为止
             * */
            no_block_horizontal : function
            no_block_vertica : function
            /**
             * @desc 往数据往左边移动,这个很重要
             * */
            moveLeft : function
            moveRight : function
            moveUp : function
            moveDown : function

      有了数据模型,那么视图就简单了,主要是用底线库underscoretemplate方法配合数据生成html字符串,然后对界面进行重绘

    View的原型方法:
            renderHTML : function //生成html字符串,然后放到界面中
            init : function //构造函数初始化方法
            bindEvents : function //给str绑定事件, 认为是控制器即可

      因为原始的2048有方块的移动效果, 我们独立起来了一个服务(工具方法,这个工具方法会被View继承), 主要是负责界面中的方块的移动, getPost是给底线库用的, 在模板生成的过程中需要根据节点的位置动态生成横竖坐标,然后定位:

        var util = {
            animateShowBlock : function() {
                setTimeout(function() {
                    this.renderHTML();
                }.bind(this),200);
            },
            animateMoveBlock : function(prop) {
                $("#num"+prop.form.y+""+prop.form.x).animate({top:40*prop.to.y,left:40*prop.to.x},200);
            },
            //底线库的模板中引用了这个方法;
            getPost : function(num) {
                return num*40 + "px";
            }
            //这个应该算是服务;
        };

      下面是全部的代码, 引用的JS使用了CDN,可以直接打开看看:

    <!DOCTYPE html>
    <html>
    <head lang="en">
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
    <script src="http://cdn.bootcss.com/underscore.js/1.8.3/underscore-min.js"></script>
    <script src="http://cdn.bootcss.com/jquery/2.1.4/jquery.js"></script>
    <style>
        #g{
            position: relative;
        }
    
        .block,.num-block{
            position: absolute;
             40px;
            height: 40px;
            line-height: 40px;
            text-align: center;
            border-radius: 4px;
        }
        .block{
            border:1px solid #eee;
            box-sizing: border-box;
        }
        .num-block{
            color:#27AE60;
            font-weight: bold;
        }
    </style>
        <div class="container">
            <div class="row">
                <div id="g">
                </div>
            </div>
        </div>
    
    <script id="tpl" type="text/template">
        <% for(var i=0; i<data.length; i++) {%>
                <!--生成背景块元素--->
            <% for(var j=0; j< data[i].length; j++ ) { %>
                <div id="<%=i%><%=j%>" class="block" style="left:<%=util.getPost(j)%>;top:<%=util.getPost(i)%>"  data-x="<%=j%>" data-y="<%=i%>" data-info='{"x":<%=[j]%>,"y":<%=[i]%>}'>
                </div>
            <% } %>
                <!--生成数字块元素--->
            <% for(var j=0; j< data[i].length; j++ ) { %>
                <!--如果数据模型里面的值为0,那么不显示这个数据的div--->
                <% if ( 0!==data[i][j] ) {%>
                    <div id="num<%=i%><%=j%>" class="num-block" style="left:<%=util.getPost(j)%>;top:<%=util.getPost(i)%>" >
                        <%=data[i][j]%>
                    </div>
                <% } %>
            <% } %>
        <% } %>
    </script>
    <script>
        var Data = function() {
            this.init();
        };
        $.extend(Data.prototype, {
            /**
             * @desc 构造函数初始化
             * */
            init : function() {
                this.generateData();
            },
            /**
             * @desc 生成了默认的数据地图
             * @param void
             * */
            generateData : function() {
                var data = [];
                for(var i=0; i<4; i++) {
                    data[i] = data[i] || [];
                    for(var j=0; j<4; j++) {
                        data[i][j] = 0;
                    };
                };
                this.map = data;
            },
            /**
             * @desc 随机一个block填充到数据里面
             * @return void
             * */
            generationBlock : function() {
                var data = this.getRandom();
                var position = this.getPosition();
                this.set( position.x, position.y, data)
            },
            /**
             * @desc 获取随机数 2 或者是 4
             * @return 2 || 4;
             * */
            getRandom : function() {
                return Math.random()>0.5 ? 2 : 4;
            },
            /**
             * @desc 获取data里面数据内容为空的位置
             * @return {x:number, y:number}
             * */
            getPosition : function() {
                var data = this.map;
                var arr = [];
                for(var i=0; i<data.length; i++ ) {
                    for(var j=0; j< data[i].length; j++ ) {
                        if( data[i][j] === 0) {
                            arr.push({x:j, y:i});
                        };
                    };
                };
                return arr[ Math.floor( Math.random()*arr.length ) ];
            },
            /**
             * @desc 把数据里第y排, 第x列的设置, 默认为0, 也可以传值;
             * @param x, y
             * */
            set : function(x,y ,arg) {
                this.map[y][x] = arg || 0;
            },
            /**
             * @desc 在二维数组的区间中水平方向是否全部为0
             * @desc i明确了二维数组的位置, k为开始位置, j为结束为止
             * */
            no_block_horizontal: function(i, k, j) {
                k++;
                for( ;k<j; k++) {
                    if(this.map[i][k] !== 0)
                    return false;
                };
                return true;
            },
            //和上面一个方法一样,检测的方向是竖排;
            no_block_vertical : function(i, k, j) {
                var data = this.map;
                k++;
                for(; k<j; k++) {
                    if(data[k][i] !== 0) {
                        return false;
                    };
                };
                return true;
            },
            /**
             * @desc 往左边移动
             * */
            moveLeft : function() {
                /*
                * 往左边移动;
                * 从上到下, 从左到右, 循环;
                * 从0开始继续循环到当前的元素 ,如果左侧的是0,而且之间的空格全部为0 , 那么往这边移,
                * 如果左边的和当前的值一样, 而且之间的空格值全部为0, 就把当前的值和最左边的值相加,赋值给最左边的值;
                * */
                var data = this.map;
                var result = [];
                for(var i=0; i<data.length; i++ ) {
                    for(var j=1; j<data[i].length; j++) {
                        if (data[i][j] != 0) {
                            for (var k = 0; k < j; k++) {
                                //当前的是data[i][j], 如果最左边的是0, 而且之间的全部是0
                                if (data[i][k] === 0 && this.no_block_horizontal(i, k, j)) {
                                    result.push( {form : {y:i,x:j}, to :{y:i,x:k}} );
                                    data[i][k] = data[i][j];
                                    data[i][j] = 0;
                                    //加了continue是因为,当前的元素已经移动到了初始的位置,之间的循环我们根本不需要走了
                                    break;
                                }else if(data[i][j]!==0 && data[i][j] === data[i][k] && this.no_block_horizontal(i, k, j)){
                                    result.push( {form : {y:i,x:j}, to :{y:i,x:k}} );
                                    data[i][k] += data[i][j];
                                    data[i][j] = 0;
                                    break;
                                };
                            };
                        };
                    };
                };
                return result;
            },
            moveRight : function() {
                var result = [];
                var data = this.map;
                for(var i=0; i<data.length; i++ ) {
                    for(var j=data[i].length-2; j>=0; j--) {
                        if (data[i][j] != 0) {
                            for (var k = data[i].length-1; k>j; k--) {
                                //当前的是data[i][j], 如果最左边的是0, 而且之间的全部是0
                                if (data[i][k] === 0 && this.no_block_horizontal(i, k, j)) {
                                    result.push( {form : {y:i,x:j}, to :{y:i,x:k}} );
                                    data[i][k] = data[i][j];
                                    data[i][j] = 0;
                                    break;
                                }else if(data[i][k]!==0 && data[i][j] === data[i][k] && this.no_block_horizontal(i, j, k)){
                                    result.push( {form : {y:i,x:j}, to :{y:i,x:k}} );
                                    data[i][k] += data[i][j];
                                    data[i][j] = 0;
                                    break;
                                };
                            };
                        };
                    };
                };
                return result;
            },
            moveUp : function() {
                var data = this.map;
                var result = [];
                // 循环要检测的长度
                for(var i=0; i<data[0].length; i++ ) {
                    // 循环要检测的高度
                    for(var j=1; j<data.length; j++) {
                        if (data[j][i] != 0) {
                            //x是确定的, 循环y方向;
                            for (var k = 0; k<j ; k++) {
                                //当前的是data[j][i], 如果最上面的是0, 而且之间的全部是0
                                if (data[k][i] === 0 && this.no_block_vertical(i, k, j)) {
                                    result.push( {form : {y:j,x:i}, to :{y:k,x:i}} );
                                    data[k][i] = data[j][i];
                                    data[j][i] = 0;
                                    break;
                                }else if(data[j][i]!==0 && data[k][i] === data[j][i] && this.no_block_vertical(i, k, j)){
                                    result.push( {form : {y:j,x:i}, to :{y:k,x:i}} );
                                    data[k][i] += data[j][i];
                                    data[j][i] = 0;
                                    break;
                                };
                            };
                        };
                    };
                };
                return result;
            },
            moveDown : function() {
                var data = this.map;
                var result = [];
                // 循环要检测的长度
                for(var i=0; i<data[0].length; i++ ) {
                    // 循环要检测的高度
                    for(var j=data.length - 1; j>=0 ; j--) {
                        if (data[j][i] != 0) {
                            //x是确定的, 循环y方向;
                            for (var k = data.length-1; k>j ; k--) {
                                if (data[k][i] === 0 && this.no_block_vertical(i, k, j)) {
                                    result.push( {form : {y:j,x:i}, to :{y:k,x:i}} );
                                    data[k][i] = data[j][i];
                                    data[j][i] = 0;
                                    break;
                                }else if(data[k][i]!==0 && data[k][i] === data[j][i] && this.no_block_vertical(i, j, k)){
                                    result.push( {form : {y:j,x:i}, to :{y:k,x:i}} );
                                    data[k][i] += data[j][i];
                                    data[j][i] = 0;
                                    break;
                                };
                            };
                        };
                    };
                };
                return result;
            }
         });
        var util = {
            animateShowBlock : function() {
                setTimeout(function() {
                    this.renderHTML();
                }.bind(this),200);
            },
            animateMoveBlock : function(prop) {
                $("#num"+prop.form.y+""+prop.form.x).animate({top:40*prop.to.y,left:40*prop.to.x},200);
            },
            //底线库的模板中引用了这个方法;
            getPost : function(num) {
                return num*40 + "px";
            }
            //这个应该算是服务;
        };
        var View = function(data) {
            this.data = data.data;
            this.el = data.el;
            this.renderHTML();
            this.init();
        };
        $.extend(View.prototype, {
            renderHTML : function() {
                var str = _.template( document.getElementById("tpl").innerHTML )( {data : this.data.map} );
                this.el.innerHTML = str;
            },
            init : function() {
                this.bindEvents();
            },
            bindEvents : function() {
                $(document).keydown(function(ev){
                    var animationArray = [];
                    switch(ev.keyCode) {
                        case 37:
                            animationArray = this.data.moveLeft();
                            break;
                        case 38 :
                            animationArray = this.data.moveUp();
                            break;
                        case 39 :
                            animationArray = this.data.moveRight();
                            break;
                        case 40 :
                            animationArray = this.data.moveDown();
                            break;
                    };
                    if( animationArray ) {
                        for(var i=0; i<animationArray.length; i++ ) {
                            var prop = animationArray[i];
                            this.animateMoveBlock(prop);
                        };
                    };
                    this.data.generationBlock();
                    this.animateShowBlock();
                }.bind(this));
            }
        });
    
        $(function() {
            var data = new Data();
            //随机生成两个节点;
            data.generationBlock();
            data.generationBlock();
            //生成视图
            var view = new View({ data :data, el : document.getElementById("g") });
            //继承工具方法, 主要是动画效果的继承;
            $.extend( true, view, util );
            //显示界面
            view.renderHTML();
        });
    </script>
    </body>
    </html>

      最近对HTML5的游戏很感兴趣, 今年8月16号国家会议中心又有WEB开发者大会, 估计H5的游戏大厅又要爆满, 别人那么大, 我想去看看, o(^▽^)o , 想想就想尿一会儿, 太激动了;

      地址是:iWeb峰会暨攻城师嘉年华·3000人+规模的北京站开启报名!8月16日 北京国际会议中心, 有没有要约的童鞋啊, 妹子优先。

      官方网站是:http://2015.html5dw.com/  赶快报名么么哒;

    作者: NONO
    出处:http://www.cnblogs.com/diligenceday/
    QQ:287101329 

  • 相关阅读:
    软件测试进程&测试类型
    课堂笔记:软件测试知识点汇总小结
    闰年测试程序
    关于 int.parse("abcd") 出错的问题分析及解决方案
    软件测试——字符串检测2.0
    边界值分析法实例分析
    测试管理
    软件评审
    单元测试与集成测试
    白盒测试
  • 原文地址:https://www.cnblogs.com/diligenceday/p/4588570.html
Copyright © 2011-2022 走看看