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

  说明:原创文章,有错之处,望多指教。本文仅供学习与交流,转载时请注明出处。谢谢。

posted @ 2015-08-01 14:12  小辉_Ray  阅读(2993)  评论(6编辑  收藏  举报