2048这个游戏是通过对二维数组的操作来实现的,其算法核心如下:

  (以一行左移为例)

    c从0开始,遍历当前行中的元素,到<CN-1(CN是一个常量,表示的是游戏格子的列数)结束,每次+1

    找到当前位置下一个不为0的位置

    如果没找到

      直接退出循环

    否则

      如果当前值等于0

        将下一位置的值与当前位置的值交换

        将下一位置设为0

        将c-1(为了让下次循环依然检查当前位置)

       否则,如果当前值等于下一个值

        将当前值 * 2

        下一位置值设为0

具体代码如下

 1 <!-- 2048.html -->
 2 <!DOCTYPE html>
 3 <html lang="en">
 4 <head>
 5     <meta charset="UTF-8">
 6     <title>2048</title>
 7     <link rel="stylesheet" href="2048.css">
 8     <script src="2048.js"></script>
 9 </head>
10 <body>
11     <!-- 分数 -->
12     <p>Score:<span id="score">0</span></p>
13     
14     <!-- 实现游戏的网格 -->
15     <div id="gridPanel">
16     </div>    
17 
18     <div id="gameOver"><!--同时包含前景和背景的容器-->
19         <div><!--半透明灰色背景--></div>
20          <p><!--居中小窗口-->
21              Game Over!<br>
22              Score:<span id="finalScore"></span><br>
23               <a class="button" onclick="game.start()">Try again!</a>
24         </p>
25     </div>
26 </body>
27 </html>

 

  1 /*2048.css*/
  2  @charset "utf-8";
  3 
  4 
  5 #gridPanel{
  6     width: 480px;
  7     height: 480px;
  8     margin: 0 auto;
  9     background-color: #bbada0;
 10     border-radius: 10px;
 11     position: relative;
 12 }
 13 .grid, .cell{
 14     width: 100px;
 15     height: 100px;
 16     border-radius: 6px;
 17 }
 18 .grid{
 19     background-color: #ccc0b3;
 20     float: left;
 21     margin-top: 16px;
 22     margin-left: 16px;
 23 }
 24 .cell{
 25     text-align: center;
 26     line-height: 100px;
 27     color: #fff;
 28     font-size: 60px;
 29     position: absolute;
 30 }
 31 
 32 /*由于想要更改游戏的网格数,设置了最大限制为8*8,所以此处前景格和背景格的样式如下*/
 33 /*此功能的实现应该还有更简便的方法,能力有限,目前学的知识只能做到这样*/
 34 #c00, #c01, #c02, #c03, #c04, #c05, #c06, #c07{top: 16px;}
 35 #c10, #c11, #c12, #c13, #c14, #c15, #c16, #c17{top: 132px;}
 36 #c20, #c21, #c22, #c23, #c24, #c25, #c26, #c27{top: 248px;}
 37 #c30, #c31, #c32, #c33, #c34, #c35, #c36, #c37{top: 364px;}
 38 #c40, #c41, #c42, #c43, #c44, #c45, #c46, #c47{top: 480px;}
 39 #c50, #c51, #c52, #c53, #c54, #c55, #c56, #c57{top: 596px;}
 40 #c60, #c61, #c62, #c63, #c64, #c65, #c66, #c67{top: 712px;}
 41 #c70, #c71, #c72, #c73, #c74, #c75, #c76, #c77{top: 828px;}
 42 
 43 #c00, #c10, #c20, #c30, #c40, #c50, #c60, #c70{left: 16px;}
 44 #c01, #c11, #c21, #c31, #c41, #c51, #c61, #c71{left: 132px;}
 45 #c02, #c12, #c22, #c32, #c42, #c52, #c62, #c72{left: 248px;}
 46 #c03, #c13, #c23, #c33, #c43, #c53, #c63, #c73{left: 364px;}
 47 #c04, #c14, #c24, #c34, #c44, #c54, #c64, #c74{left: 480px;}
 48 #c05, #c15, #c25, #c35, #c45, #c55, #c65, #c75{left: 596px;}
 49 #c06, #c16, #c26, #c36, #c46, #c56, #c66, #c76{left: 712px;}
 50 #c07, #c17, #c27, #c37, #c47, #c57, #c67, #c77{left: 828px;}
 51 
 52 .n2{background-color: #eee3da;}
 53 .n4{background-color: #ede0c8;}
 54 .n8{background-color: #f2b179;}
 55 .n16{background-color: #f59563;}
 56 .n32{background-color: #f67c5f;}
 57 .n64{background-color: #f65e3d;}
 58 .n128{background-color: #edcf72;}
 59 .n256{background-color: #edcc61;}
 60 .n512{background-color: #9c0;}
 61 .n1024{background-color: #33b5e5;}
 62 .n2048{background-color: #09c;}
 63 .n4096{background-color: #a6c;}
 64 .n8192{background-color: #93c;}
 65 
 66 .n2, .n4{color: #776e65;}
 67 .n1024, .n2048, .n4096, .n8192{font-size: 40px;}
 68 
 69 /*显示分数*/
 70 p{
 71     width: 480px;
 72     margin: 0 auto;
 73     font-family: Arial;
 74     font-weight: bold;
 75     font-size: 40px;
 76     padding-top: 50px;
 77 }
 78 
 79 /* 游戏结束 */
 80 /*Game Over*/
 81 #gameOver{
 82     width:100%;
 83     height:100%;
 84     position:absolute;
 85     top:0;
 86     left:0;
 87      display:none;
 88 }
 89 #gameOver>div{
 90     width:100%;
 91     height:100%;
 92      background-color:#555;
 93      opacity:0.5;
 94 }
 95 #gameOver>p{
 96     width:300px;
 97     height:200px;
 98      border:1px solid #edcf72;
 99      line-height:1.6em;
100      text-align:center;
101      background-color:#fff;
102      border-radius:10px;
103      position:absolute;
104      top:50%;
105      left:50%;
106      margin-top:-100px;
107      margin-left:-150px;
108 }
109 .button{
110     padding:10px;
111     border-radius:6px;
112      background-color:#9f8b77;
113      color:#fff;
114      cursor:pointer;
115 }

 

  1 /*2048.js*/
  2 var game = {
  3     data : [],        //存储所有单元格数据,二维数组
  4     RN : 4,            //总行数,可在此处改变,最大为8
  5     CN : 4,            //总列数,可在此处改变,最大为8
  6     score : 0,         //保存分数
  7     state: 0,         //游戏当前状态:Running|GameOver
  8      RUNNING : 1,    //运行中
  9      GAMEOVER : 0,    //游戏结束
 10      PLAYING : 2;    //动画播放中
 11 
 12      //获得所有背景格的html代码
 13      getGridHTML : function(){    
 14          for(var r = 0, arr = []; r < this.RN; r++){
 15              for(var c = 0; c < this.CN; c++){
 16                  arr.push("" + r + c);
 17              }
 18          }
 19          return '<div id="g' + arr.join('" class="grid"></div><div id="g') + '" class="grid"></div>';
 20      },
 21      //获得所有前景格的html代码
 22      getCellHTML : function(){    
 23          for(var r = 0, arr = []; r < this.RN; r++){
 24              for(var c = 0; c < this.CN; c++){
 25                  arr.push("" + r + c);
 26              }
 27          }
 28          return '<div id="c' + arr.join('" class="cell"></div><div id="c') + '" class="cell"></div>';
 29      },
 30      //判断游戏状态为结束
 31     isGameOver:function(){
 32       //如果没有满,则返回false
 33       if(!this.isFull()){
 34            return false;
 35       }else{//否则
 36            //从左上角第一个元素开始,遍历二维数组
 37            for(var r = 0; r < this.RN; r++){
 38             for(var c = 0; c < this.CN; c++){
 39                  //如果当前元素不是最右侧元素
 40                  if(c < this.CN-1){
 41                      // 如果当前元素==右侧元素
 42                       if(this.data[r][c] == this.data[r][c + 1]){
 43                            return false;
 44                      }
 45                  }
 46                  //如果当前元素不是最下方元素
 47                 if(r < this.RN - 1){
 48                      // 如果当前元素==下方元素
 49                       if(this.data[r][c] == this.data[r + 1][c]){
 50                            return false;
 51                       }
 52                 }
 53             }
 54            }
 55            return true;
 56         }
 57     },
 58     //开始游戏
 59     start : function(){
 60         var panel = document.getElementById('gridPanel');
 61         //游戏开始获得网格布局
 62         panel.innerHTML = this.getGridHTML() + this.getCellHTML();
 63         //将panel的高度设置为RN*116+16+"px"
 64         panel.style.height = this.RN * 116 + 16 + 'px';
 65         //将panel的宽度设置为CN*116+16+"px"
 66         panel.style.width = this.CN * 116 + 16 + 'px';
 67 
 68         this.data = [];        //清空旧数组        
 69         for(var r = 0; r < this.RN; r++){        //r从0开始,到<RN结束,每次+1    
 70             this.data.push([]);            //在data中压入一个空数组        
 71             for(var c = 0; c < this.CN; c++){    //c从0开始,到<CN结束,每次+1
 72                 this.data[r].push(0);            //向data中r行,压入一个0
 73             }
 74         }
 75 
 76         this.state = this.RUNNING;        //设置游戏状态
 77         this.score = 0;        //分数重置为0
 78           //找到游戏结束界面,隐藏
 79           var div = document.getElementById("gameOver");
 80           div.style.display = "none";
 81 
 82         this.randomNum();
 83         this.randomNum();
 84         this.updateView();
 85     },
 86     //初始界面生成两个随机数
 87     randomNum : function(){            //在随机的不重复的位置生成一个2或4    
 88         if(!this.isFull()){            //只有不满时,才尝试生成随机数
 89             for(;;){            
 90                 var r = Math.floor(Math.random() * this.RN);    //在0~RN-1之间生成一个行下标,存在r中
 91                 var c = Math.floor(Math.random() * this.CN);    //在0~CN-1之间生成一个列下标,存在c中
 92                 /*
 93                     如果data中r行c列等于0            
 94                     生成一个0~1之间的随机数
 95                         如果随机数>0.5,就在r行c列放入4
 96                         否则放入2
 97                 */
 98                 if (this.data[r][c] == 0) {        
 99                     this.data[r][c] = Math.random() > 0.5 ? 4 : 2;
100                     break;//    退出循环
101                 }            
102             }
103         }        
104     },
105     //将data数组中每个元素更新到页面div
106     updateView : function(){
107         //遍历data中每个元素的值
108         for(var r = 0; r < this.RN; r++){
109             for(var c = 0; c < this.CN; c++){
110                 //找到页面上和当前位置对相应的div
111                 var divObj = document.getElementById("c" + r + c);
112                 if (this.data[r][c] == 0) {        //如果当前值为0
113                     divObj.innerHTML = "";        //清除innerHTML
114                     divObj.className = "cell";    //还原className为"cell"
115                 }else{    
116                     divObj.innerHTML = this.data[r][c];        //否则,将当前值放入innerHTML
117                     divObj.className = "cell n" + this.data[r][c];        //修改className为"cell n" + 当前值
118                 }
119             }
120         }
121         var span = document.getElementById("score");
122         span.innerHTML = this.score;
123         //判断并修改游戏状态为GAMEOVER
124         if(this.isGameOver()){
125               this.state = this.GAMEOVER;
126               var div = document.getElementById("gameOver");
127               var span = document.getElementById("finalScore");
128               span.innerHTML = this.score;         
129               div.style.display = "block";            //修改div的style属性下的display子属性为"block"
130          }
131     },
132     //判断是否满格
133     isFull : function(){
134         for (var r = 0; r < this.RN; r++) {
135             for (var c = 0; c < this.CN; c++) {
136                 if (this.data[r][c] == 0) {        //如果当前元素等于0
137                     return false;        //返回false
138                 }
139             }
140         }
141         return true;    //遍历结束,返回true
142     },
143     //左移所有行
144     moveLeft : function(){
145         var before = this.data.toString();
146         for (var r = 0; r < this.RN; r++) {    //遍历data中的每一行
147             this.moveLeftInRow(r);            //左移当前行
148         }
149         var after = this.data.toString();
150         if(before != after){
151             animation.state();
152             // this.randomNum();
153             // this.updateView();
154         }
155     },
156     //左移一行,传入要移动的行号
157     moveLeftInRow : function(r){
158         //c从0开始,遍历当前行中的元素,到<CN-1结束,每次+1
159         for (var c = 0; c < this.CN-1; c++) {
160             //找到c之后下一个不为0的值的位置,存在next中
161             var nextc = this.getNextInRow(r,c);
162             if(nextc == -1){    
163                 break;        //如果nextc等于-1,退出循环
164             }else{            //否则
165                 if(this.data[r][c] == 0){        //如果当前位置等于0
166                     this.data[r][c] = this.data[r][nextc];    //将当前位置设为下一个位置的值
167                     this.data[r][nextc] = 0;    //将下一位置设为0
168                     var div = document.getElementById("c" + r + nextc);
169                     animation.addTask(div,r,nextc,r,c);
170                     c--;    //保证下次依然检查当前元素
171                 }else if(this.data[r][c] == this.data[r][nextc]){    //否则,如果当前位置等于下一位置
172                     this.data[r][c] *= 2;        //当前位置 = 当前位置值*2
173                     this.score += this.data[r][c];            //增加分数
174                     this.data[r][nextc] = 0;    //将下一位置设为0
175                     var div = document.getElementById("c" + r + nextc);
176                     animation.addTask(div,r,nextc,r,c);
177                 }
178             }
179         }
180     },
181     //找r行c列位置之后,不为0的下一个位置
182     getNextInRow : function(r,c){
183         for(var nextc = c + 1; nextc < this.CN; nextc++){    //nextc从c+1开始,遍历r行剩余元素
184             if(this.data[r][nextc] != 0){        //如果nextc不等于0
185                 return nextc;
186             }
187         }
188         return -1;        //循环结束,返回-1
189     },
190     //右移所有行
191     moveRight : function(){
192         var before = this.data.toString();
193         for (var r = 0; r < this.RN; r++) {    //遍历data中的每一行
194             this.moveRightInRow(r);            //右移当前行
195         }
196         var after = this.data.toString();
197         if(before != after){
198             animation.state();
199         }
200     },
201     //右移一行,传入要移动的行号
202     moveRightInRow : function(r){
203         //c从CN-1开始,到>0结束,每次-1
204         for (var c = this.CN-1; c > 0 ; c--) {
205             //找到c之后下一个不为0的值的位置,存在next中
206             var prevc = this.getPrevInRow(r,c);
207             if(prevc == -1){    
208                 break;        //如果prevc等于-1,退出循环
209             }else{            //否则
210                 if(this.data[r][c] == 0){        //如果当前位置等于0
211                     this.data[r][c] = this.data[r][prevc];    //将当前位置设为下一个位置的值
212                     this.data[r][prevc] = 0;    //将下一位置设为0
213                     var div = document.getElementById("c" + r + prevc);
214                     animation.addTask(div, r, prevc, r, c);
215                     c++;    //保证下次依然检查当前元素
216                 }else if(this.data[r][c] == this.data[r][prevc]){    //否则,如果当前位置等于下一位置
217                     this.data[r][c] *= 2;        //当前位置 = 当前位置值*2
218                     this.score += this.data[r][c];            //增加分数
219                     this.data[r][prevc] = 0;    //将下一位置设为0
220                     var div = document.getElementById("c"+r+prevc);
221                     animation.addTask(div,r,prevc,r,c);
222                 }
223             }
224         }
225     },
226     //找r行c列位置之后,不为0的下一个位置
227     getPrevInRow : function(r,c){
228         for(var prevc = c - 1; prevc >= 0; prevc--){    //prevc从c+1开始,遍历r行剩余元素
229             if(this.data[r][prevc] != 0){            //如果prevc不等于0
230                 return prevc;
231             }
232         }
233         return -1;        //循环结束,返回-1
234     },
235     //上移所有行
236     moveUp : function(){
237         var before = this.data.toString();
238         for (var c = 0; c < this.CN; c++) {    //遍历data中的每一列
239             this.moveUpInCol(c);            //右移当前行
240         }
241         var after = this.data.toString();
242         if(before != after){
243             animation.start();
244         }
245     },
246     //上移一列,传入要移动的列号
247     moveUpInCol : function(c){
248         //r从0开始,遍历当前列中的元素,到<RN-1结束,每次+1
249         for (var r = 0; r < this.RN-1 ; r++) {
250             //找到c之后下一个不为0的值的位置,存在next中
251             var nextr = this.getNextInCol(r,c);
252             if(nextr == -1){    
253                 break;        //如果nextr等于-1,退出循环
254             }else{            //否则
255                 if(this.data[r][c] == 0){        //如果当前位置等于0
256                     this.data[r][c] = this.data[nextr][c];    //将当前位置设为下一个位置的值
257                     this.data[nextr][c] = 0;    //将下一位置设为0
258                     var div = document.getElementById("c"+ nextr + c);
259                     animation.addTask(div,nextr,c,r,c);
260                     r--;    //保证下次依然检查当前元素
261                 }else if(this.data[r][c] == this.data[nextr][c]){    //否则,如果当前位置等于下一位置
262                     this.data[r][c] *= 2;        //当前位置 = 当前位置值*2
263                     this.score += this.data[r][c];            //增加分数
264                     this.data[nextr][c] = 0;    //将下一位置设为0
265                     var div = document.getElementById("c"+ nextr + c);
266                     animation.addTask(div,nextr,c,r,c);
267                 }
268             }
269         }
270     },
271     //找r行c列位置之后,不为0的下一个位置
272     getNextInCol : function(r,c){
273         for(var nextr = r + 1; nextr < this.RN; nextr++){    //nextr从c+1开始,遍历c列剩余元素
274             if(this.data[nextr][c] != 0){            //如果nextr不等于0
275                 return nextr;
276             }
277         }
278         return -1;        //循环结束,返回-1
279     },
280     //下移所有行
281     moveDown : function(){
282         var before = this.data.toString();
283         for (var c = 0; c < this.CN; c++) {    //遍历data中的每一列
284             this.moveDownInCol(c);            //右移当前行
285         }
286         var after = this.data.toString();
287         if(before != after){
288             animation.start();
289         }
290     },
291     //下移一列,传入要移动的列号
292     moveDownInCol : function(c){
293         //r从RN-1开始,遍历当前列中的元素,到>0结束,每次-1
294         for (var r = this.RN-1; r > 0 ; r--) {
295             //找到c之后下一个不为0的值的位置,存在next中
296             var prevr = this.getPrevInCol(r,c);
297             if(prevr == -1){    
298                 break;        //如果prevr等于-1,退出循环
299             }else{            //否则
300                 if(this.data[r][c] == 0){        //如果当前位置等于0
301                     this.data[r][c] = this.data[prevr][c];    //将当前位置设为下一个位置的值
302                     this.data[prevr][c] = 0;    //将下一位置设为0
303                     var div = document.getElementById("c" + prevr + c);
304                     animation.addTask(div,prevr,c,r,c);
305                     r++;    //保证下次依然检查当前元素
306                 }else if(this.data[r][c] == this.data[prevr][c]){    //否则,如果当前位置等于下一位置
307                     this.data[r][c] *= 2;        //当前位置 = 当前位置值*2
308                     this.score += this.data[r][c];            //增加分数
309                     this.data[prevr][c] = 0;    //将下一位置设为0
310                     var div = document.getElementById("c" + prevr + c);
311                     animation.addTask(div,prevr,c,r,c);
312                 }
313             }
314         }
315     },
316     //找r行c列位置之后,不为0的下一个位置
317     getPrevInCol : function(r,c){
318         for(var prevr = r - 1; prevr >= 0; prevr--){    //prevr从r-1开始,遍历c列剩余元素
319             if(this.data[prevr][c] != 0){            //如果prevr不等于0
320                 return prevr;
321             }
322         }
323         return -1;        //循环结束,返回-1
324     }
325 };
326 //当窗口加载后
327 window.onload = function(){
328     game.start();
329     /*键盘事件绑定*/
330     document.onkeydown = function(){
331         if(game.state == game.running){
332             var e = window.event || arguments[0];
333             var code = e.keyCode;
334             //如果按的是向左箭头,就调用左移方法
335             //左37 上38 右39 下40
336             if(code == 37){
337                 game.moveLeft();
338             }else if(code == 39){
339                 game.moveRight();
340             }else if(code == 38){
341                 game.moveUp();
342             }else if(code == 40){
343                 game.moveDown();
344             }
345         }        
346     }
347 }
 
 1 /*animation.js*/
 2 /*实现上下左右移动时的动画*/
 3 
 4 var animation = fucntion(){
 5     DURE : 500;        //总时间
 6     STEPS : 50;        //总步数
 7     moved : 0;        //当前移动步数
 8     timer : null;    //保存当前计时器的序号
 9     tasks : [];        //放入每次任务需要移动的所有元素和距离
10 
11     //像tasks数组中增加任务对象
12     addTask : function(divObj,currC,tarR,tarC){
13         var topDist = (tarR - currR) * 116;
14         var leftDist = (tarC - currC) * 116;
15         var topStep = topDist / this.STEPS;
16         var leftStep = leftDist / this.STEPS;
17         this.tasks.push(
18             {obj:divObj,top:topStep,left:leftStep}
19         );
20     },
21     move : function(){
22         for (var i = 0; i < this.tasks.length; i++) {
23             var task = this.tasks[i];
24             var style = getComputedStyle(task.obj);
25             task.obj.style.top = parseFloat(style.top) + task.top + "px";
26             task.obj.style.left = parseFloat(style.left) + task.left + "px";
27         }
28         if (--this.moved == 0) {
29             clearInterval(this.timer);
30             for (var i = 0; i < this.tasks.length; i++) {
31                 var task = this.tasks[i];
32                 task.obj.style.top = "";
33                 task.obj.style.left = "";
34             }
35             this.tasks = [];
36             game.randomNum();
37             game.state = game.RUNNING;
38             game.updateView();
39         }
40     },
41     start : function(){
42         game.state = game.PLAYING;
43         var self = this;
44         self.moved = self.STEPS;
45         self.timer = setInterval(function(){
46             self.move();
47         },self.DURE / self.STEPS);
48     }
49 };