由於最近在百度IFE看到有2048任務,所以昨天興趣一來自己也做了一個。大概花了五個小時完成,不過不足之處是操作時沒有滑動效果。昨晚新增了手機版本,流暢度還可以,不過由於沒有滑動,游戲過程顯得很突兀啊,且容我下次再加上吧。下面先講講這個游戲怎么實現,這是個人想的方法。不足之處,多多指教啊。
首先我在做這個小游戲的時候我想到了兩種方法:第一種方法就是本例用到的方法,利用方向鍵操作,只改變相應DIV塊的背景以及更改文字,其特點是16個DIV的位置是固定不變的;第二種方法就是通過定位來實現,操作方向鍵/滑動屏幕時改變left/top值,這種方法的好處是更容易做滑動效果,不過需要多建一個DIV層或者加背景。時間關系,目前我只用了第一種。
1、界面與樣式
PC端:HTML內容很簡單,直接使用兩個DIV包裹16個DIV即可;而CSS的話wrap及其它DIV都可以使用固定值,放數字的DIV先寫統一的樣式,每個數字DIV都預寫一種特定class的樣式。最后再加一個動畫,就是2出來后的放大效果。為了不影響布局,我采用的是CSS3的transform:scale3d動畫,而不是通過改變大小來實現動畫。
移動端:跟PC端的相比,這個的HTML頁面只是比上面的增加了一張img背景圖;而CSS方面則有較大差異,全部采用百分比布局,高度由背景圖1:1撐開,由於高度不確定,加載完頁面后需要使用js獲取小DIV的寬度,並賦給DIV一個相同的行高,以使文字垂直居中。
HTML代碼

1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8" /> 5 <title>2048游戲</title> 6 <link rel="stylesheet" type="text/css" href="css/main.css"/> 7 <script type="text/javascript" src="js/main.js"></script> 8 </head> 9 <body> 10 <div id="wrap"> 11 <div id="inner"> 12 <div></div> 13 <div></div> 14 <div></div> 15 <div></div> 16 17 <div></div> 18 <div></div> 19 <div></div> 20 <div></div> 21 22 <div></div> 23 <div></div> 24 <div></div> 25 <div></div> 26 27 <div></div> 28 <div></div> 29 <div></div> 30 <div></div> 31 </div> 32 </div> 33 </body> 34 </html>

1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8" /> 5 <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /> 6 <title>2048游戲</title> 7 <link rel="stylesheet" type="text/css" href="css/main.css"/> 8 <script src="js/touch.js" type="text/javascript" charset="utf-8"></script> 9 </head> 10 <body> 11 <div id="wrap"> 12 <!--背景圖--> 13 <div id="bgPic"> 14 <img src="img/bg2.png"/> 15 </div> 16 <div id="inner"> 17 <div></div> 18 <div></div> 19 <div></div> 20 <div></div> 21 22 <div></div> 23 <div></div> 24 <div></div> 25 <div></div> 26 27 <div></div> 28 <div></div> 29 <div></div> 30 <div></div> 31 32 <div></div> 33 <div></div> 34 <div></div> 35 <div></div> 36 </div> 37 </div> 38 <div id="text">提示:通過滑動屏幕操作游戲。</div> 39 <script src="js/main.js" type="text/javascript" charset="utf-8"></script> 40 <script type="text/javascript"> 41 var inner = document.getElementById("inner"); 42 var divs = inner.children; 43 var divW = divs[0].offsetWidth; 44 for(var i=0;i< 16;i++){ 45 divs[i].style.lineHeight = divW + "px"; 46 } 47 </script> 48 </body> 49 </html>
CSS代碼

1 @charset "utf-8"; 2 3 body,h1,h2,h3,h4,p,dl,dd,ul,ol,form,input,textarea,th,td,select{margin: 0;padding: 0;} 4 em{font-style: normal;} 5 li{list-style: none;} 6 a{text-decoration: none;} 7 img{border: none;vertical-align: top;margin: 0;} 8 table{border-collapse: collapse;} 9 input,textarea{outline: none;} 10 textarea{resize:none;overflow: auto;} 11 body{font-size:12px;font-family: arial;} 12 13 #wrap{ 14 width: 492px; 15 height: 492px; 16 margin: 30px auto; 17 background: #b8af9e; 18 border-radius: 10px; 19 padding: 5px; 20 } 21 #inner{ 22 width: 480px; 23 height: 480px; 24 overflow: hidden; 25 } 26 #inner div{ 27 width: 106px; 28 height: 106px; 29 margin-left: 14px; 30 margin-top: 14px; 31 background: #ccc0b2; 32 float: left; 33 font-size: 50px; 34 line-height: 106px; 35 text-align: center; 36 font-weight: bold; 37 } 38 #inner .num2{ 39 color: #7c736a; 40 background: #eee4da; 41 } 42 #inner .num4{ 43 color: #7c736a; 44 background: #ece0c8; 45 } 46 #inner .num8{ 47 color: #fff7eb; 48 background: #f2b179; 49 } 50 #inner .num16{ 51 color: #fff7eb; 52 background: #f59563; 53 } 54 #inner .num32{ 55 color: #FFF7EB; 56 background: #f57c5f; 57 } 58 #inner .num64{ 59 color: #FFF7EB; 60 background: #f65d3b; 61 } 62 #inner .num128{ 63 color: #FFF7EB; 64 background: #edce71; 65 } 66 #inner .num256{ 67 color: #FFF7EB; 68 background: #edcc61; 69 } 70 #inner .num512{ 71 color: #FFF7EB; 72 background: #ecc850; 73 } 74 #inner .num1024{ 75 font-size: 46px; 76 color: #FFF7EB; 77 background: #edc53f; 78 } 79 #inner .num2048{ 80 font-size: 46px; 81 color: #FFF7EB; 82 background: #eec22e; 83 } 84 #inner .num4096{ 85 font-size: 46px; 86 color:#FFF7EB ; 87 background: #3d3a33; 88 } 89 90 /*num2動畫*/ 91 .animate{ 92 -webkit-animation: pulse 0.3s both; 93 animation: pulse 0.3s both; 94 } 95 @-webkit-keyframes pulse { 96 0% { 97 -webkit-transform: scale3d(1, 1, 1); 98 transform: scale3d(1, 1, 1); 99 } 100 101 50% { 102 -webkit-transform: scale3d(1.1, 1.1, 1.1); 103 transform: scale3d(1.1, 1.1, 1.1); 104 } 105 106 100% { 107 -webkit-transform: scale3d(1, 1, 1); 108 transform: scale3d(1, 1, 1); 109 } 110 } 111 112 @keyframes pulse { 113 0% { 114 -webkit-transform: scale3d(1, 1, 1); 115 -ms-transform: scale3d(1, 1, 1); 116 transform: scale3d(1, 1, 1); 117 } 118 119 50% { 120 -webkit-transform: scale3d(1.1, 1.1, 1.1); 121 -ms-transform: scale3d(1.1, 1.1, 1.1); 122 transform: scale3d(1.1, 1.1, 1.1); 123 } 124 125 100% { 126 -webkit-transform: scale3d(1, 1, 1); 127 -ms-transform: scale3d(1, 1, 1); 128 transform: scale3d(1, 1, 1); 129 } 130 }

1 @charset "utf-8"; 2 3 body,h1,h2,h3,h4,p,dl,dd,ul,ol,form,input,textarea,th,td,select{margin: 0;padding: 0;} 4 em{font-style: normal;} 5 li{list-style: none;} 6 a{text-decoration: none;} 7 img{border: none;vertical-align: top;margin: 0;padding: 0;} 8 table{border-collapse: collapse;} 9 input,textarea{outline: none;} 10 textarea{resize:none;overflow: auto;} 11 body{font-size:12px;font-family: arial;} 12 13 body,html{ 14 width: 100%; 15 height: 100%; 16 } 17 #wrap{ 18 position: relative; 19 width: 94%; 20 left: 3%; 21 top: 5%; 22 background: #b8af9e; 23 border-radius: 20px; 24 } 25 #bgPic{ 26 width: 100%; 27 border-radius: 20px; 28 } 29 #bgPic img{ 30 width: 100%; 31 } 32 #inner{ 33 width: 93.6%; 34 height: 93.6%; 35 left: 3.2%; 36 top: 3.2%; 37 position: absolute; 38 } 39 40 #inner div{ 41 position: absolute; 42 width: 22.435897%; 43 height: 22.435897%; 44 background: #eee4da; 45 font-size: 4em; 46 line-height: 1.2; 47 text-align: center; 48 font-weight: bold; 49 } 50 #inner div:nth-child(1){ 51 left: 0; 52 top: 0; 53 } 54 #inner div:nth-child(2){ 55 left: 25.854701%; 56 top: 0; 57 } 58 #inner div:nth-child(3){ 59 left: 51.709402%; 60 top: 0; 61 } 62 #inner div:nth-child(4){ 63 right: 0; 64 top: 0; 65 } 66 #inner div:nth-child(5){ 67 left: 0; 68 top: 25.854701%; 69 } 70 #inner div:nth-child(6){ 71 left: 25.854701%; 72 top: 25.854701%; 73 } 74 #inner div:nth-child(7){ 75 left: 51.709402%; 76 top: 25.854701%; 77 } 78 #inner div:nth-child(8){ 79 right: 0; 80 top: 25.854701%; 81 } 82 #inner div:nth-child(9){ 83 left: 0; 84 top: 51.709402%; 85 } 86 #inner div:nth-child(10){ 87 left: 25.854701%; 88 top: 51.709402%; 89 } 90 #inner div:nth-child(11){ 91 left: 51.709402%; 92 top: 51.709402%; 93 } 94 #inner div:nth-child(12){ 95 right: 0; 96 top: 51.709402%; 97 } 98 #inner div:nth-child(13){ 99 left: 0; 100 bottom: 0; 101 } 102 #inner div:nth-child(14){ 103 left: 25.854701%; 104 bottom: 0; 105 } 106 #inner div:nth-child(15){ 107 left: 51.709402%; 108 bottom: 0; 109 } 110 #inner div:nth-child(16){ 111 right: 0; 112 bottom: 0; 113 } 114 115 116 #inner .num2{ 117 color: #7c736a; 118 background: #eee4da; 119 } 120 #inner .num4{ 121 color: #7c736a; 122 background: #ece0c8; 123 } 124 #inner .num8{ 125 color: #fff7eb; 126 background: #f2b179; 127 } 128 #inner .num16{ 129 color: #fff7eb; 130 background: #f59563; 131 } 132 #inner .num32{ 133 color: #FFF7EB; 134 background: #f57c5f; 135 } 136 #inner .num64{ 137 color: #FFF7EB; 138 background: #f65d3b; 139 } 140 #inner .num128{ 141 color: #FFF7EB; 142 background: #edce71; 143 font-size: 2.8em; 144 } 145 #inner .num256{ 146 color: #FFF7EB; 147 background: #edcc61; 148 font-size: 2.8em; 149 } 150 #inner .num512{ 151 color: #FFF7EB; 152 background: #ecc850; 153 font-size: 2.8em; 154 } 155 #inner .num1024{ 156 font-size: 2em; 157 color: #FFF7EB; 158 background: #edc53f; 159 } 160 #inner .num2048{ 161 font-size: 2em; 162 color: #FFF7EB; 163 background: #eec22e; 164 } 165 #inner .num4096{ 166 font-size: 2em; 167 color:#FFF7EB ; 168 background: #3d3a33; 169 } 170 #text{ 171 position: relative; 172 width: 100%; 173 height: 30px; 174 font-size: 18px; 175 color: #7C736A; 176 text-align: center; 177 line-height: 30px; 178 margin: 0 auto; 179 top: 8%; 180 } 181 /*num2動畫*/ 182 .animate{ 183 -webkit-animation: pulse 0.2s both; 184 animation: pulse 0.2s both; 185 } 186 @-webkit-keyframes pulse { 187 0% { 188 -webkit-transform: scale3d(1, 1, 1); 189 transform: scale3d(1, 1, 1); 190 } 191 192 50% { 193 -webkit-transform: scale3d(1.1, 1.1, 1.1); 194 transform: scale3d(1.1, 1.1, 1.1); 195 } 196 197 100% { 198 -webkit-transform: scale3d(1, 1, 1); 199 transform: scale3d(1, 1, 1); 200 } 201 } 202 203 @keyframes pulse { 204 0% { 205 -webkit-transform: scale3d(1, 1, 1); 206 -ms-transform: scale3d(1, 1, 1); 207 transform: scale3d(1, 1, 1); 208 } 209 210 50% { 211 -webkit-transform: scale3d(1.1, 1.1, 1.1); 212 -ms-transform: scale3d(1.1, 1.1, 1.1); 213 transform: scale3d(1.1, 1.1, 1.1); 214 } 215 216 100% { 217 -webkit-transform: scale3d(1, 1, 1); 218 -ms-transform: scale3d(1, 1, 1); 219 transform: scale3d(1, 1, 1); 220 } 221 }
2、功能模塊
PC端&移動端:
1 var inner = document.getElementById("inner"); 2 var divs = inner.children; 3 var Len = divs.length;//塊數量 4 //初始化標記 5 for(var i=0;i< Len;i++){ 6 divs[i].index = 0; 7 } 8 //隨機數2 9 function addRan(){ 10 var ran = Math.floor(Math.random()*Len); 11 var zero = 0;//判斷空位 12 for(var i=0;i<Len;i++){ 13 if(divs[i].index==0){ 14 zero++; 15 } 16 } 17 //占滿 18 if(zero==0){ 19 return; 20 } 21 //空位 22 if(divs[ran].index==0){ 23 divs[ran].innerHTML = 2; 24 divs[ran].className = "num2 animate"; 25 divs[ran].index = 2; 26 setTimeout(function(){ 27 divs[ran].className = "num2"; 28 },100); 29 }else{ 30 addRan(); 31 } 32 return; 33 } 34 //初始隨機兩個 35 addRan(); 36 addRan();
解釋:首先給16數字DIV賦一個初值的屬性值,即div[i].index = 0;創建一個隨機函數,先判斷空位數,數量大於0即生成;接着判斷空位的標記值,如果不等於0則遞歸執行,重新生成一個隨機數,直到符合條件。
1 //相加 2 function add(k,p){ 3 if(divs[k].index==divs[k+p].index && divs[k+p].index !=0){ 4 divs[k].innerHTML *=2; 5 divs[k].index *=2; 6 divs[k].className = "num" + divs[k].index; 7 divs[k+p].innerHTML = ""; 8 divs[k+p].className = ""; 9 divs[k+p].index = 0; 10 } 11 } 12 //移動 13 function move(k,p){ 14 if(divs[k].index==0 && divs[k+p].index !=0){ 15 divs[k].innerHTML = divs[k+p].innerHTML; 16 divs[k].className = divs[k+p].className; 17 divs[k+p].innerHTML = ""; 18 divs[k+p].className = ""; 19 divs[k].index = divs[k+p].index; 20 divs[k+p].index = 0; 21 } 22 }
解釋:
k代表的是存放相加結果或者移動到達后的div下標,而k+p則是相鄰的div的下標。根據方向不一樣,p可取±1(左右)和±4(上下)。
相加條件成立時,即相鄰兩個div的值相等且其中一個不為零時,則把它們相加。然后K位的值自身乘2,標記值同步乘2,class改為相應的名;而被相加的DIV則清空內容,class名置空,index值置0。
移動條件成立時,即相鄰兩個div一個標記為0而另一個不為0時,則向k位移動。移動時先將鄰位的值賦給它,對應的class名也賦給它,然后將自身class和值都置空。最后給修改它們的標記值,K位等於鄰位的標記,鄰位標記置0。
1 //左移 2 function moveLeft(){ 3 function left(){ 4 for(var i=0;i<13;i+=4){ 5 //重復三次 6 for(var t=0;t<3;t++){ 7 for(var j=0;j<3;j++){ 8 move(i+j,1); 9 } 10 } 11 } 12 } 13 left(); 14 //檢測相等 15 for(var i=0;i<13;i+=4){ 16 for(var j=0;j<3;j++){ 17 add(i+j,1); 18 } 19 } 20 //重排 21 left(); 22 }

1 //上移 2 function moveUp(){ 3 function up(){ 4 for(var i=0;i<4;i++){ 5 for(var t=0;t<3;t++){ 6 for(var j =0; j<12; j+=4){ 7 move(i+j,4); 8 } 9 } 10 } 11 } 12 up(); 13 for(var i=0;i<4;i++){ 14 for(var t=0;t<12;t+=4){ 15 add(i+t,4) 16 } 17 } 18 //加完重排 19 up(); 20 } 21 //右移 22 function moveRight(){ 23 function right(){ 24 for(var i=0;i<13;i+=4){ 25 for(var t=0;t<3;t++){//重復三次檢測 26 for(var j=3;j>0;j--){ 27 move(i+j,-1); 28 } 29 } 30 } 31 } 32 right(); 33 //檢測相加 34 for(var i=0;i<13;i+=4){ 35 for(var t=3;t>0;t--){ 36 add(i+t,-1); 37 } 38 } 39 right(); 40 } 41 //下移 42 function moveDown(){ 43 function down(){ 44 for(var i=0;i<4;i++){ 45 for(var t=0;t<3;t++){//重復三次檢測 46 for(var j=12;j>0;j-=4){ 47 move(i+j,-4); 48 } 49 } 50 } 51 } 52 down(); 53 //相加 54 for(var i=0;i<4;i++){ 55 for(var t=12;t>0;t-=4){ 56 add(i+t,-4); 57 } 58 } 59 down(); 60 }
根據移動的是水平還是垂直方向,判斷先行后列還是先列后行的順序,循環執行move函數。移動完后檢查相鄰的數字是否相等,遍歷所有div並將符合條件的相加。由於位置改變了,所以最后還要移動重排一次,等待下一步操作。重復三次的for循環是為了檢測同一行或同一列內,是否所有的空位都移動完了。上面幾個函數代碼還可以進一步復用,為了更明了我暫時就沒融合它們了,過兩天幾天更新我在試試。
PC端:
1 //方向鍵 2 document.onkeydown = function (e){ 3 var e = e || window.event; 4 switch(e.keyCode){ 5 //左 6 case 37: 7 moveLeft(); 8 addRan(); //產生隨機的2 9 return false;//取消方向鍵的默認事件,下同 10 break; 11 //上 12 case 38: 13 moveUp(); 14 addRan(); 15 return false; 16 break; 17 //右 18 case 39: 19 moveRight(); 20 addRan(); 21 return false; 22 break; 23 //下 24 case 40: 25 moveDown(); 26 addRan(); 27 return false; 28 break; 29 } 30 }
移動端:
1 //滑動觸摸屏幕 2 var target = document.body; 3 //取消默認事件 4 touch.on(target,"touchstart",function(ev){ 5 ev.preventDefault(); 6 }) 7 //左 8 touch.on(target,"swipeleft",function(ev){ 9 moveLeft(); 10 addRan(); 11 }); 12 //上 13 touch.on(target,"swipeup",function(ev){ 14 moveUp(); 15 addRan(); 16 }); 17 //右 18 touch.on(target,"swiperight",function(ev){ 19 moveRight(); 20 addRan(); 21 }); 22 //下 23 touch.on(target,"swipedown",function(ev){ 24 moveDown(); 25 addRan(); 26 });
解釋:PC版的也可以用事件監聽方法,不過都要記得取消方向鍵的默認事件,不然可能會移動滾動條。注意,移動版的我使用了百度的touch.js手勢庫(http://touch.code.baidu.com/),所以記得引入touch.js文件。整個小游戲就大概這幾個函數了,最后把它們一起放置在window.onload里面執行就好了。
游戲地址:
PC端:www.chengguanhui.com/demos/2048
手機端:www.chengguanhui.com/demos/2048_mobile
說明:原創文章,有錯之處,望多指教。本文僅供學習與交流,轉載時請注明出處。謝謝。