Javascript写了一个2048的游戏
去年2048很火, 本来我也没玩过, 同事说如果用JS写 只要100多行代码;
PS(iWeb峰会暨攻城师嘉年华2015嘉年华要来啦, 在文章结尾有具体的地址和时间);
今天试了一下, 逻辑也不复杂, 主要是数据构造函数上的数据的各种操作, 然后通过重新渲染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 : function
no_block_vertica : function
/**
* @desc 往数据往左边移动,这个很重要
* */
moveLeft : function
moveRight : function
moveUp : function
moveDown : 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, 而且之间的全部是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
Javascript写了一个2048的游戏的更多相关文章
- 使用C#控制台应用程序完成一个2048小游戏
曾经使用C#控制台应用程序写的一个2048,现在翻出来回顾一下 Box类是2048中每一个小格子的相关信息,包括格子的横纵坐标.格子的值和格子是否刚合并这些信息. Grid类是网格的相关信息,包括网格 ...
- JavaScript写的一个带AI的井字棋
最近有一门课结束了,需要做一个井字棋的游戏,我用JavaScript写了一个.首先界面应该问题不大,用html稍微写一下就可以.主要是人机对弈时的ai算法,如何使电脑方聪明起来,是值得思考一下的.开始 ...
- 如何在CentOS上安装一个2048小游戏
如何在centos上安装一个2048小游戏 最近在学习CentOS系统,就琢磨着玩点什么,然后我看到有人在玩2048小游戏,所有我就在想,为啥不装一个2048小游戏搞一下嘞,于是乎,我就开始工作啦 由 ...
- Javascript写的一个可拖拽排序的列表
自己常试写了一个可拖拽进行自定义排序的列表,可能写的不太好,欢迎提供意见. 我的思路是将列表中的所有项都放进一个包裹层,将该包裹层设为相对定位,每当点击一个项时,将该项脱离文档并克隆一份重新添加到文档 ...
- 最少javascript代码完成一个2048游戏
原生javascript代码写的2048游戏.建议在谷歌浏览器下跑.'WASD'控制方向.演示地址请移步:http://runjs.cn/detail/bp8baf8b 直接贴代码~ html: &l ...
- 发个2012年用java写的一个控制台小游戏
时间是把杀狗刀 突然发现了12年用java写的控制台玩的一个文字游戏,有兴趣的可以下载试试哈汪~ 里面难点当时确实遇到过,在计算倒计时的时候用了多线程,当时还写了好久才搞定.很怀念那个时间虽然不会做游 ...
- 【JavaScript游戏开发】使用HTML5+Canvas+JavaScript 封装的一个超级马里奥游戏(包含源码)
这个游戏基本上是建立在JavaScript模块化的开发基础上进行封装的,对游戏里面需要使用到的游戏场景进行了封装,分别实现了Game,Sprite,enemy,player, base,Animati ...
- C#,JavaScript两种语言 2048小游戏
<html> <head> <style type="text/css"> .haha { border-width: 2; font-size ...
- 从零开始手把手教你使用javascript+canvas开发一个塔防游戏01地图创建
项目演示 项目演示地址: 体验一下 项目源码: 项目源码 代码结构 本节做完效果 游戏主页面 index.html <!DOCTYPE html PUBLIC "-//W3C//DTD ...
随机推荐
- 2014 UESTC暑前集训搜索专题解题报告
A.解救小Q BFS.每次到达一个状态时看是否是在传送阵的一点上,是则传送到另一点即可. 代码: #include <iostream> #include <cstdio> # ...
- POJ2796Feel Good[单调栈]
Feel Good Time Limit: 3000MS Memory Limit: 65536K Total Submissions: 13376 Accepted: 3719 Case T ...
- AnjularJs的增删改查(单页网站)
2016.6.4 学习文献: 你的第一个AngularJS应用:https://segmentfault.com/a/1190000000347412 AngularJS 提交表单的方式:http:/ ...
- ios客户端快速滚动和回弹效果的实现
现在很多for Mobile的HTML5网页内都有快速滚动和回弹的效果,看上去和原生app的效率都有得一拼. 要实现这个效果很简单,只需要给容器加一行css代码即可 -webkit-overflow- ...
- 投入Html5的怀抱,最近在研究的Egret
html5没有办法不关注,实在太火热了,几年前还不行,如今确是环境较好,typescript语言很好学习,可能基于之前的基础,不到一个星期就基本上差不多了,虽然还有一些小问题,但那都是经验积累下来可以 ...
- Protocol https not supported or disabled in libcurl
最后用PHP Curl 模拟访问HTTPS ,总是得到 Protocol https not supported or disabled in libcurl 错误,奇怪了,找了很多资料,有人说没有开 ...
- Linux共享库 socket辅助方法
//sockhelp.h#ifndef _vx #define _vx #ifdef __cplusplus extern "C" { #endif /** * readn - 读 ...
- eclipse/intellij idea 远程调试hadoop 2.6.0
很多hadoop初学者估计都我一样,由于没有足够的机器资源,只能在虚拟机里弄一个linux安装hadoop的伪分布,然后在host机上win7里使用eclipse或Intellj idea来写代码测试 ...
- 2015-2016-2 《Java程序设计》教学进程
2015-2016-2 <Java程序设计>教学进程 目录 考核方式 寒假准备 教学进程 第00周学习任务和要求 第01周学习任务和要求 第02周学习任务和要求 第03周学习任务和要求 第 ...
- href的那些事
很多网站中都会使用<a>标签和 href属性来做链接,尤其在分页显示中用得最普遍.然而很多人对href的使用却并不十分了解. 1.href="#" 这个在网页中上滚回顶 ...