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