Welcome 微信登录

首页 / 脚本样式 / JavaScript / Javascript编写2048小游戏

去年2048很火, 本来我也没玩过, 同事说如果用JS写2048 只要100多行代码;
今天试了一下, 逻辑也不复杂, 主要是数据构造函数上的数据的各种操作, 然后通过重新渲染DOM实现界面的更新, 整体不复杂, JS,css,和HTML合起来就300多行;
界面的生成使用了underscore.js的template方法, 使用了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 : functionno_block_vertica : function/** * @desc 往数据往左边移动,这个很重要 * */moveLeft : functionmoveRight : functionmoveUp : functionmoveDown : function
有了数据模型,那么视图就简单了,主要是用底线库underscore的template方法配合数据生成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, 而且之间的全部是0if (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, 而且之间的全部是0if (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, 而且之间的全部是0if (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>
以上所述就是本文的全部内容了,希望大家能够喜欢。