▓▓▓▓▓▓ 大致介紹
看了一個實現網頁版2048小游戲的視頻,覺得能做出自己以前喜歡玩的小游戲很有意思便自己動手試了試,真正的驗證了這句話-不要以為你以為的就是你以為的,看視頻時覺得看懂了,會寫了,但是自己實現起來會遇到各種問題。比如,在最后判斷游戲是否結束的時候,我寫的語句語法是對的,但就是不執行。最后通過對視頻源碼的分析對比,發現原作者寫的一個setTimeout定時器有額外的意思,本來我以為它就是簡單的一個延時動畫,其實他是在等待另外一個函數執行完畢。-_-||。最后還是很高興能寫出來,也改進了一些源代碼的不足。
jQuery在這個游戲中的應用並不多,如果對其中的jQuery語法有疑問,可以參考我寫的jQuery學習之路(持續更新),里面有講解
預覽:2048網頁版
這篇博客並不是詳細的講解,只是大致介紹函數的作用,其中實現的細節注釋中有解釋,網上的這個源碼有點亂,如果想看比較整齊的源碼或者視頻的可以QQ聯系我(免費)(找共同學習的伙伴)
▓▓▓▓▓▓ 思路
這個小游戲可以抽象化分為3層(我覺得這樣能更好理解)
◆最底下的一層是基本的樣式(可見的)
◆中間的層是最主要的,是一個4x4的二維數組,游戲中我們都是對這個二維數組進行操作(不可見的)
◆最上面的一層也是一個4x4的二維數組,它只是根據第二層數組的每個數顯示樣式(可見的)
我們通過最底下的一層顯示最基本的16個小方格,通過鍵盤的按鍵或者手指在屏幕的滑動來操作中間層的數組,最后在通過最上面的一層顯示出數字
▓▓▓▓▓▓ 基本結構與樣式
基本的結構和樣式都挺簡單,直接看代碼
結構:

1 <div id="test2048"> 2 <div id="header"> 3 <h1>2048</h1> 4 <a href="javascript:newgame()" >開始新的游戲</a> 5 <p>分數:<span id="score">0</span></p> 6 </div> 7 <div id="container"> 8 <div class="cell" id="cell-0-0"></div> 9 <div class="cell" id="cell-0-1"></div> 10 <div class="cell" id="cell-0-2"></div> 11 <div class="cell" id="cell-0-3"></div> 12 <div class="cell" id="cell-1-0"></div> 13 <div class="cell" id="cell-1-1"></div> 14 <div class="cell" id="cell-1-2"></div> 15 <div class="cell" id="cell-1-3"></div> 16 <div class="cell" id="cell-2-0"></div> 17 <div class="cell" id="cell-2-1"></div> 18 <div class="cell" id="cell-2-2"></div> 19 <div class="cell" id="cell-2-3"></div> 20 <div class="cell" id="cell-3-0"></div> 21 <div class="cell" id="cell-3-1"></div> 22 <div class="cell" id="cell-3-2"></div> 23 <div class="cell" id="cell-3-3"></div> 24 </div> 25 </div>
樣式:

1 *{ 2 margin: 0; 3 padding: 0; 4 } 5 #test2048{ 6 font-family: Arial; 7 margin: 0 auto; 8 text-align: center; 9 } 10 #header{ 11 margin: 20px; 12 } 13 #header a{ 14 font-family: Arial; 15 text-decoration: none; 16 display: block; 17 color: white; 18 margin: 20px auto; 19 width: 125px; 20 height: 35px; 21 text-align: center; 22 line-height: 40px; 23 background-color: #8f7a66; 24 border-radius: 10px; 25 font-size: 15px; 26 } 27 #header p{ 28 font-family: Arial; 29 font-size: 20px; 30 } 31 #container{ 32 width: 460px; 33 height: 460px; 34 background-color: #bbada0; 35 margin: 0 auto; 36 border-radius: 10px; 37 position: relative; 38 padding: 20px; 39 } 40 .cell{ 41 width: 100px; 42 height: 100px; 43 border-radius: 6px; 44 background-color: #ccc0b3; 45 position: absolute; 46 }
從CSS樣式可以看出,我們並沒有對每個格子的位置進行設置,因為如果用CSS給每個格子設置樣式代碼量太大,而且他們的位置有一定的規律,所以我們可以用js循環來完成每個格子樣式的設置
代碼:

1 // 初始化棋盤格 2 function initialize(){ 3 for(var i=0;i<4;i++){ 4 for(var j=0;j<4;j++){ 5 // 設置棋盤格的位置 6 var everyCell = $('#cell-'+ i +'-'+ j); 7 everyCell.css({top:getPos(i),left:getPos(j)}); 8 } 9 } 10 }

1 // 獲取位置 2 function getPos(num){ 3 return 20 + num*120; 4 }
這樣我們的第一層就好了
效果:
現在構造第二層,即構建一個4x4的值全部為0的數組,由於在構造第二層時,有兩層循環,所以我們可以在構造第一層時也能構造第二層
第三層是用js生成16個格子,它和第一層的16個格子一一對應
代碼:

1 // 數字格 2 function numFormat(){ 3 for(var i=0;i<4;i++){ 4 for(var j=0;j<4;j++){ 5 $('#container').append('<div class="number" id="number-'+ i +'-'+ j +'"></div>') 6 7 // 設置數字格的位置,樣式 8 var everyNumber = $('#number-'+ i +'-'+ j); 9 if(checkerboard[i][j] == 0){ 10 everyNumber.css({ 11 width:'0px', 12 height:'0px', 13 top:getPos(i) + 50, 14 left:getPos(j) + 50 15 }) 16 }else{ 17 everyNumber.css({ 18 width:'100px', 19 height:'100px', 20 top:getPos(i), 21 left:getPos(j), 22 backgroundColor:getBackgroundColor(checkerboard[i][j]), 23 color:getColor(checkerboard[i][j]) 24 }); 25 everyNumber.text(checkerboard[i][j]); 26 } 27 } 28 } 29 }

1 // 獲取相應數字的背景顏色 2 function getBackgroundColor(number){ 3 4 switch (number) { 5 case 2:return "#eee4da";break; 6 case 4:return "#ede0c8";break; 7 case 8:return "#f2b179";break; 8 case 16:return "#f59563";break; 9 case 32:return "#f67c5f";break; 10 case 64:return "#f65e3b";break; 11 case 128:return "#edcf72";break; 12 case 256:return "#edcc61";break; 13 case 512:return "#9c0";break; 14 case 1024:return "#33b5e5";break; 15 case 2048:return "#09c";break; 16 case 4096:return "#a6c";break; 17 case 8192:return "#93c";break; 18 } 19 }

1 // 設置相應數字的文字顏色 2 function getColor(number){ 3 if (number <= 4) { 4 return "#776e65" 5 } 6 return "white"; 7 }
▓▓▓▓▓▓ 初始化
在每次游戲重新開始時,都會在隨機的位置出現兩個隨機的數字,我們寫一個在隨機位置出現一個隨機數的函數,只要調用兩次就可以實現了
代碼:

1 // 隨機的在一個位置上產生一個數字 2 function randomNum(){ 3 // 隨機產生一個坐標值 4 var randomX = Math.floor(Math.random() * 4); 5 var randomY = Math.floor(Math.random() * 4); 6 7 // 隨機產生一個數字(2或4) 8 var randomValue = Math.random() > 0.5 ? 2 : 4; 9 10 // 在數字格不為0的地方生成一個隨機數字 11 while(true){ 12 if(checkerboard[randomX][randomY] == 0){ 13 break; 14 }else{ 15 16 var randomX = Math.floor(Math.random() * 4); 17 var randomY = Math.floor(Math.random() * 4); 18 } 19 } 20 21 // 將隨機產生的數字顯示在隨機的位置上 22 checkerboard[randomX][randomY] = randomValue; 23 24 // 動畫 25 randomNumAnimate(randomX,randomY,randomValue); 26 }

1 // 隨機產生數字的動畫 2 function randomNumAnimate(randomX,randomY,randomValue){ 3 var randomnum = $('#number-'+ randomX +'-'+ randomY); 4 randomnum.css({ 5 backgroundColor:getBackgroundColor(randomValue), 6 color:getColor(randomValue), 7 }) 8 .text(randomValue) 9 .animate({ 10 width:'100px', 11 height:'100px', 12 top:getPos(randomX), 13 left:getPos(randomY) 14 },50); 15 }
▓▓▓▓▓▓ 基本操作
我們通過switch循環,來根據用戶不同的輸入進行不同的操作
代碼:

1 // 獲取鍵盤事件,檢測不同的按鍵進行不同的操作 2 $(document).keydown(function(event){ 3 switch(event.keyCode){ 4 case 37://左 5 if(canMoveLeft(checkerboard)){ 6 // 如果可以向左移動 7 8 MoveLeft(); 9 // 向左移動 10 11 12 setTimeout(function(){ 13 randomNum(); 14 },200); 15 // 隨機產生一個數字 16 } 17 break; 18 case 38://上 19 if(canMoveUp(checkerboard)){ 20 // 如果可以向上移動 21 22 MoveUp(); 23 // 向上移動 24 25 setTimeout(function(){ 26 randomNum(); 27 },200); 28 // 隨機產生一個數字 29 } 30 break; 31 case 39://右 32 if(canMoveRight(checkerboard)){ 33 // 如果可以向右移動 34 35 MoveRight(); 36 // 向右移動 37 38 setTimeout(function(){ 39 randomNum(); 40 },200); 41 // 隨機產生一個數字 42 } 43 break; 44 case 40://下 45 if(canMoveDown(checkerboard)){ 46 // 如果可以向下移動 47 48 MoveDown(); 49 // 向下移動 50 51 setTimeout(function(){ 52 randomNum(); 53 },200); 54 // 隨機產生一個數字 55 } 56 break; 57 default: 58 break; 59 } 60 });
由於數字格的移動只有左、上、右、下四種方式,並且他們都是大同小異的,所以就拿向左移動為例,
向左移動,我們首先需要判斷它是否能向左移動,能向左移動有兩種情況
第一種:當前格子的左邊的格子是空的即值為0
第二種:當前格子的值和左邊格子的值相同
由於向左移動,所以第一列的格子不可能向左移動,所以不需要判斷
代碼:

1 // 判斷是否可以向左移動 2 function canMoveLeft(checkerboard){ 3 for(var i=0;i<4;i++){ 4 for(var j=1;j<4;j++){ 5 if(checkerboard[i][j] != 0){ 6 // 如果這個數字格它左邊的數字格為空或者左邊的數字格和它相等,則可以向左移動 7 if(checkerboard[i][j-1] == 0 || checkerboard[i][j] == checkerboard[i][j-1]){ 8 return true; 9 } 10 } 11 } 12 } 13 return false; 14 }
判斷能否向左移動后,我們就要對可以移動的格子進行移動,這里需要特別注意,向哪個方向移動就要先從哪個方向開始判斷
代碼:

1 // 向左移動 2 function MoveLeft(){ 3 for(var i=0;i<4;i++){ 4 for(var j=1;j<4;j++){ 5 if(checkerboard[i][j] != 0){ 6 for(var k=0;k<j;k++){ 7 if(checkerboard[i][k] == 0 && noMiddleNumRow(i,k,j,checkerboard)){ 8 moveAnimation(i,j,i,k); 9 checkerboard[i][k] = checkerboard[i][j]; 10 checkerboard[i][j] = 0; 11 }else if(checkerboard[i][k] == checkerboard[i][j] && noMiddleNumRow(i,k,j,checkerboard) && !hasConflicted[i][k]){ 12 moveAnimation(i,j,i,k); 13 checkerboard[i][k] += checkerboard[i][j]; 14 checkerboard[i][j] = 0; 15 16 } 17 } 18 } 19 } 20 } 21 // 設置刷新的時間是為了讓運動的動畫走完在進行更新數字格,否則數字格運動的動畫將會被打斷 22 setTimeout(function(){ 23 numFormat(); 24 },200); 25 }

1 // 判斷中間的數字格是否為0(行) 2 function noMiddleNumRow(row,col1,col2,checkerboard){ 3 for(var i=col1+1;i<col2;i++){ 4 if(checkerboard[row][i] != 0){ 5 return false; 6 } 7 } 8 return true; 9 }
將上、右、下四個方向寫完以后,游戲基本的操作就已經完成了。
▓▓▓▓▓▓ 游戲分數和判斷游戲結束
游戲的分數是每個相加的數的和,所以我們在每個數相加的時候更新分數就可以了
代碼:

1 // 更新分數 2 score += checkerboard[k][j]; 3 updateScore(score);

1 // 設置分數 2 function updateScore(num){ 3 $('#score').text(num); 4 }
判斷游戲是否結束很簡單,用我們之前定義的方法就可以實現
代碼:

1 // 判斷游戲是否結束 2 function wheGameOver(checkerboard){ 3 if(!canMoveLeft(checkerboard) && !canMoveUp(checkerboard) && !canMoveRight(checkerboard) && !canMoveDown(checkerboard) ){ 4 showGameOver(); 5 } 6 }

1 // 顯示游戲結束 2 function showGameOver(){ 3 $('#container').append("<div id='gameover'><p>最終得分</p><span>"+ score +"</span><a href='javascript:resert();'>重新開始游戲</a></div> ") 4 } 5 6 // 重新開始游戲 7 function resert(){ 8 $('#gameover').remove(); 9 newgame(); 10 }
▓▓▓▓▓▓ 最后優化
1、游戲中會出現一次移動,一個數會被累加很多次
在原游戲中,每個數在每次操作中只能累加一次,所以我們在定義一個4x4的值為false的數組,與中間層的數組一一對應,專門用來防止一個數的多次累加,如果是false則可以累加,並將值改為false,否則不可以累加
2、結束死循環
由於在設置隨機數的時候用到了一個死循環,但是在游戲結束后,該循環還在,所以我們在死循環中在添加一個條件,如果游戲結束就跳出循環
3、最后的結束游戲提示不執行

1 case 37://左 2 if(canMoveLeft(checkerboard)){ 3 // 如果可以向左移動 4 5 MoveLeft(); 6 // 向左移動 7 8 setTimeout(function(){ 9 wheGameOver(checkerboard) 10 },300); 11 // 判斷游戲是否結束,這里設置延時是因為要等到隨機產生數字后再進行判斷,如果不加 12 // 延時,則最后一次的判斷因為canMoveLeft(checkerboard)為false就不會再執行了 13 14 setTimeout(function(){ 15 randomNum(); 16 },200); 17 // 隨機產生一個數字 18 } 19 break;
從代碼中可以看出,判斷游戲是否結束是在隨機產生一個數字前執行的,所以在判斷游戲結束時,總是有一個空的格子,所以代碼執行后認為游戲沒有結束,但是當這個隨機數字產生后,所有的格子不能移動,當我們按鍵時,if條件不通過,判斷游戲是否結束的函數不能執行。所以我們要給判斷游戲結束的函數設置定時器,讓他在隨機產生一個數字后再進行判斷
4、在移動端可以執行
由於原作者沒有寫有關移動端的操作,所以我在網上找的判斷移動端觸屏手機滑動位置的代碼,加入了游戲的事件就可以執行了

1 //返回角度 2 function GetSlideAngle(dx, dy) { 3 return Math.atan2(dy, dx) * 180 / Math.PI; 4 } 5 6 //根據起點和終點返回方向 1:向上,2:向下,3:向左,4:向右,0:未滑動 7 function GetSlideDirection(startX, startY, endX, endY) { 8 var dy = startY - endY; 9 var dx = endX - startX; 10 varresult = 0; 11 12 //如果滑動距離太短 13 if(Math.abs(dx) < 2 && Math.abs(dy) < 2) { 14 returnresult; 15 } 16 17 var angle = GetSlideAngle(dx, dy); 18 if(angle >= -45 && angle < 45) { 19 result = 4; 20 }else if (angle >= 45 && angle < 135) { 21 result = 1; 22 }else if (angle >= -135 && angle < -45) { 23 result = 2; 24 } 25 else if ((angle >= 135 && angle <= 180) || (angle >= -180 && angle < -135)) { 26 result = 3; 27 } 28 29 return result; 30 } 31 32 //滑動處理 33 var startX, startY; 34 document.addEventListener('touchstart',function (ev) { 35 startX = ev.touches[0].pageX; 36 startY = ev.touches[0].pageY; 37 }, false); 38 document.addEventListener('touchend',function (ev) { 39 var endX, endY; 40 endX = ev.changedTouches[0].pageX; 41 endY = ev.changedTouches[0].pageY; 42 var direction = GetSlideDirection(startX, startY, endX, endY); 43 switch(direction) { 44 case 0: 45 //沒滑動 46 break; 47 case 1: 48 if(canMoveUp(checkerboard)){ 49 // 如果可以向上移動 50 51 MoveUp(); 52 // 向上移動 53 54 setTimeout(function(){ 55 wheGameOver(checkerboard) 56 },300); 57 // 判斷游戲是否結束 58 59 setTimeout(function(){ 60 randomNum(); 61 },200); 62 // 隨機產生一個數字 63 } 64 break; 65 case 2: 66 if(canMoveDown(checkerboard)){ 67 // 如果可以向下移動 68 69 MoveDown(); 70 // 向下移動 71 72 setTimeout(function(){ 73 wheGameOver(checkerboard) 74 },300); 75 // 判斷游戲是否結束 76 77 setTimeout(function(){ 78 randomNum(); 79 },200); 80 // 隨機產生一個數字 81 } 82 break; 83 case 3: 84 if(canMoveLeft(checkerboard)){ 85 // 如果可以向左移動 86 87 MoveLeft(); 88 // 向左移動 89 90 setTimeout(function(){ 91 wheGameOver(checkerboard) 92 },300); 93 // 判斷游戲是否結束,這里設置延時是因為要等到隨機產生數字后再進行判斷,如果不加 94 // 延時,則最后一次的判斷因為canMoveLeft(checkerboard)為false就不會再執行了 95 96 setTimeout(function(){ 97 randomNum(); 98 },200); 99 // 隨機產生一個數字 100 } 101 break; 102 case 4: 103 if(canMoveRight(checkerboard)){ 104 // 如果可以向右移動 105 106 MoveRight(); 107 // 向右移動 108 109 setTimeout(function(){ 110 wheGameOver(checkerboard) 111 },300); 112 // 判斷游戲是否結束 113 114 setTimeout(function(){ 115 randomNum(); 116 },200); 117 // 隨機產生一個數字 118 } 119 break; 120 default: 121 } 122 }, false);
▓▓▓▓▓▓ 總結
總體來說這個游戲實現起來並不是太難,就是許多小的操作集合起來
如果想看視頻或者源碼請QQ聯系我