2048游戲詳解


  由於最近在百度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>
PC端
 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 }
PC端
  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

  說明:原創文章,有錯之處,望多指教。本文僅供學習與交流,轉載時請注明出處。謝謝。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM