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 };