Javascript編寫2048小游戲
去年2048很火, 本來(lái)我也沒(méi)玩過(guò), 同事說(shuō)如果用JS寫2048 只要100多行代碼;
今天試了一下, 邏輯也不復(fù)雜, 主要是數(shù)據(jù)構(gòu)造函數(shù)上的數(shù)據(jù)的各種操作, 然后通過(guò)重新渲染DOM實(shí)現(xiàn)界面的更新, 整體不復(fù)雜, JS,css,和HTML合起來(lái)就300多行;
界面的生成使用了underscore.js的template方法, 使用了jQuery,主要是DOM的選擇和操作以及動(dòng)畫效果,事件的綁定只做了PC端的兼容,只綁定了keydown事件;
把代碼放到github-page上, 通過(guò)點(diǎn)擊這里查看 實(shí)例: 打開2048實(shí)例;
效果圖如下:

所有的代碼分為兩大塊,Data, View;
Data是構(gòu)造函數(shù), 會(huì)把數(shù)據(jù)構(gòu)造出來(lái), 數(shù)據(jù)會(huì)繼承原型上的一些方法;
View是根據(jù)Data的實(shí)例生成視圖,并綁定事件等, 我直接把事件認(rèn)為是controller了,和View放在了一起, 沒(méi)必要分開;
Data的結(jié)構(gòu)如下:
/**
* @desc 構(gòu)造函數(shù)初始化
* */
init : function
/**
* @desc 生成了默認(rèn)的數(shù)據(jù)地圖
* @param void
* */
generateData : function
/**
* @desc 隨機(jī)一個(gè)block填充到數(shù)據(jù)里面
* @return void
* */
generationBlock : function
/**
* @desc 獲取隨機(jī)數(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è)置, 默認(rèn)為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ù)往左邊移動(dòng),這個(gè)很重要
* */
moveLeft : function
moveRight : function
moveUp : function
moveDown : function
有了數(shù)據(jù)模型,那么視圖就簡(jiǎn)單了,主要是用底線庫(kù)underscore的template方法配合數(shù)據(jù)生成html字符串,然后對(duì)界面進(jìn)行重繪:
View的原型方法:
renderHTML : function //生成html字符串,然后放到界面中
init : function //構(gòu)造函數(shù)初始化方法
bindEvents : function //給str綁定事件, 認(rèn)為是控制器即可
因?yàn)樵嫉?048有方塊的移動(dòng)效果, 我們獨(dú)立起來(lái)了一個(gè)服務(wù)(工具方法,這個(gè)工具方法會(huì)被View繼承), 主要是負(fù)責(zé)界面中的方塊的移動(dòng), getPost是給底線庫(kù)用的, 在模板生成的過(guò)程中需要根據(jù)節(jié)點(diǎn)的位置動(dòng)態(tài)生成橫豎坐標(biāo),然后定位:
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);
},
//底線庫(kù)的模板中引用了這個(gè)方法;
getPost : function(num) {
return num*40 + "px";
}
//這個(gè)應(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,那么不顯示這個(gè)數(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 生成了默認(rèn)的數(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 隨機(jī)一個(gè)block填充到數(shù)據(jù)里面
* @return void
* */
generationBlock : function() {
var data = this.getRandom();
var position = this.getPosition();
this.set( position.x, position.y, data)
},
/**
* @desc 獲取隨機(jī)數(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è)置, 默認(rèn)為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;
},
//和上面一個(gè)方法一樣,檢測(cè)的方向是豎排;
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 往左邊移動(dòng)
* */
moveLeft : function() {
/*
* 往左邊移動(dòng);
* 從上到下, 從左到右, 循環(huán);
* 從0開始繼續(xù)循環(huán)到當(dāng)前的元素 ,如果左側(cè)的是0,而且之間的空格全部為0 , 那么往這邊移,
* 如果左邊的和當(dāng)前的值一樣, 而且之間的空格值全部為0, 就把當(dāng)前的值和最左邊的值相加,賦值給最左邊的值;
* */
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++) {
//當(dāng)前的是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是因?yàn)?,?dāng)前的元素已經(jīng)移動(dò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--) {
//當(dāng)前的是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)要檢測(cè)的長(zhǎng)度
for(var i=0; i<data[0].length; i++ ) {
// 循環(huán)要檢測(cè)的高度
for(var j=1; j<data.length; j++) {
if (data[j][i] != 0) {
//x是確定的, 循環(huán)y方向;
for (var k = 0; k<j ; k++) {
//當(dāng)前的是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)要檢測(cè)的長(zhǎng)度
for(var i=0; i<data[0].length; i++ ) {
// 循環(huán)要檢測(cè)的高度
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);
},
//底線庫(kù)的模板中引用了這個(gè)方法;
getPost : function(num) {
return num*40 + "px";
}
//這個(gè)應(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();
//隨機(jī)生成兩個(gè)節(jié)點(diǎn);
data.generationBlock();
data.generationBlock();
//生成視圖
var view = new View({ data :data, el : document.getElementById("g") });
//繼承工具方法, 主要是動(dòng)畫效果的繼承;
$.extend( true, view, util );
//顯示界面
view.renderHTML();
});
</script>
</body>
</html>
以上所述就是本文的全部?jī)?nèi)容了,希望大家能夠喜歡。
相關(guān)文章
在瀏覽器測(cè)試JavaScript的方法小結(jié)
測(cè)試JavaScript代碼是一件很痛苦的事情,很多情況下都是寫好代碼不斷刷新測(cè)試,其實(shí)chrome瀏覽器的console下就很方便,這里就為大家簡(jiǎn)單分享一下2023-03-03
Three.Js實(shí)現(xiàn)顏色自定義變換效果實(shí)例
這篇文章主要給大家介紹了關(guān)于Three.Js實(shí)現(xiàn)顏色自定義變換效果的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2023-02-02
JavaScript獲取URL參數(shù)的四種方法總結(jié)
在前端開發(fā)過(guò)程中難免會(huì)遇到處理url參數(shù)的問(wèn)題,這篇文章主要給大家總結(jié)介紹了關(guān)于JavaScript獲取URL參數(shù)的四種方法,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-01-01
JS 分號(hào)引起的一段調(diào)試問(wèn)題
看看執(zhí)行后有什么效果,無(wú)論textbox1.text是什么值,都會(huì)時(shí)放條件判斷.為什么呢2009-06-06
canvas實(shí)現(xiàn)粒子時(shí)鐘效果
本文將使用canvas實(shí)現(xiàn)粒子時(shí)鐘效果,具有一定的參考價(jià)值,下面跟著小編一起來(lái)看下吧2017-02-02
JS實(shí)現(xiàn)Date日期格式的本地化的方法小結(jié)
為了更好的更新多語(yǔ)言日期的顯示,所以希望實(shí)現(xiàn)日期的本地化格式顯示要求,常規(guī)的特殊字符型格式化無(wú)法滿足顯示要求,這里整理了幾種我思考實(shí)現(xiàn)的本地化實(shí)現(xiàn)功能2024-06-06
在for循環(huán)中l(wèi)ength值是否需要緩存
這篇文章主要介紹了在for循環(huán)中l(wèi)ength值是否需要緩存,需要的朋友可以參考下2015-07-07

