jQuery編寫網(wǎng)頁版2048小游戲
大致介紹
看了一個實現(xiàn)網(wǎng)頁版2048小游戲的視頻,覺得能做出自己以前喜歡玩的小游戲很有意思便自己動手試了試,真正的驗證了這句話-不要以為你以為的就是你以為的,看視頻時覺得看懂了,會寫了,但是自己實現(xiàn)起來會遇到各種問題。比如,在最后判斷游戲是否結(jié)束的時候,我寫的語句語法是對的,但就是不執(zhí)行。最后通過對視頻源碼的分析對比,發(fā)現(xiàn)原作者寫的一個setTimeout定時器有額外的意思,本來我以為它就是簡單的一個延時動畫,其實他是在等待另外一個函數(shù)執(zhí)行完畢。-_-||。最后還是很高興能寫出來,也改進(jìn)了一些源代碼的不足。
這篇博客并不是詳細(xì)的講解,只是大致介紹函數(shù)的作用,其中實現(xiàn)的細(xì)節(jié)注釋中有解釋,網(wǎng)上的這個源碼有點亂,如果想看比較整齊的源碼或者視頻的可以QQ聯(lián)系我(免費)(找共同學(xué)習(xí)的伙伴)
思路
這個小游戲可以抽象化分為3層(我覺得這樣能更好理解)
◆最底下的一層是基本的樣式(可見的)
◆中間的層是最主要的,是一個4x4的二維數(shù)組,游戲中我們都是對這個二維數(shù)組進(jìn)行操作(不可見的)
◆最上面的一層也是一個4x4的二維數(shù)組,它只是根據(jù)第二層數(shù)組的每個數(shù)顯示樣式(可見的)
我們通過最底下的一層顯示最基本的16個小方格,通過鍵盤的按鍵或者手指在屏幕的滑動來操作中間層的數(shù)組,最后在通過最上面的一層顯示出數(shù)字
基本結(jié)構(gòu)與樣式
基本的結(jié)構(gòu)和樣式都挺簡單,直接看代碼
結(jié)構(gòu):
<div id="test2048"> <div id="header"> <h1>2048</h1> <a href="javascript:newgame()" >開始新的游戲</a> <p>分?jǐn)?shù):<span id="score">0</span></p> </div> <div id="container"> <div class="cell" id="cell-0-0"></div> <div class="cell" id="cell-0-1"></div> <div class="cell" id="cell-0-2"></div> <div class="cell" id="cell-0-3"></div> <div class="cell" id="cell-1-0"></div> <div class="cell" id="cell-1-1"></div> <div class="cell" id="cell-1-2"></div> <div class="cell" id="cell-1-3"></div> <div class="cell" id="cell-2-0"></div> <div class="cell" id="cell-2-1"></div> <div class="cell" id="cell-2-2"></div> <div class="cell" id="cell-2-3"></div> <div class="cell" id="cell-3-0"></div> <div class="cell" id="cell-3-1"></div> <div class="cell" id="cell-3-2"></div> <div class="cell" id="cell-3-3"></div> </div> </div>
樣式:
*{
margin: 0;
padding: 0;
}
#test2048{
font-family: Arial;
margin: 0 auto;
text-align: center;
}
#header{
margin: 20px;
}
#header a{
font-family: Arial;
text-decoration: none;
display: block;
color: white;
margin: 20px auto;
width: 125px;
height: 35px;
text-align: center;
line-height: 40px;
background-color: #8f7a66;
border-radius: 10px;
font-size: 15px;
}
#header p{
font-family: Arial;
font-size: 20px;
}
#container{
width: 460px;
height: 460px;
background-color: #bbada0;
margin: 0 auto;
border-radius: 10px;
position: relative;
padding: 20px;
}
.cell{
width: 100px;
height: 100px;
border-radius: 6px;
background-color: #ccc0b3;
position: absolute;
}
從CSS樣式可以看出,我們并沒有對每個格子的位置進(jìn)行設(shè)置,因為如果用CSS給每個格子設(shè)置樣式代碼量太大,而且他們的位置有一定的規(guī)律,所以我們可以用js循環(huán)來完成每個格子樣式的設(shè)置
代碼:
// 初始化棋盤格
function initialize(){
for(var i=0;i<4;i++){
for(var j=0;j<4;j++){
// 設(shè)置棋盤格的位置
var everyCell = $('#cell-'+ i +'-'+ j);
everyCell.css({top:getPos(i),left:getPos(j)});
}
}
}
// 獲取位置
function getPos(num){
return 20 + num*120;
}
這樣我們的第一層就好了
效果:

現(xiàn)在構(gòu)造第二層,即構(gòu)建一個4x4的值全部為0的數(shù)組,由于在構(gòu)造第二層時,有兩層循環(huán),所以我們可以在構(gòu)造第一層時也能構(gòu)造第二層
第三層是用js生成16個格子,它和第一層的16個格子一一對應(yīng)
代碼:
// 數(shù)字格
function numFormat(){
for(var i=0;i<4;i++){
for(var j=0;j<4;j++){
$('#container').append('<div class="number" id="number-'+ i +'-'+ j +'"></div>')
// 設(shè)置數(shù)字格的位置,樣式
var everyNumber = $('#number-'+ i +'-'+ j);
if(checkerboard[i][j] == 0){
everyNumber.css({
width:'0px',
height:'opx',
top:getPos(i) + 50,
left:getPos(j) + 50
})
}else{
everyNumber.css({
width:'100px',
height:'100px',
top:getPos(i),
left:getPos(j),
backgroundColor:getBackgroundColor(checkerboard[i][j]),
color:getColor(checkerboard[i][j])
});
everyNumber.text(checkerboard[i][j]);
}
}
}
}
// 獲取相應(yīng)數(shù)字的背景顏色
function getBackgroundColor(number){
switch (number) {
case 2:return "#eee4da";break;
case 4:return "#ede0c8";break;
case 8:return "#f2b179";break;
case 16:return "#f59563";break;
case 32:return "#f67c5f";break;
case 64:return "#f65e3b";break;
case 128:return "#edcf72";break;
case 256:return "#edcc61";break;
case 512:return "#9c0";break;
case 1024:return "#33b5e5";break;
case 2048:return "#09c";break;
case 4096:return "#a6c";break;
case 8192:return "#93c";break;
}
}
// 設(shè)置相應(yīng)數(shù)字的文字顏色
function getColor(number){
if (number <= 4) {
return "#776e65"
}
return "white";
}
初始化
在每次游戲重新開始時,都會在隨機(jī)的位置出現(xiàn)兩個隨機(jī)的數(shù)字,我們寫一個在隨機(jī)位置出現(xiàn)一個隨機(jī)數(shù)的函數(shù),只要調(diào)用兩次就可以實現(xiàn)了
代碼:
// 隨機(jī)的在一個位置上產(chǎn)生一個數(shù)字
function randomNum(){
// 隨機(jī)產(chǎn)生一個坐標(biāo)值
var randomX = Math.floor(Math.random() * 4);
var randomY = Math.floor(Math.random() * 4);
// 隨機(jī)產(chǎn)生一個數(shù)字(2或4)
var randomValue = Math.random() > 0.5 ? 2 : 4;
// 在數(shù)字格不為0的地方生成一個隨機(jī)數(shù)字
while(true){
if(checkerboard[randomX][randomY] == 0){
break;
}else{
var randomX = Math.floor(Math.random() * 4);
var randomY = Math.floor(Math.random() * 4);
}
}
// 將隨機(jī)產(chǎn)生的數(shù)字顯示在隨機(jī)的位置上
checkerboard[randomX][randomY] = randomValue;
// 動畫
randomNumAnimate(randomX,randomY,randomValue);
}
// 隨機(jī)產(chǎn)生數(shù)字的動畫
function randomNumAnimate(randomX,randomY,randomValue){
var randomnum = $('#number-'+ randomX +'-'+ randomY);
randomnum.css({
backgroundColor:getBackgroundColor(randomValue),
color:getColor(randomValue),
})
.text(randomValue)
.animate({
width:'100px',
height:'100px',
top:getPos(randomX),
left:getPos(randomY)
},50);
}
基本操作
我們通過switch循環(huán),來根據(jù)用戶不同的輸入進(jìn)行不同的操作
代碼:
// 獲取鍵盤事件,檢測不同的按鍵進(jìn)行不同的操作
$(document).keydown(function(event){
switch(event.keyCode){
case 37://左
if(canMoveLeft(checkerboard)){
// 如果可以向左移動
MoveLeft();
// 向左移動
setTimeout(function(){
randomNum();
},200);
// 隨機(jī)產(chǎn)生一個數(shù)字
}
break;
case 38://上
if(canMoveUp(checkerboard)){
// 如果可以向上移動
MoveUp();
// 向上移動
setTimeout(function(){
randomNum();
},200);
// 隨機(jī)產(chǎn)生一個數(shù)字
}
break;
case 39://右
if(canMoveRight(checkerboard)){
// 如果可以向右移動
MoveRight();
// 向右移動
setTimeout(function(){
randomNum();
},200);
// 隨機(jī)產(chǎn)生一個數(shù)字
}
break;
case 40://下
if(canMoveDown(checkerboard)){
// 如果可以向下移動
MoveDown();
// 向下移動
setTimeout(function(){
randomNum();
},200);
// 隨機(jī)產(chǎn)生一個數(shù)字
}
break;
default:
break;
}
});
由于數(shù)字格的移動只有左、上、右、下四種方式,并且他們都是大同小異的,所以就拿向左移動為例,
向左移動,我們首先需要判斷它是否能向左移動,能向左移動有兩種情況
第一種:當(dāng)前格子的左邊的格子是空的即值為0
第二種:當(dāng)前格子的值和左邊格子的值相同
由于向左移動,所以第一列的格子不可能向左移動,所以不需要判斷
代碼:
// 判斷是否可以向左移動
function canMoveLeft(checkerboard){
for(var i=0;i<4;i++){
for(var j=1;j<4;j++){
if(checkerboard[i][j] != 0){
// 如果這個數(shù)字格它左邊的數(shù)字格為空或者左邊的數(shù)字格和它相等,則可以向左移動
if(checkerboard[i][j-1] == 0 || checkerboard[i][j] == checkerboard[i][j-1]){
return true;
}
}
}
}
return false;
}
判斷能否向左移動后,我們就要對可以移動的格子進(jìn)行移動,這里需要特別注意,向哪個方向移動就要先從哪個方向開始判斷
代碼:
// 向左移動
function MoveLeft(){
for(var i=0;i<4;i++){
for(var j=1;j<4;j++){
if(checkerboard[i][j] != 0){
for(var k=0;k<j;k++){
if(checkerboard[i][k] == 0 && noMiddleNumRow(i,k,j,checkerboard)){
moveAnimation(i,j,i,k);
checkerboard[i][k] = checkerboard[i][j];
checkerboard[i][j] = 0;
}else if(checkerboard[i][k] == checkerboard[i][j] && noMiddleNumRow(i,k,j,checkerboard) && !hasConflicted[i][k]){
moveAnimation(i,j,i,k);
checkerboard[i][k] += checkerboard[i][j];
checkerboard[i][j] = 0;
}
}
}
}
}
// 設(shè)置刷新的時間是為了讓運動的動畫走完在進(jìn)行更新數(shù)字格,否則數(shù)字格運動的動畫將會被打斷
setTimeout(function(){
numFormat();
},200);
}
// 判斷中間的數(shù)字格是否為0(行)
function noMiddleNumRow(row,col1,col2,checkerboard){
for(var i=col1+1;i<col2;i++){
if(checkerboard[row][i] != 0){
return false;
}
}
return true;
}
將上、右、下四個方向?qū)懲暌院螅螒蚧镜牟僮骶鸵呀?jīng)完成了。
游戲分?jǐn)?shù)和判斷游戲結(jié)束
游戲的分?jǐn)?shù)是每個相加的數(shù)的和,所以我們在每個數(shù)相加的時候更新分?jǐn)?shù)就可以了
代碼:
// 更新分?jǐn)?shù) score += checkerboard[k][j]; updateScore(score);
// 設(shè)置分?jǐn)?shù)
function updateScore(num){
$('#score').text(num);
}
判斷游戲是否結(jié)束很簡單,用我們之前定義的方法就可以實現(xiàn)
代碼:
// 判斷游戲是否結(jié)束
function wheGameOver(checkerboard){
if(!canMoveLeft(checkerboard) && !canMoveUp(checkerboard) && !canMoveRight(checkerboard) && !canMoveDown(checkerboard) ){
showGameOver();
}
}
// 顯示游戲結(jié)束
function showGameOver(){
$('#container').append("<div id='gameover'><p>最終得分</p><span>"+ score +"</span><a href='javascript:resert();'>重新開始游戲</a></div> ")
}
// 重新開始游戲
function resert(){
$('#gameover').remove();
newgame();
}
最后優(yōu)化
1、游戲中會出現(xiàn)一次移動,一個數(shù)會被累加很多次
在原游戲中,每個數(shù)在每次操作中只能累加一次,所以我們在定義一個4x4的值為false的數(shù)組,與中間層的數(shù)組一一對應(yīng),專門用來防止一個數(shù)的多次累加,如果是false則可以累加,并將值改為false,否則不可以累加
2、結(jié)束死循環(huán)
由于在設(shè)置隨機(jī)數(shù)的時候用到了一個死循環(huán),但是在游戲結(jié)束后,該循環(huán)還在,所以我們在死循環(huán)中在添加一個條件,如果游戲結(jié)束就跳出循環(huán)
3、最后的結(jié)束游戲提示不執(zhí)行
case 37://左
if(canMoveLeft(checkerboard)){
// 如果可以向左移動
MoveLeft();
// 向左移動
setTimeout(function(){
wheGameOver(checkerboard)
},300);
// 判斷游戲是否結(jié)束,這里設(shè)置延時是因為要等到隨機(jī)產(chǎn)生數(shù)字后再進(jìn)行判斷,如果不加
// 延時,則最后一次的判斷因為canMoveLeft(checkerboard)為false就不會再執(zhí)行了
setTimeout(function(){
randomNum();
},200);
// 隨機(jī)產(chǎn)生一個數(shù)字
}
break;
從代碼中可以看出,判斷游戲是否結(jié)束是在隨機(jī)產(chǎn)生一個數(shù)字前執(zhí)行的,所以在判斷游戲結(jié)束時,總是有一個空的格子,所以代碼執(zhí)行后認(rèn)為游戲沒有結(jié)束,但是當(dāng)這個隨機(jī)數(shù)字產(chǎn)生后,所有的格子不能移動,當(dāng)我們按鍵時,if條件不通過,判斷游戲是否結(jié)束的函數(shù)不能執(zhí)行。所以我們要給判斷游戲結(jié)束的函數(shù)設(shè)置定時器,讓他在隨機(jī)產(chǎn)生一個數(shù)字后再進(jìn)行判斷
4、在移動端可以執(zhí)行
由于原作者沒有寫有關(guān)移動端的操作,所以我在網(wǎng)上找的判斷移動端觸屏手機(jī)滑動位置的代碼,加入了游戲的事件就可以執(zhí)行了
//返回角度
function GetSlideAngle(dx, dy) {
return Math.atan2(dy, dx) * 180 / Math.PI;
}
//根據(jù)起點和終點返回方向 1:向上,2:向下,3:向左,4:向右,0:未滑動
function GetSlideDirection(startX, startY, endX, endY) {
var dy = startY - endY;
var dx = endX - startX;
varresult = 0;
//如果滑動距離太短
if(Math.abs(dx) < 2 && Math.abs(dy) < 2) {
returnresult;
}
var angle = GetSlideAngle(dx, dy);
if(angle >= -45 && angle < 45) {
result = 4;
}else if (angle >= 45 && angle < 135) {
result = 1;
}else if (angle >= -135 && angle < -45) {
result = 2;
}
else if ((angle >= 135 && angle <= 180) || (angle >= -180 && angle < -135)) {
result = 3;
}
return result;
}
//滑動處理
var startX, startY;
document.addEventListener('touchstart',function (ev) {
startX = ev.touches[0].pageX;
startY = ev.touches[0].pageY;
}, false);
document.addEventListener('touchend',function (ev) {
var endX, endY;
endX = ev.changedTouches[0].pageX;
endY = ev.changedTouches[0].pageY;
var direction = GetSlideDirection(startX, startY, endX, endY);
switch(direction) {
case 0:
//沒滑動
break;
case 1:
if(canMoveUp(checkerboard)){
// 如果可以向上移動
MoveUp();
// 向上移動
setTimeout(function(){
wheGameOver(checkerboard)
},300);
// 判斷游戲是否結(jié)束
setTimeout(function(){
randomNum();
},200);
// 隨機(jī)產(chǎn)生一個數(shù)字
}
break;
case 2:
if(canMoveDown(checkerboard)){
// 如果可以向下移動
MoveDown();
// 向下移動
setTimeout(function(){
wheGameOver(checkerboard)
},300);
// 判斷游戲是否結(jié)束
setTimeout(function(){
randomNum();
},200);
// 隨機(jī)產(chǎn)生一個數(shù)字
}
break;
case 3:
if(canMoveLeft(checkerboard)){
// 如果可以向左移動
MoveLeft();
// 向左移動
setTimeout(function(){
wheGameOver(checkerboard)
},300);
// 判斷游戲是否結(jié)束,這里設(shè)置延時是因為要等到隨機(jī)產(chǎn)生數(shù)字后再進(jìn)行判斷,如果不加
// 延時,則最后一次的判斷因為canMoveLeft(checkerboard)為false就不會再執(zhí)行了
setTimeout(function(){
randomNum();
},200);
// 隨機(jī)產(chǎn)生一個數(shù)字
}
break;
case 4:
if(canMoveRight(checkerboard)){
// 如果可以向右移動
MoveRight();
// 向右移動
setTimeout(function(){
wheGameOver(checkerboard)
},300);
// 判斷游戲是否結(jié)束
setTimeout(function(){
randomNum();
},200);
// 隨機(jī)產(chǎn)生一個數(shù)字
}
break;
default:
}
}, false);
總結(jié)
總體來說這個游戲?qū)崿F(xiàn)起來并不是太難,就是許多小的操作集合起來
以上就是本文的全部內(nèi)容,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作能帶來一定的幫助,同時也希望多多支持腳本之家!
相關(guān)文章
jQuery實現(xiàn)form表單序列化轉(zhuǎn)換為json對象功能示例
這篇文章主要介紹了jQuery實現(xiàn)form表單序列化轉(zhuǎn)換為json對象功能,結(jié)合實例形式分析了表單數(shù)據(jù)傳輸中使用serializeJson進(jìn)行序列化操作相關(guān)實現(xiàn)技巧,需要的朋友可以參考下2018-05-05

