由于最近在百度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
说明:原创文章,有错之处,望多指教。本文仅供学习与交流,转载时请注明出处。谢谢。

本文链接:2048游戏详解,转载请注明。