IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    2048游戏详解 - 小辉_Ray

    小辉_Ray发表于 2015-08-01 06:12:00
    love 0

      由于最近在百度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游戏详解,转载请注明。



沪ICP备19023445号-2号
友情链接