Javascript編寫2048小游戲
去年2048很火, 本來我也沒玩過, 同事說如果用JS寫2048 只要100多行代碼;
今天試了一下, 邏輯也不復雜, 主要是數(shù)據(jù)構(gòu)造函數(shù)上的數(shù)據(jù)的各種操作, 然后通過重新渲染DOM實現(xiàn)界面的更新, 整體不復雜, JS,css,和HTML合起來就300多行;
界面的生成使用了underscore.js的template方法, 使用了jQuery,主要是DOM的選擇和操作以及動畫效果,事件的綁定只做了PC端的兼容,只綁定了keydown事件;
把代碼放到github-page上, 通過點擊這里查看 實例: 打開2048實例;
效果圖如下:
所有的代碼分為兩大塊,Data, View;
Data是構(gòu)造函數(shù), 會把數(shù)據(jù)構(gòu)造出來, 數(shù)據(jù)會繼承原型上的一些方法;
View是根據(jù)Data的實例生成視圖,并綁定事件等, 我直接把事件認為是controller了,和View放在了一起, 沒必要分開;
Data的結(jié)構(gòu)如下:
/** * @desc 構(gòu)造函數(shù)初始化 * */ init : function /** * @desc 生成了默認的數(shù)據(jù)地圖 * @param void * */ generateData : function /** * @desc 隨機一個block填充到數(shù)據(jù)里面 * @return void * */ generationBlock : function /** * @desc 獲取隨機數(shù) 2 或者是 4 * @return 2 || 4; * */ getRandom : function /** * @desc 獲取data里面數(shù)據(jù)內(nèi)容為空的位置 * @return {x:number, y:number} * */ getPosition : function /** * @desc 把數(shù)據(jù)里第y排, 第x列的設(shè)置, 默認為0, 也可以傳值; * @param x, y * */ set : function /** * @desc 在二維數(shù)組的區(qū)間中水平方向是否全部為0 * @desc i明確了二維數(shù)組的位置, k為開始位置, j為結(jié)束為止 * */ no_block_horizontal : function no_block_vertica : function /** * @desc 往數(shù)據(jù)往左邊移動,這個很重要 * */ moveLeft : function moveRight : function moveUp : function moveDown : function
有了數(shù)據(jù)模型,那么視圖就簡單了,主要是用底線庫underscore的template方法配合數(shù)據(jù)生成html字符串,然后對界面進行重繪:
View的原型方法:
renderHTML : function //生成html字符串,然后放到界面中
init : function //構(gòu)造函數(shù)初始化方法
bindEvents : function //給str綁定事件, 認為是控制器即可
因為原始的2048有方塊的移動效果, 我們獨立起來了一個服務(wù)(工具方法,這個工具方法會被View繼承), 主要是負責界面中的方塊的移動, getPost是給底線庫用的, 在模板生成的過程中需要根據(jù)節(jié)點的位置動態(tài)生成橫豎坐標,然后定位:
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"; } //這個應(yīng)該算是服務(wù); };
下面是全部的代碼, 引用的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> <% } %> <!--生成數(shù)字塊元素---> <% for(var j=0; j< data[i].length; j++ ) { %> <!--如果數(shù)據(jù)模型里面的值為0,那么不顯示這個數(shù)據(jù)的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 構(gòu)造函數(shù)初始化 * */ init : function() { this.generateData(); }, /** * @desc 生成了默認的數(shù)據(jù)地圖 * @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填充到數(shù)據(jù)里面 * @return void * */ generationBlock : function() { var data = this.getRandom(); var position = this.getPosition(); this.set( position.x, position.y, data) }, /** * @desc 獲取隨機數(shù) 2 或者是 4 * @return 2 || 4; * */ getRandom : function() { return Math.random()>0.5 ? 2 : 4; }, /** * @desc 獲取data里面數(shù)據(jù)內(nèi)容為空的位置 * @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 把數(shù)據(jù)里第y排, 第x列的設(shè)置, 默認為0, 也可以傳值; * @param x, y * */ set : function(x,y ,arg) { this.map[y][x] = arg || 0; }, /** * @desc 在二維數(shù)組的區(qū)間中水平方向是否全部為0 * @desc i明確了二維數(shù)組的位置, k為開始位置, j為結(jié)束為止 * */ 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() { /* * 往左邊移動; * 從上到下, 從左到右, 循環(huán); * 從0開始繼續(xù)循環(huán)到當前的元素 ,如果左側(cè)的是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是因為,當前的元素已經(jīng)移動到了初始的位置,之間的循環(huán)我們根本不需要走了 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 = []; // 循環(huán)要檢測的長度 for(var i=0; i<data[0].length; i++ ) { // 循環(huán)要檢測的高度 for(var j=1; j<data.length; j++) { if (data[j][i] != 0) { //x是確定的, 循環(huán)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 = []; // 循環(huán)要檢測的長度 for(var i=0; i<data[0].length; i++ ) { // 循環(huán)要檢測的高度 for(var j=data.length - 1; j>=0 ; j--) { if (data[j][i] != 0) { //x是確定的, 循環(huán)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"; } //這個應(yīng)該算是服務(wù); }; 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(); //隨機生成兩個節(jié)點; data.generationBlock(); data.generationBlock(); //生成視圖 var view = new View({ data :data, el : document.getElementById("g") }); //繼承工具方法, 主要是動畫效果的繼承; $.extend( true, view, util ); //顯示界面 view.renderHTML(); }); </script> </body> </html>
以上所述就是本文的全部內(nèi)容了,希望大家能夠喜歡。
相關(guān)文章
TypeScript里string和String的區(qū)別
這篇文章主要介紹了TypeScript里string和String的區(qū)別,真的不止是大小寫的區(qū)別,string表示原生類型,而String表示對象,下文更多詳細內(nèi)容需要的小伙伴可以參考一下2022-03-03JS/jQuery實現(xiàn)超簡單的Table表格添加,刪除行功能示例
這篇文章主要介紹了JS/jQuery實現(xiàn)超簡單的Table表格添加,刪除行功能,結(jié)合實例形式詳細分析了JS與jQuery針對Table表格添加,刪除行功能的相關(guān)實現(xiàn)技巧,需要的朋友可以參考下2019-07-07淺談webpack 構(gòu)建性能優(yōu)化策略小結(jié)
webpack以其豐富的功能和靈活的配置而深受業(yè)內(nèi)吹捧,逐步取代了grunt和gulp成為大多數(shù)前端工程實踐中的首選,這篇文章主要介紹了淺談webpack 構(gòu)建性能優(yōu)化策略小結(jié),感興趣的小伙伴們可以參考一下2018-06-06js漢字排序問題 支持中英文混排,兼容各瀏覽器,包括CHROME
這套排序機制同時兼容了IE和ff 可以實現(xiàn)所有瀏覽器下排序的統(tǒng)一哦2011-12-12