文章出處

  去年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;
        width: 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 


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()